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
Uses WinUSB driver instead of libusb
Written in Zig (Sorry, I can't stand Windows-flavoured C++)
Supported titles:
- Chunithm
- Chunithm NEW
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
Custom firmware USB device: 1CCF:2333

View File

@ -2,15 +2,19 @@ const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
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);
lib.setBuildMode(b.standardReleaseOptions());
lib.setTarget(target);
lib.install();
const lib86 = b.addSharedLibrary("chuniio_tasoller", "src/main.zig", .unversioned);
lib86.setBuildMode(b.standardReleaseOptions());
lib86.setTarget(CrossTarget{ .os_tag = .windows, .cpu_arch = .i386, .abi = .msvc });
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");
lib.setBuildMode(b.standardReleaseOptions());
exe.setTarget(target);
exe.install();
// const exe = b.addExecutable("tasoller_test", "src/main.zig");
// lib.setBuildMode(b.standardReleaseOptions());
// exe.setTarget(target);
// exe.install();
}

View File

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

View File

@ -1,7 +1,12 @@
const builtin = @import("builtin");
const std = @import("std");
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 BOOL = win32.foundation.BOOL;
const HRESULT = std.os.windows.HRESULT;
const S_OK = std.os.windows.S_OK;
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 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_WRITE = win32.system.system_services.GENERIC_WRITE;
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_ReadPipe = win32.devices.usb.WinUsb_ReadPipe;
const chuni_io_slider_callback_t = ?fn ([*c]const u8) callconv(.C) void;
const Config = struct {
test_key: i32,
serv_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 thread_op = std.Thread.Mutex{};
var slider_active = false;
@ -94,9 +58,28 @@ var input_thread: ?std.Thread = null;
var usb_out_op = std.Thread.Mutex{};
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;
// 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 {
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;
while(SetupDiEnumDeviceInterfaces(hDevInfo, null, &GUID_DEVINTERFACE_USB_DEVICE, ifIdx, &devIf) != 0) : (ifIdx += 1) {
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);
devIfDetail.cbSize = @sizeOf(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
if(SetupDiGetDeviceInterfaceDetailA(hDevInfo, &devIf, devIfDetail, 263, &requiredSize, null) == 0) {
@ -163,18 +146,21 @@ fn tasoller_init() !void {
// Poll input regardless of slider start/stop
fn input_thread_proc() void {
std.log.info("[chuniio] Input thread started", .{});
while(true) {
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()});
}
}
}
const chuni_io_slider_callback_t = ?fn ([*c]const u8) callconv(.C) void;
fn slider_thread_proc(callback: chuni_io_slider_callback_t) void {
var pressure = std.mem.zeroes([32]u8);
while(slider_active) {
for(usb_in[4..]) |byte, i| {
for(usb_in.?[4..]) |byte, i| {
// Tasoller order: top->bottom, left->right
// Chunithm order: top->bottom, right->left
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 {
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";
cfg = .{
.test_key = @intCast(i32, GetPrivateProfileIntA("io3", "test", 0x31, cfg_file)),
.serv_key = @intCast(i32, GetPrivateProfileIntA("io3", "service", 0x32, 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;
};
input_thread = std.Thread.spawn(.{}, input_thread_proc, .{}) catch |err| {
std.log.err("[chuniio] Spawn input thread: {any}", .{err});
return E_FAIL;
};
if(cfg.?.chusan == 0) {
std.log.info("[chuniio] Mode: chuni", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
usb_in = gpa.allocator().alloc(u8, 0x24) catch |err| {
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;
}
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(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);
beams.?.* |= usb_in[3] & 0b111111;
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);
beams.?.* |= usb_in.?[3] & 0b111111;
}
var coin_conter: c_ushort = 0;
var coin_prev_depressed = false;
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;
const coin_depressed = GetAsyncKeyState(cfg.?.coin_key) != 0;
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 {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
if(callback == null) return;
thread_op.lock();
defer thread_op.unlock();
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 {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
thread_op.lock();
defer thread_op.unlock();
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 {
if(cfg.?.chusan != 0 and builtin.cpu.arch != .i386) return;
if(rgb == null) return;
var n: u32 = 0;