2018-05-24 02:14:17 +00:00
|
|
|
#pragma once
|
2014-01-05 09:59:17 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL;
|
|
|
|
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
|
|
|
|
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
|
2014-01-05 09:59:17 +00:00
|
|
|
|
|
|
|
struct InputJoypadDirectInput {
|
2015-06-20 05:44:05 +00:00
|
|
|
Input& input;
|
|
|
|
InputJoypadDirectInput(Input& input) : input(input) {}
|
|
|
|
|
2014-01-05 09:59:17 +00:00
|
|
|
struct Joypad {
|
2015-06-12 13:14:38 +00:00
|
|
|
shared_pointer<HID::Joypad> hid{new HID::Joypad};
|
2014-01-05 09:59:17 +00:00
|
|
|
|
|
|
|
LPDIRECTINPUTDEVICE8 device = nullptr;
|
|
|
|
LPDIRECTINPUTEFFECT effect = nullptr;
|
|
|
|
|
|
|
|
uint32_t pathID = 0;
|
|
|
|
uint16_t vendorID = 0;
|
|
|
|
uint16_t productID = 0;
|
|
|
|
bool isXInputDevice = false;
|
|
|
|
};
|
|
|
|
vector<Joypad> joypads;
|
|
|
|
|
|
|
|
uintptr_t handle = 0;
|
|
|
|
LPDIRECTINPUT8 context = nullptr;
|
|
|
|
LPDIRECTINPUTDEVICE8 device = nullptr;
|
|
|
|
bool xinputAvailable = false;
|
2018-05-24 02:14:17 +00:00
|
|
|
uint effects = 0;
|
2014-01-05 09:59:17 +00:00
|
|
|
|
2018-05-24 02:14:17 +00:00
|
|
|
auto assign(shared_pointer<HID::Joypad> hid, uint groupID, uint inputID, int16_t value) -> void {
|
2015-06-12 13:14:38 +00:00
|
|
|
auto& group = hid->group(groupID);
|
|
|
|
if(group.input(inputID).value() == value) return;
|
2015-06-20 05:44:05 +00:00
|
|
|
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
2015-06-12 13:14:38 +00:00
|
|
|
group.input(inputID).setValue(value);
|
2014-01-05 09:59:17 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
2014-01-05 09:59:17 +00:00
|
|
|
for(auto& jp : joypads) {
|
|
|
|
if(FAILED(jp.device->Poll())) jp.device->Acquire();
|
|
|
|
|
|
|
|
DIJOYSTATE2 state;
|
|
|
|
if(FAILED(jp.device->GetDeviceState(sizeof(DIJOYSTATE2), &state))) continue;
|
|
|
|
|
2018-05-24 02:14:17 +00:00
|
|
|
for(auto n : range(4)) {
|
2014-01-05 09:59:17 +00:00
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 0, state.lX);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 1, state.lY);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 2, state.lZ);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 3, state.lRx);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 4, state.lRy);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Axis, 5, state.lRz);
|
|
|
|
|
2018-05-24 02:14:17 +00:00
|
|
|
uint pov = state.rgdwPOV[n];
|
2014-01-05 09:59:17 +00:00
|
|
|
int16_t xaxis = 0;
|
|
|
|
int16_t yaxis = 0;
|
|
|
|
|
|
|
|
if(pov < 36000) {
|
2017-10-07 07:28:12 +00:00
|
|
|
if(pov >= 31500 || pov <= 4500) yaxis = -32767;
|
2014-01-05 09:59:17 +00:00
|
|
|
if(pov >= 4500 && pov <= 13500) xaxis = +32767;
|
|
|
|
if(pov >= 13500 && pov <= 22500) yaxis = +32767;
|
2017-10-07 07:28:12 +00:00
|
|
|
if(pov >= 22500 && pov <= 31500) xaxis = -32767;
|
2014-01-05 09:59:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, xaxis);
|
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, yaxis);
|
|
|
|
}
|
|
|
|
|
2018-05-24 02:14:17 +00:00
|
|
|
for(auto n : range(128)) {
|
2014-01-05 09:59:17 +00:00
|
|
|
assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]);
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
devices.append(jp.hid);
|
2014-01-05 09:59:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto rumble(uint64_t id, bool enable) -> bool {
|
2014-01-05 09:59:17 +00:00
|
|
|
for(auto& jp : joypads) {
|
2015-06-12 13:14:38 +00:00
|
|
|
if(jp.hid->id() != id) continue;
|
2014-01-05 09:59:17 +00:00
|
|
|
if(jp.effect == nullptr) continue;
|
|
|
|
|
|
|
|
if(enable) jp.effect->Start(1, 0);
|
|
|
|
else jp.effect->Stop();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
Update to v103r15 release.
byuu says:
Changelog:
- ruby: rewrote the API interfaces for Video, Audio, Input
- ruby/audio: can now select the number of output channels (not useful
to higan, sorry)
- ruby/asio: various improvements
- tomoko: audio settings panel can now select separate audio devices
(for ASIO, OSS so far)
- tomoko: audio settings panel frequency and latency lists are
dynamically populated now
Note: due to the ruby API rewrite, most drivers will not compile. Right
now, the following work:
- video: Direct3D, XShm
- audio: ASIO, OSS
- input: Windows, SDL, Xlib
It takes a really long time to rewrite these (six hours to do the
above), so it's going to be a while before we're back at 100%
functionality again.
Errata:
- ASIO needs device(), setDevice()
- need to call setDevice() at program startup to populate
frequency/latency settings properly
- changing the device and/or frequency needs to update the emulator
resampler rates
The really hard part is going to be the last one: the only way to change
the emulator frequency is to flush all the audio streams and then
recompute all the coefficients for the resamplers. If this is called
during emulation, all audio streams will be erased and thus no sound
will be output. I'll most likely be forced to simply ignore
device/frequency changes until the user loads another game. It is at
least possible to toggle the latency dynamically.
2017-07-17 05:11:18 +00:00
|
|
|
auto initialize(uintptr handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
|
|
|
|
if(!handle) return false;
|
2014-01-05 09:59:17 +00:00
|
|
|
this->handle = handle;
|
|
|
|
this->context = context;
|
|
|
|
this->xinputAvailable = xinputAvailable;
|
|
|
|
context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
Update to v103r15 release.
byuu says:
Changelog:
- ruby: rewrote the API interfaces for Video, Audio, Input
- ruby/audio: can now select the number of output channels (not useful
to higan, sorry)
- ruby/asio: various improvements
- tomoko: audio settings panel can now select separate audio devices
(for ASIO, OSS so far)
- tomoko: audio settings panel frequency and latency lists are
dynamically populated now
Note: due to the ruby API rewrite, most drivers will not compile. Right
now, the following work:
- video: Direct3D, XShm
- audio: ASIO, OSS
- input: Windows, SDL, Xlib
It takes a really long time to rewrite these (six hours to do the
above), so it's going to be a while before we're back at 100%
functionality again.
Errata:
- ASIO needs device(), setDevice()
- need to call setDevice() at program startup to populate
frequency/latency settings properly
- changing the device and/or frequency needs to update the emulator
resampler rates
The really hard part is going to be the last one: the only way to change
the emulator frequency is to flush all the audio streams and then
recompute all the coefficients for the resamplers. If this is called
during emulation, all audio streams will be erased and thus no sound
will be output. I'll most likely be forced to simply ignore
device/frequency changes until the user loads another game. It is at
least possible to toggle the latency dynamically.
2017-07-17 05:11:18 +00:00
|
|
|
auto terminate() -> void {
|
2014-01-05 09:59:17 +00:00
|
|
|
for(auto& jp : joypads) {
|
|
|
|
jp.device->Unacquire();
|
|
|
|
if(jp.effect) jp.effect->Release();
|
|
|
|
jp.device->Release();
|
|
|
|
}
|
|
|
|
joypads.reset();
|
|
|
|
context = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto initJoypad(const DIDEVICEINSTANCE* instance) -> bool {
|
2014-01-05 09:59:17 +00:00
|
|
|
Joypad jp;
|
|
|
|
jp.vendorID = instance->guidProduct.Data1 >> 0;
|
|
|
|
jp.productID = instance->guidProduct.Data1 >> 16;
|
2014-01-28 10:04:58 +00:00
|
|
|
jp.isXInputDevice = false;
|
2014-01-05 09:59:17 +00:00
|
|
|
if(auto device = rawinput.find(jp.vendorID, jp.productID)) {
|
|
|
|
jp.isXInputDevice = device().isXInputDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Microsoft has intentionally imposed artificial restrictions on XInput devices when used with DirectInput
|
|
|
|
//a) the two triggers are merged into a single axis, making uniquely distinguishing them impossible
|
|
|
|
//b) rumble support is not exposed
|
|
|
|
//thus, it's always preferred to let the XInput driver handle these joypads
|
|
|
|
//but if the driver is not available (XInput 1.3 does not ship with stock Windows XP), fall back on DirectInput
|
|
|
|
if(jp.isXInputDevice && xinputAvailable) return DIENUM_CONTINUE;
|
|
|
|
|
|
|
|
if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) return DIENUM_CONTINUE;
|
|
|
|
jp.device = device;
|
|
|
|
device->SetDataFormat(&c_dfDIJoystick2);
|
|
|
|
device->SetCooperativeLevel((HWND)handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
|
|
|
|
|
|
|
effects = 0;
|
|
|
|
device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
|
|
|
|
device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR);
|
2015-06-12 13:14:38 +00:00
|
|
|
jp.hid->setRumble(effects > 0);
|
2014-01-05 09:59:17 +00:00
|
|
|
|
2014-01-28 10:04:58 +00:00
|
|
|
DIPROPGUIDANDPATH property;
|
|
|
|
memset(&property, 0, sizeof(DIPROPGUIDANDPATH));
|
|
|
|
property.diph.dwSize = sizeof(DIPROPGUIDANDPATH);
|
|
|
|
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
|
|
property.diph.dwObj = 0;
|
|
|
|
property.diph.dwHow = DIPH_DEVICE;
|
|
|
|
device->GetProperty(DIPROP_GUIDANDPATH, &property.diph);
|
|
|
|
string devicePath = (const char*)utf8_t(property.wszPath);
|
2018-09-02 14:06:41 +00:00
|
|
|
jp.pathID = Hash::CRC32(devicePath).value();
|
2018-05-24 02:14:17 +00:00
|
|
|
jp.hid->setVendorID(jp.vendorID);
|
|
|
|
jp.hid->setProductID(jp.productID);
|
|
|
|
jp.hid->setPathID(jp.pathID);
|
2014-01-28 10:04:58 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
if(jp.hid->rumble()) {
|
2014-01-05 09:59:17 +00:00
|
|
|
//disable auto-centering spring for rumble support
|
|
|
|
DIPROPDWORD property;
|
|
|
|
memset(&property, 0, sizeof(DIPROPDWORD));
|
|
|
|
property.diph.dwSize = sizeof(DIPROPDWORD);
|
|
|
|
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
|
|
property.diph.dwObj = 0;
|
|
|
|
property.diph.dwHow = DIPH_DEVICE;
|
|
|
|
property.dwData = false;
|
|
|
|
device->SetProperty(DIPROP_AUTOCENTER, &property.diph);
|
|
|
|
|
Update to higan and icarus v095r15 release.
r13 and r14 weren't posted as individual releases, but their changelogs
were posted.
byuu says about r13:
I'm not going to be posting WIPs for r13 and above for a while.
The reason is that I'm working on the major manifest overhaul I've
discussed previously on the icarus subforum.
I'm recreating my boards database from scratch using the map files
and the new map analyzer. The only games that will load are ones
I've created board definitions for, and updated
sfc/cartridge/markup.cpp to parse. Once I've finished all the
boards, then I'll update the heuristics.
Then finally, I'll sync the syntax changes over to the fc, gb, gba
cores.
Once that's done, I'll start posting WIPs again, along with a new
build of icarus.
But I'll still post changelogs as I work through things.
Changelog (r13):
- preservation: created new database-builder tool (merges
region-specific databases with boards)
- icarus: support new, external database format
(~/.config/icarus/Database/(Super Famicom.bml, ...)
- added 1A3B-(10,11,12); 1A3B-20
byuu says about r14:
r14 work:
I successfully created mappings for every board used in the US set.
I also updated icarus' heuristics to use the new mappings, and
created ones there for the boards that are only in the JP set.
Then I patched icarus to support pulling games out of the database
when it's used on a game folder to generate a manifest file.
Then I updated a lot of code in higan/sfc to support the new mapping
syntax. sfc/cartridge/markup.cpp is about half the size it used to
be with the new mappings, and I was able to kill off both map/id and
map/select entirely.
Then I updated all four emulated systems (and both subsystems) to
use "board" as the root node, and harmonized their syntax (made them
all more consistent with each other.)
Then I added a manifest viewer to the tools window+menu. It's kind
of an advanced user feature, but oh well. No reason to coddle people
when the feature is very useful for developers. The viewer will show
all manifests in order when you load multi-cart games as well.
Still not going to call any syntax 100% done right now, but
thankfully with the new manifest-free folders, nobody will have to
do anything to use the new format. Just download the new version and
go.
The Super Famicom Event stuff is currently broken (CC92/PF94
boards). That's gonna be fun to support.
byuu says about r15:
EDIT: small bug in icarus with heuristics. Edit
core/super-famicom.cpp line 27:
if(/*auto*/ markup = cartridge.markup) {
Gotta remove that "auto" so that it returns valid markup.
Resolved the final concerns I had with the new manifest format.
Right now there are two things that are definitely broken: MCC (BS-X
Town cart) and Event (CC '92 and PF'94).
And there are a few things that are untested: SPC7110, EpsonRTC,
SharpRTC, SDD1+RAM, SufamiTurbo, BS-X slotted carts.
2015-12-19 08:52:34 +00:00
|
|
|
DWORD dwAxes[2] = {(DWORD)DIJOFS_X, (DWORD)DIJOFS_Y};
|
2014-01-05 09:59:17 +00:00
|
|
|
LONG lDirection[2] = {0, 0};
|
|
|
|
DICONSTANTFORCE force;
|
|
|
|
force.lMagnitude = DI_FFNOMINALMAX; //full force
|
|
|
|
DIEFFECT effect;
|
|
|
|
memset(&effect, 0, sizeof(DIEFFECT));
|
|
|
|
effect.dwSize = sizeof(DIEFFECT);
|
|
|
|
effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
|
|
|
|
effect.dwDuration = INFINITE;
|
|
|
|
effect.dwSamplePeriod = 0;
|
|
|
|
effect.dwGain = DI_FFNOMINALMAX;
|
|
|
|
effect.dwTriggerButton = DIEB_NOTRIGGER;
|
|
|
|
effect.dwTriggerRepeatInterval = 0;
|
|
|
|
effect.cAxes = 2;
|
|
|
|
effect.rgdwAxes = dwAxes;
|
|
|
|
effect.rglDirection = lDirection;
|
|
|
|
effect.lpEnvelope = 0;
|
|
|
|
effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
|
|
effect.lpvTypeSpecificParams = &force;
|
|
|
|
effect.dwStartDelay = 0;
|
|
|
|
device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL);
|
|
|
|
}
|
|
|
|
|
2018-05-24 02:14:17 +00:00
|
|
|
for(auto n : range(6)) jp.hid->axes().append(n);
|
|
|
|
for(auto n : range(8)) jp.hid->hats().append(n);
|
|
|
|
for(auto n : range(128)) jp.hid->buttons().append(n);
|
2014-01-05 09:59:17 +00:00
|
|
|
joypads.append(jp);
|
|
|
|
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto initAxis(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
|
2014-01-05 09:59:17 +00:00
|
|
|
DIPROPRANGE range;
|
|
|
|
memset(&range, 0, sizeof(DIPROPRANGE));
|
|
|
|
range.diph.dwSize = sizeof(DIPROPRANGE);
|
|
|
|
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
|
|
range.diph.dwHow = DIPH_BYID;
|
|
|
|
range.diph.dwObj = instance->dwType;
|
|
|
|
range.lMin = -32768;
|
|
|
|
range.lMax = +32767;
|
|
|
|
device->SetProperty(DIPROP_RANGE, &range.diph);
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto initEffect(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
|
2014-01-05 09:59:17 +00:00
|
|
|
effects++;
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL {
|
2014-01-05 09:59:17 +00:00
|
|
|
return ((InputJoypadDirectInput*)p)->initJoypad(instance);
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
|
2014-01-05 09:59:17 +00:00
|
|
|
return ((InputJoypadDirectInput*)p)->initAxis(instance);
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
|
2014-01-05 09:59:17 +00:00
|
|
|
return ((InputJoypadDirectInput*)p)->initEffect(instance);
|
|
|
|
}
|