Support chusan

This commit is contained in:
akiroz 2022-03-18 01:45:24 +08:00
parent 93c464518e
commit 21932f9bde
No known key found for this signature in database
GPG Key ID: 8A5957C4A2F68ACC
4 changed files with 130 additions and 75 deletions

View File

@ -2,12 +2,29 @@
chuniio driver for tasoller custom 2.0 firmware chuniio driver for tasoller custom 2.0 firmware
Uses WinUSB driver instead of libusb Supported titles:
- Chunithm
Written in Zig (Sorry, I can't stand Windows-flavoured C++) - Chunithm NEW
Downloads avaliable in [releases](https://dev.s-ul.net/akiroz/chuniio-tasoller/-/releases) Downloads avaliable in [releases](https://dev.s-ul.net/akiroz/chuniio-tasoller/-/releases)
## Configuration
segatools.ini
```ini
[chuniio]
path=chuniio_tasoller.dll
; path=chuniio_tasoller_x64.dll
;; For Chunithm NEW, set to 1
chusan=0
[io3]
test=0x31
service=0x32
coin=0x33
```
## USB Protocol ## USB Protocol
Custom firmware USB device: 1CCF:2333 Custom firmware USB device: 1CCF:2333

View File

@ -2,15 +2,19 @@ const std = @import("std");
const CrossTarget = std.zig.CrossTarget; const CrossTarget = std.zig.CrossTarget;
pub fn build(b: *std.build.Builder) void { pub fn build(b: *std.build.Builder) void {
const target = CrossTarget{ .os_tag = .windows, .cpu_arch = .i386, .abi = .msvc };
const lib = b.addSharedLibrary("chuniio_tasoller", "src/main.zig", .unversioned); const lib86 = b.addSharedLibrary("chuniio_tasoller", "src/main.zig", .unversioned);
lib.setBuildMode(b.standardReleaseOptions()); lib86.setBuildMode(b.standardReleaseOptions());
lib.setTarget(target); lib86.setTarget(CrossTarget{ .os_tag = .windows, .cpu_arch = .i386, .abi = .msvc });
lib.install(); lib86.install();
const lib64 = b.addSharedLibrary("chuniio_tasoller_x64", "src/main.zig", .unversioned);
lib64.setBuildMode(b.standardReleaseOptions());
lib64.setTarget(CrossTarget{ .os_tag = .windows, .cpu_arch = .x86_64, .abi = .msvc });
lib64.install();
const exe = b.addExecutable("tasoller_test", "src/main.zig"); // const exe = b.addExecutable("tasoller_test", "src/main.zig");
lib.setBuildMode(b.standardReleaseOptions()); // lib.setBuildMode(b.standardReleaseOptions());
exe.setTarget(target); // exe.setTarget(target);
exe.install(); // exe.install();
} }

View File

@ -1,3 +1,6 @@
[chuniio]
chusan=1
[io3] [io3]
test=0x31 test=0x31
service=0x32 service=0x32

View File

@ -1,7 +1,12 @@
const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const win32 = @import("zigwin32/win32.zig"); const win32 = @import("zigwin32/win32.zig");
const W = std.unicode.utf8ToUtf16LeStringLiteral;
const HANDLE = std.os.windows.HANDLE;
const LPVOID = std.os.windows.LPVOID;
const DWORD = std.os.windows.DWORD; const DWORD = std.os.windows.DWORD;
const BOOL = win32.foundation.BOOL;
const HRESULT = std.os.windows.HRESULT; const HRESULT = std.os.windows.HRESULT;
const S_OK = std.os.windows.S_OK; const S_OK = std.os.windows.S_OK;
const E_FAIL = std.os.windows.E_FAIL; const E_FAIL = std.os.windows.E_FAIL;
@ -10,6 +15,12 @@ const GetLastError = win32.foundation.GetLastError;
const GetAsyncKeyState = win32.ui.input.keyboard_and_mouse.GetAsyncKeyState; const GetAsyncKeyState = win32.ui.input.keyboard_and_mouse.GetAsyncKeyState;
const GetPrivateProfileIntA = win32.system.windows_programming.GetPrivateProfileIntA; const GetPrivateProfileIntA = win32.system.windows_programming.GetPrivateProfileIntA;
const PAGE_READWRITE = win32.system.memory.PAGE_READWRITE;
const FILE_MAP_ALL_ACCESS = win32.system.memory.FILE_MAP_ALL_ACCESS;
const CreateFileMapping = win32.system.memory.CreateFileMapping;
const MapViewOfFile = win32.system.memory.MapViewOfFile;
const DLL_PROCESS_ATTACH = win32.system.system_services.DLL_PROCESS_ATTACH;
const GENERIC_READ = win32.system.system_services.GENERIC_READ; const GENERIC_READ = win32.system.system_services.GENERIC_READ;
const GENERIC_WRITE = win32.system.system_services.GENERIC_WRITE; const GENERIC_WRITE = win32.system.system_services.GENERIC_WRITE;
const OPEN_EXISTING = win32.storage.file_system.OPEN_EXISTING; const OPEN_EXISTING = win32.storage.file_system.OPEN_EXISTING;
@ -32,60 +43,13 @@ const WinUsb_Initialize = win32.devices.usb.WinUsb_Initialize;
const WinUsb_WritePipe = win32.devices.usb.WinUsb_WritePipe; const WinUsb_WritePipe = win32.devices.usb.WinUsb_WritePipe;
const WinUsb_ReadPipe = win32.devices.usb.WinUsb_ReadPipe; const WinUsb_ReadPipe = win32.devices.usb.WinUsb_ReadPipe;
const chuni_io_slider_callback_t = ?fn ([*c]const u8) callconv(.C) void;
const Config = struct { const Config = struct {
test_key: i32, test_key: i32,
serv_key: i32, serv_key: i32,
coin_key: i32, coin_key: i32,
chusan: i32,
}; };
pub fn main() !void {
var ground = usb_out[4*3 .. 29*3];
var left = usb_out[32*3 .. 56*3];
var right = usb_out[56*3 .. 80*3];
var i: u32 = 0;
while(i < 32) : (i += 1) {
if(i < ground.len/3) {
ground[i*3 + 1] = 0x13;
ground[i*3 + 0] = 0x7a;
ground[i*3 + 2] = 0x5f;
if(i % 2 == 0) {
ground[i*3 + 1] = 0x66;
ground[i*3 + 0] = 0xee;
ground[i*3 + 2] = 0xab;
}
if(i % 4 == 0) {
ground[i*3 + 1] = 0xe1;
ground[i*3 + 0] = 0x28;
ground[i*3 + 2] = 0x85;
}
}
if(i < left.len/3) {
left[i*3 + 1] = 0x13;
left[i*3 + 0] = 0x7a;
left[i*3 + 2] = 0x5f;
}
if(i < right.len/3) {
right[i*3 + 1] = 0x13;
right[i*3 + 0] = 0x7a;
right[i*3 + 2] = 0x5f;
}
}
try tasoller_init();
while(true) {
if(WinUsb_WritePipe(tasoller, 0x03, @ptrCast(*u8, &usb_out), usb_out.len, &i, null) == 0) {
std.log.warn("[chuniio] WinUsb_WritePipe: {any}", .{GetLastError()});
}
if(WinUsb_ReadPipe(tasoller, 0x84, @ptrCast(*u8, &usb_in), usb_in.len, &i, null) == 0) {
std.log.warn("[chuniio] WinUsb_ReadPipe: {any}", .{GetLastError()});
}
std.time.sleep(100_000_000);
}
}
// MAIN DRIVER ======================================================================================================
var cfg: ?Config = null; var cfg: ?Config = null;
var thread_op = std.Thread.Mutex{}; var thread_op = std.Thread.Mutex{};
var slider_active = false; var slider_active = false;
@ -94,9 +58,28 @@ var input_thread: ?std.Thread = null;
var usb_out_op = std.Thread.Mutex{}; var usb_out_op = std.Thread.Mutex{};
var usb_out = std.mem.zeroes([80*3]u8); var usb_out = std.mem.zeroes([80*3]u8);
var usb_in = std.mem.zeroes([0x24]u8); var usb_in: ?[]u8 = null;
var tasoller: ?*anyopaque = null; var tasoller: ?*anyopaque = null;
// Test program
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
usb_in = try gpa.allocator().alloc(u8, 0x24);
var i: u32 = 0;
try tasoller_init();
while(true) {
if(WinUsb_WritePipe(tasoller, 0x03, @ptrCast(*u8, &usb_out), usb_out.len, &i, null) == 0) {
std.log.warn("[chuniio] WinUsb_WritePipe: {any}", .{GetLastError()});
}
if(WinUsb_ReadPipe(tasoller, 0x84, usb_in.ptr, usb_in.len, &i, null) == 0) {
std.log.warn("[chuniio] WinUsb_ReadPipe: {any}", .{GetLastError()});
}
std.time.sleep(100_000_000);
}
}
// MAIN DRIVER ======================================================================================================
fn tasoller_init() !void { fn tasoller_init() !void {
const hDevInfo = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, null, null, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); const hDevInfo = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, null, null, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
@ -112,7 +95,7 @@ fn tasoller_init() !void {
var devicePath: ?[*:0]const u8 = null; var devicePath: ?[*:0]const u8 = null;
while(SetupDiEnumDeviceInterfaces(hDevInfo, null, &GUID_DEVINTERFACE_USB_DEVICE, ifIdx, &devIf) != 0) : (ifIdx += 1) { while(SetupDiEnumDeviceInterfaces(hDevInfo, null, &GUID_DEVINTERFACE_USB_DEVICE, ifIdx, &devIf) != 0) : (ifIdx += 1) {
var requiredSize: u32 = 0; var requiredSize: u32 = 0;
var detailBuf = std.mem.zeroes([263]u8); var detailBuf align(4) = std.mem.zeroes([512]u8);
var devIfDetail = @ptrCast(*SP_DEVICE_INTERFACE_DETAIL_DATA_A, &detailBuf); var devIfDetail = @ptrCast(*SP_DEVICE_INTERFACE_DETAIL_DATA_A, &detailBuf);
devIfDetail.cbSize = @sizeOf(SP_DEVICE_INTERFACE_DETAIL_DATA_A); devIfDetail.cbSize = @sizeOf(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
if(SetupDiGetDeviceInterfaceDetailA(hDevInfo, &devIf, devIfDetail, 263, &requiredSize, null) == 0) { if(SetupDiGetDeviceInterfaceDetailA(hDevInfo, &devIf, devIfDetail, 263, &requiredSize, null) == 0) {
@ -163,18 +146,21 @@ fn tasoller_init() !void {
// Poll input regardless of slider start/stop // Poll input regardless of slider start/stop
fn input_thread_proc() void { fn input_thread_proc() void {
std.log.info("[chuniio] Input thread started", .{});
while(true) { while(true) {
var len: u32 = 0; var len: u32 = 0;
if(WinUsb_ReadPipe(tasoller, 0x84, @ptrCast(*u8, &usb_in), usb_in.len, &len, null) == 0) { if(WinUsb_ReadPipe(tasoller, 0x84, @ptrCast(*u8, usb_in.?.ptr), usb_in.?.len, &len, null) == 0) {
std.log.warn("[chuniio] WinUsb_ReadPipe: {any}", .{GetLastError()}); std.log.warn("[chuniio] WinUsb_ReadPipe: {any}", .{GetLastError()});
} }
} }
} }
const chuni_io_slider_callback_t = ?fn ([*c]const u8) callconv(.C) void;
fn slider_thread_proc(callback: chuni_io_slider_callback_t) void { fn slider_thread_proc(callback: chuni_io_slider_callback_t) void {
var pressure = std.mem.zeroes([32]u8); var pressure = std.mem.zeroes([32]u8);
while(slider_active) { while(slider_active) {
for(usb_in[4..]) |byte, i| { for(usb_in.?[4..]) |byte, i| {
// Tasoller order: top->bottom, left->right // Tasoller order: top->bottom, left->right
// Chunithm order: top->bottom, right->left // Chunithm order: top->bottom, right->left
pressure[if(i%2 == 0) 30-i else 32-i] = byte; pressure[if(i%2 == 0) 30-i else 32-i] = byte;
@ -184,37 +170,77 @@ fn slider_thread_proc(callback: chuni_io_slider_callback_t) void {
} }
} }
// DLL EXPORTS ======================================================================================================
export fn chuni_io_get_api_version() c_ushort { export fn chuni_io_get_api_version() c_ushort {
return 0x0101; return 0x0101;
} }
export fn chuni_io_jvs_init() HRESULT { pub export fn DllMain(hDllHandle: HANDLE, dwReason: DWORD, lpreserved: LPVOID) BOOL {
_ = hDllHandle;
_ = lpreserved;
if(dwReason != DLL_PROCESS_ATTACH) return win32.zig.TRUE;
std.log.info("[chuniio] Initializing", .{});
const cfg_file = ".\\segatools.ini"; const cfg_file = ".\\segatools.ini";
cfg = .{ cfg = .{
.test_key = @intCast(i32, GetPrivateProfileIntA("io3", "test", 0x31, cfg_file)), .test_key = @intCast(i32, GetPrivateProfileIntA("io3", "test", 0x31, cfg_file)),
.serv_key = @intCast(i32, GetPrivateProfileIntA("io3", "service", 0x32, cfg_file)), .serv_key = @intCast(i32, GetPrivateProfileIntA("io3", "service", 0x32, cfg_file)),
.coin_key = @intCast(i32, GetPrivateProfileIntA("io3", "coin", 0x33, cfg_file)), .coin_key = @intCast(i32, GetPrivateProfileIntA("io3", "coin", 0x33, cfg_file)),
.chusan = @intCast(i32, GetPrivateProfileIntA("chuniio", "chusan", 0, cfg_file)),
}; };
tasoller_init() catch {
return E_FAIL; if(cfg.?.chusan == 0) {
}; std.log.info("[chuniio] Mode: chuni", .{});
input_thread = std.Thread.spawn(.{}, input_thread_proc, .{}) catch |err| { var gpa = std.heap.GeneralPurposeAllocator(.{}){};
std.log.err("[chuniio] Spawn input thread: {any}", .{err}); usb_in = gpa.allocator().alloc(u8, 0x24) catch |err| {
return E_FAIL; std.log.err("[chuniio] alloc: {any}", .{err});
}; return win32.zig.FALSE;
};
} else {
std.log.info("[chuniio] Mode: chusan", .{});
const szName = W("Global\\ChuniioTasoller");
const hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, null, PAGE_READWRITE, 0, 0x24, szName) orelse {
std.log.err("[chuniio] CreateFileMapping: {any}", .{GetLastError()});
return win32.zig.FALSE;
};
const pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0x24) orelse {
std.log.err("[chuniio] MapViewOfFile: {any}", .{GetLastError()});
return win32.zig.FALSE;
};
usb_in = @ptrCast([*]u8, pBuf)[0..0x24];
}
if(builtin.cpu.arch == .i386) {
tasoller_init() catch {
return win32.zig.FALSE;
};
input_thread = std.Thread.spawn(.{}, input_thread_proc, .{}) catch |err| {
std.log.err("[chuniio] Spawn input thread: {any}", .{err});
return win32.zig.FALSE;
};
}
return win32.zig.TRUE;
}
export fn chuni_io_jvs_init() HRESULT {
return S_OK; return S_OK;
} }
export fn chuni_io_jvs_poll(opbtn: ?[*]u8, beams: ?[*]u8) void { export fn chuni_io_jvs_poll(opbtn: ?[*]u8, beams: ?[*]u8) void {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .x86_64) return;
if(opbtn == null or beams == null) return; if(opbtn == null or beams == null) return;
if(GetAsyncKeyState(cfg.?.test_key) != 0 or (usb_in[3] & (1 << 7)) != 0) opbtn.?.* |= (1 << 0); if(GetAsyncKeyState(cfg.?.test_key) != 0 or (usb_in.?[3] & (1 << 7)) != 0) opbtn.?.* |= (1 << 0);
if(GetAsyncKeyState(cfg.?.test_key) != 0 or (usb_in[3] & (1 << 6)) != 0) opbtn.?.* |= (1 << 1); if(GetAsyncKeyState(cfg.?.test_key) != 0 or (usb_in.?[3] & (1 << 6)) != 0) opbtn.?.* |= (1 << 1);
beams.?.* |= usb_in[3] & 0b111111; beams.?.* |= usb_in.?[3] & 0b111111;
} }
var coin_conter: c_ushort = 0; var coin_conter: c_ushort = 0;
var coin_prev_depressed = false; var coin_prev_depressed = false;
export fn chuni_io_jvs_read_coin_counter(total: ?*c_ushort) void { export fn chuni_io_jvs_read_coin_counter(total: ?*c_ushort) void {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .x86_64) return;
if(total == null) return; if(total == null) return;
const coin_depressed = GetAsyncKeyState(cfg.?.coin_key) != 0; const coin_depressed = GetAsyncKeyState(cfg.?.coin_key) != 0;
if(coin_depressed and !coin_prev_depressed) coin_conter += 1; if(coin_depressed and !coin_prev_depressed) coin_conter += 1;
@ -227,7 +253,9 @@ export fn chuni_io_slider_init() HRESULT {
} }
export fn chuni_io_slider_start(callback: chuni_io_slider_callback_t) void { export fn chuni_io_slider_start(callback: chuni_io_slider_callback_t) void {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
if(callback == null) return; if(callback == null) return;
thread_op.lock(); thread_op.lock();
defer thread_op.unlock(); defer thread_op.unlock();
if(slider_thread == null) { if(slider_thread == null) {
@ -240,6 +268,8 @@ export fn chuni_io_slider_start(callback: chuni_io_slider_callback_t) void {
} }
export fn chuni_io_slider_stop() void { export fn chuni_io_slider_stop() void {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
thread_op.lock(); thread_op.lock();
defer thread_op.unlock(); defer thread_op.unlock();
if(slider_thread != null) { if(slider_thread != null) {
@ -250,6 +280,7 @@ export fn chuni_io_slider_stop() void {
} }
export fn chuni_io_slider_set_leds(rgb: ?[*]u8) void { export fn chuni_io_slider_set_leds(rgb: ?[*]u8) void {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
if(rgb == null) return; if(rgb == null) return;
var n: u32 = 0; var n: u32 = 0;