Compare commits

...

12 Commits
1.6.1 ... main

Author SHA1 Message Date
scrib-bler
c5f6496adf Changed default dll behaviour to be compatiable with chusanApp games 2024-08-06 18:37:51 +01:00
8078821d7c Attempt to make configuration instructions clearer. 2024-07-26 17:21:37 +00:00
1bc0bc65e3 Update README.md
Removed reference to chuniio branch, as all versions of Dniel97 segatools now support the LED change
2024-06-09 21:39:35 +00:00
da041efcec LUMINOUS support
added LUMINOUS support, kek
2024-03-22 05:51:09 +00:00
a67e50866e removed [io3] section
removed [io3] section from readme as it was confusing for some people
2024-03-22 05:49:01 +00:00
8ace7dace5 clean up alignment
probably still bad
2024-01-09 19:49:08 +00:00
84489e8fae Make tower LEDs more arcade accurate
Now using all the tower LED data sent by the game instead of just using 1 and repeating it across all LEDs.

This will have no effect in game as the game simply sets them all to the same colour anyway :(
2024-01-09 19:36:56 +00:00
1cfbf74bc6 API version bumped to 0x0102 in line wtih Dniel97 segatools
0x0102 is to signify the Tower LED support, which has already been implemented. This is so that we are reporting the correct API version.
2023-12-25 23:25:48 +00:00
dbd35c2c8c Merge pull request 'Added support for zig v0.11' (#1) from Dniel97/chuniio-tasoller:main into main
Reviewed-on: #1
Tested with zig 0.11 and zig 0.12dev. Tested resulted dlls in game.
2023-12-21 19:24:18 +00:00
cd59aca9e5
Added support for zig v0.11 2023-12-21 02:55:11 +01:00
aef4fa5c56 change chuni_io_led_init from c_int to HRESULT to better match segatools implementation 2023-12-21 00:09:48 +00:00
15be6e1a2c Update README.md 2023-12-19 23:58:13 +00:00
5 changed files with 155 additions and 134 deletions

View File

@ -1,39 +1,38 @@
## chuniio-tasoller *with air tower LEDs*
chuniio driver for tasoller custom 2.0 firmware
now with working air tower LEDs when using [Dniel97 segatools](https://gitea.tendokyu.moe/Dniel97/segatools)
now with working air tower LEDs when using [Dniel97 segatools](https://gitea.tendokyu.moe/Dniel97/segatools/src/branch/chuniio)
thanks to:
**akiroz** for the original code and USB Protocol information
[**Dniel97**](https://gitea.tendokyu.moe/Dniel97) for rewritting my failed attempts at making this work
the current implementation may not be fully arcade accurate, but should be visually acceptable during gameplay
this should hopefully be addressed in the future
- [**akiroz**](https://dev.s-ul.net/akiroz/chuniio-tasoller) for the original code and USB Protocol information
- [**Dniel97**](https://gitea.tendokyu.moe/Dniel97) for rewritting my failed attempts at making this work
Supported titles:
- Chunithm
- Chunithm NEW
- Chunithn SUN
- Chunithm SUN PLUS
- CHUNITHM
- CHUNITHM NEW
- CHUNITHM NEW PLUS
- CHUNITHM SUN
- CHUNITHM SUN PLUS
- CHUNITHM LUMINOUS
## Configuration
- For modern CHUNITHM (NEW and above)
segatools.ini
```ini
[chuniio]
path32=chuniio_tasoller.dll
path64=chuniio_tasoller_x64.dll
```
- For older CHUNITHM (PARADISE LOST and older)
segatools.ini
```ini
[chuniio]
;; For Chunithm or Chunithm NEW (segatools_32.ini)
path=chuniio_tasoller.dll
;; For Chunithm NEW (segatools_64.ini)
; path=chuniio_tasoller_x64.dll
;; Uncomment for Chunithm NEW
; chusan=1
[io3]
test=0x31
service=0x32
coin=0x33
chusan=0
```
## USB Protocol
@ -59,6 +58,6 @@ Custom firmware USB device: 1CCF:2333
```
$ git clone ...
$ git submodule update --init
$ zig build -Drelease-safe=true
$ zig build -Doptimize=ReleaseSafe
$ ls zig-out/lib/chuniio_tasoller.dll
```

View File

@ -1,20 +1,24 @@
const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
const Build = std.build.Builder;
const Step = Build.Step;
pub fn build(b: *std.build.Builder) void {
pub fn build(b: *Build) void {
const mode = b.standardOptimizeOption(.{});
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 lib86 = b.addSharedLibrary(Build.SharedLibraryOptions{
.name = "chuniio_tasoller",
.root_source_file = .{.path = "src/main.zig"},
.target = CrossTarget{ .os_tag = .windows, .cpu_arch = .x86, .abi = .msvc },
.optimize = mode,
});
b.installArtifact(lib86);
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 lib64 = b.addSharedLibrary(Build.SharedLibraryOptions{
.name = "chuniio_tasoller_x64",
.root_source_file = .{.path = "src/main.zig"},
.target = CrossTarget{ .os_tag = .windows, .cpu_arch = .x86_64, .abi = .msvc },
.optimize = mode,
});
b.installArtifact(lib64);
}

View File

@ -1,4 +1,11 @@
[chuniio]
;; For Chunithm
;path=chuniio_tasoller.dll
;; For Chunithm NEW or newer
path32=chuniio_tasoller.dll
path64=chuniio_tasoller_x64.dll
;; Uncomment for Chunithm NEW or newer
chusan=1
[io3]

View File

@ -58,7 +58,7 @@ var input_thread: ?std.Thread = null;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
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: ?[]u8 = null;
var tasoller: ?*anyopaque = null;
@ -67,11 +67,11 @@ pub fn main() !void {
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) {
while (true) {
if (WinUsb_WritePipe(tasoller, 0x03, @as(*u8, @ptrCast(&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) {
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);
@ -81,8 +81,7 @@ pub fn main() !void {
// MAIN DRIVER ======================================================================================================
fn tasoller_init() !void {
if(cfg.?.chusan == 1) {
if (cfg.?.chusan == 1) {
std.log.info("[chuniio] Initializing mode: chusan", .{});
const szName = W("Local\\ChuniioTasoller");
const hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, null, PAGE_READWRITE, 0, 0x24, szName) orelse {
@ -93,8 +92,8 @@ fn tasoller_init() !void {
std.log.err("[chuniio] MapViewOfFile: {any}", .{GetLastError()});
return error.AccessError;
};
usb_in = @ptrCast([*]u8, pBuf)[0..0x24];
if(builtin.cpu.arch == .x86_64) return;
usb_in = @as([*]u8, @ptrCast(pBuf))[0..0x24];
if (builtin.cpu.arch == .x86_64) return;
} else {
std.log.info("[chuniio] Initializing mode: chuni", .{});
usb_in = gpa.allocator().alloc(u8, 0x24) catch |err| {
@ -104,7 +103,7 @@ fn tasoller_init() !void {
}
const hDevInfo = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, null, null, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(hDevInfo == INVALID_HANDLE_VALUE) {
if (hDevInfo == INVALID_HANDLE_VALUE) {
std.log.err("[chuniio] SetupDiGetClassDevs: {any}", .{GetLastError()});
return error.AccessError;
}
@ -114,55 +113,47 @@ fn tasoller_init() !void {
var devIf = std.mem.zeroes(SP_DEVICE_INTERFACE_DATA);
devIf.cbSize = @sizeOf(SP_DEVICE_INTERFACE_DATA);
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 detailBuf align(4) = std.mem.zeroes([512]u8);
var devIfDetail = @ptrCast(*SP_DEVICE_INTERFACE_DETAIL_DATA_A, &detailBuf);
var devIfDetail = @as(*SP_DEVICE_INTERFACE_DETAIL_DATA_A, @ptrCast(&detailBuf));
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) {
std.log.err("[chuniio] SetupDiGetDeviceInterfaceDetailA: {any}", .{GetLastError()});
continue;
}
if(requiredSize >= 263) {
if (requiredSize >= 263) {
std.log.err("[chuniio] SetupDiGetDeviceInterfaceDetailA: Path too long", .{});
continue;
}
const path = detailBuf[@offsetOf(SP_DEVICE_INTERFACE_DETAIL_DATA_A, "DevicePath") .. requiredSize :0];
const path = detailBuf[@offsetOf(SP_DEVICE_INTERFACE_DETAIL_DATA_A, "DevicePath")..requiredSize :0];
// std.log.info("[chuniio] devPath: {s}", .{path});
if(std.mem.indexOf(u8, path, "vid_1ccf") == null) continue;
if(std.mem.indexOf(u8, path, "pid_2333") == null) continue;
if (std.mem.indexOf(u8, path, "vid_1ccf") == null) continue;
if (std.mem.indexOf(u8, path, "pid_2333") == null) continue;
devicePath = path;
break;
}
if(devicePath == null) {
if (devicePath == null) {
std.log.err("[chuniio] Tasoller not found", .{});
return error.AccessError;
}
const hDeviceHandle = CreateFileA(
devicePath,
@intToEnum(FILE_ACCESS_FLAGS, GENERIC_READ | GENERIC_WRITE),
@intToEnum(FILE_SHARE_MODE, @enumToInt(FILE_SHARE_READ) | @enumToInt(FILE_SHARE_WRITE)),
null, // Security Attributes
OPEN_EXISTING,
.FILE_FLAG_OVERLAPPED,
null // Template File
) orelse {
std.log.err("[chuniio] CreateFileA {s}: {any}", .{devicePath, GetLastError()});
return error.AccessError;
};
if(hDeviceHandle == INVALID_HANDLE_VALUE) {
std.log.err("[chuniio] CreateFileA {s}: {any}", .{devicePath, GetLastError()});
const hDeviceHandle = CreateFileA(devicePath, @as(FILE_ACCESS_FLAGS, @enumFromInt(GENERIC_READ | GENERIC_WRITE)), @as(FILE_SHARE_MODE, @enumFromInt(@intFromEnum(FILE_SHARE_READ) | @intFromEnum(FILE_SHARE_WRITE))), null, // Security Attributes
OPEN_EXISTING, .FILE_FLAG_OVERLAPPED, null // Template File
);
if (hDeviceHandle == INVALID_HANDLE_VALUE) {
std.log.err("[chuniio] CreateFileA {any}: {any}\n", .{ devicePath, GetLastError() });
return error.AccessError;
}
if(WinUsb_Initialize(hDeviceHandle, &tasoller) == 0) {
std.log.err("[chuniio] WinUsb_Initialize: {any}", .{GetLastError()});
if (WinUsb_Initialize(hDeviceHandle, &tasoller) == 0) {
std.log.err("[chuniio] WinUsb_Initialize: {any}\n", .{ GetLastError() });
return error.AccessError;
}
// Init magic bytes
std.mem.copy(u8, usb_out[0..3], &[_]u8{0x42, 0x4C, 0x00});
std.mem.copy(u8, usb_out[0..3], &[_]u8{ 0x42, 0x4C, 0x00 });
input_thread = std.Thread.spawn(.{}, input_thread_proc, .{}) catch |err| {
std.log.err("[chuniio] Spawn input thread: {any}", .{err});
@ -173,23 +164,23 @@ 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) {
while (true) {
var len: u32 = 0;
if(WinUsb_ReadPipe(tasoller, 0x84, @ptrCast(*u8, usb_in.?.ptr), @intCast(u32, usb_in.?.len), &len, null) == 0) {
if (WinUsb_ReadPipe(tasoller, 0x84, @as(*u8, @ptrCast(usb_in.?.ptr)), @as(u32, @intCast(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;
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| {
while (slider_active) {
for (usb_in.?[4..], 0..) |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;
pressure[if (i % 2 == 0) 30 - i else 32 - i] = byte;
}
callback.?(&pressure);
std.time.sleep(1_000_000); // 1ms, limit reporting to 1kHz max
@ -199,65 +190,65 @@ fn slider_thread_proc(callback: chuni_io_slider_callback_t) void {
// DLL EXPORTS ======================================================================================================
export fn chuni_io_get_api_version() c_ushort {
return 0x0101;
return 0x0102;
}
pub export fn DllMain(hDllHandle: HANDLE, dwReason: DWORD, lpreserved: LPVOID) BOOL {
_ = hDllHandle;
_ = lpreserved;
if(dwReason != DLL_PROCESS_ATTACH) return win32.zig.TRUE;
if (dwReason != DLL_PROCESS_ATTACH) return win32.zig.TRUE;
const cfg_file = ".\\segatools.ini";
std.log.info("[chuniio] Loading config from {s}", .{cfg_file});
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)),
.test_key = @as(i32, @intCast(GetPrivateProfileIntA("io3", "test", 0x70, cfg_file))),
.serv_key = @as(i32, @intCast(GetPrivateProfileIntA("io3", "service", 0x71, cfg_file))),
.coin_key = @as(i32, @intCast(GetPrivateProfileIntA("io3", "coin", 0x72, cfg_file))),
.chusan = @as(i32, @intCast(GetPrivateProfileIntA("chuniio", "chusan", 1, cfg_file))),
};
return win32.zig.TRUE;
}
export fn chuni_io_jvs_init() HRESULT {
if(cfg.?.chusan == 1 and builtin.cpu.arch != .x86_64) return S_OK;
if (cfg.?.chusan == 1 and builtin.cpu.arch != .x86_64) return S_OK;
tasoller_init() catch return E_FAIL;
return S_OK;
}
export fn chuni_io_jvs_poll(opbtn: ?[*]u8, beams: ?[*]u8) void {
if(cfg.?.chusan == 1 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.?.serv_key) != 0 or (usb_in.?[3] & (1 << 6)) != 0) opbtn.?.* |= (1 << 1);
beams.?.* |= usb_in.?[3] & 0b111111;
if (cfg.?.chusan == 1 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.?[0] |= (1 << 0);
if (GetAsyncKeyState(cfg.?.serv_key) != 0 or (usb_in.?[3] & (1 << 6)) != 0) opbtn.?[0] |= (1 << 1);
beams.?[0] |= 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 == 1 and builtin.cpu.arch != .x86_64) return;
if(total == null) return;
if (cfg.?.chusan == 1 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;
if (coin_depressed and !coin_prev_depressed) coin_conter += 1;
coin_prev_depressed = coin_depressed;
total.?.* = coin_conter;
}
export fn chuni_io_slider_init() HRESULT {
if(cfg.?.chusan == 0) return S_OK;
if (cfg.?.chusan == 0) return S_OK;
tasoller_init() catch return E_FAIL;
return S_OK;
}
export fn chuni_io_slider_start(callback: chuni_io_slider_callback_t) void {
if(cfg.?.chusan == 1 and builtin.cpu.arch != .i386) return;
if(callback == null) return;
if (cfg.?.chusan == 1 and builtin.cpu.arch != .x86) return;
if (callback == null) return;
thread_op.lock();
defer thread_op.unlock();
if(slider_thread == null) {
if (slider_thread == null) {
slider_active = true;
slider_thread = std.Thread.spawn(.{}, slider_thread_proc, .{callback}) catch |err| {
std.log.err("[chuniio] Spawn slider thread: {any}", .{err});
@ -267,11 +258,11 @@ export fn chuni_io_slider_start(callback: chuni_io_slider_callback_t) void {
}
export fn chuni_io_slider_stop() void {
if(cfg.?.chusan == 1 and builtin.cpu.arch != .i386) return;
if (cfg.?.chusan == 1 and builtin.cpu.arch != .x86) return;
thread_op.lock();
defer thread_op.unlock();
if(slider_thread != null) {
if (slider_thread != null) {
slider_active = false;
slider_thread.?.join();
slider_thread = null;
@ -279,52 +270,72 @@ export fn chuni_io_slider_stop() void {
}
export fn chuni_io_slider_set_leds(rgb: ?[*]u8) void {
if(cfg.?.chusan == 1 and builtin.cpu.arch != .i386) return;
if(rgb == null) return;
if (cfg.?.chusan == 1 and builtin.cpu.arch != .x86) return;
if (rgb == null) return;
var n: u32 = 0;
const out = usb_out[3..96];
while(n < 31) : (n += 1) {
out[n*3+0] = rgb.?[n*3+2];
out[n*3+1] = rgb.?[n*3+1];
out[n*3+2] = rgb.?[n*3+0];
while (n < 31) : (n += 1) {
out[n * 3 + 0] = rgb.?[n * 3 + 2];
out[n * 3 + 1] = rgb.?[n * 3 + 1];
out[n * 3 + 2] = rgb.?[n * 3 + 0];
}
usb_out_op.lock();
defer usb_out_op.unlock();
if(WinUsb_WritePipe(tasoller, 0x03, @ptrCast(*u8, &usb_out), usb_out.len, &n, null) == 0) {
if (WinUsb_WritePipe(tasoller, 0x03, @as(*u8, @ptrCast(&usb_out)), usb_out.len, &n, null) == 0) {
std.log.warn("[chuniio] WinUsb_WritePipe: {any}", .{GetLastError()});
}
}
export fn chuni_io_led_init() c_int {
return 0;
export fn chuni_io_led_init() HRESULT {
return S_OK;
}
export fn chuni_io_led_set_colors(board: u8, rgb: ?[*]u8) void {
if(cfg.?.chusan == 1 and builtin.cpu.arch != .i386) return;
if(rgb == null) return;
if (cfg.?.chusan == 1 and builtin.cpu.arch != .x86) return;
if (rgb == null) return;
var n: u32 = 0;
if(board == 0) {
if (board == 0) {
const out = usb_out[96..168];
while(n < 24) : (n += 1) {
out[n*3+1] = rgb.?[0x96];
out[n*3+0] = rgb.?[0x97];
out[n*3+2] = rgb.?[0x98];
}
} else if(board == 1) {
const out = usb_out[168..240];
while(n < 24) : (n += 1) {
out[n*3+1] = rgb.?[0xb4];
out[n*3+0] = rgb.?[0xb5];
out[n*3+2] = rgb.?[0xb6];
const led_limit: u32 = 3;
const i_limit: u32 = 8;
var led: u32 = 0;
while (led < led_limit) {
var i: u32 = 0;
while (i < i_limit) {
n = (8 * led) + i;
out[n * 3 + 1] = rgb.?[0x96 + (led * 3)];
out[n * 3 + 0] = rgb.?[0x97 + (led * 3)];
out[n * 3 + 2] = rgb.?[0x98 + (led * 3)];
i += 1;
}
led += 1;
}
} else if (board == 1) {
const out = usb_out[168..240];
const led_limit: u32 = 3;
const i_limit: u32 = 8;
var led: u32 = 0;
while (led < led_limit) {
var i: u32 = 0;
while (i < i_limit) {
n = (8 * led) + i;
out[n * 3 + 1] = rgb.?[0xb4 + (led * 3)];
out[n * 3 + 0] = rgb.?[0xb5 + (led * 3)];
out[n * 3 + 2] = rgb.?[0xb6 + (led * 3)];
i += 1;
}
led += 1;
}
}
usb_out_op.lock();
defer usb_out_op.unlock();
if(WinUsb_WritePipe(tasoller, 0x03, @ptrCast(*u8, &usb_out), usb_out.len, &n, null) == 0) {
if (WinUsb_WritePipe(tasoller, 0x03, @as(*u8, @ptrCast(&usb_out)), usb_out.len, &n, null) == 0) {
std.log.warn("[chuniio] WinUsb_WritePipe: {any}", .{GetLastError()});
}
}

@ -1 +1 @@
Subproject commit a74c9dae6a1ccd361eb9a1d146a09c08d22f02b0
Subproject commit 6777f1db221d0cb50322842f558f03e3c3a4099f