Support chusan
This commit is contained in:
parent
93c464518e
commit
21932f9bde
23
README.md
23
README.md
@ -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
|
||||||
|
22
build.zig
22
build.zig
@ -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 exe = b.addExecutable("tasoller_test", "src/main.zig");
|
const lib64 = b.addSharedLibrary("chuniio_tasoller_x64", "src/main.zig", .unversioned);
|
||||||
lib.setBuildMode(b.standardReleaseOptions());
|
lib64.setBuildMode(b.standardReleaseOptions());
|
||||||
exe.setTarget(target);
|
lib64.setTarget(CrossTarget{ .os_tag = .windows, .cpu_arch = .x86_64, .abi = .msvc });
|
||||||
exe.install();
|
lib64.install();
|
||||||
|
|
||||||
|
// const exe = b.addExecutable("tasoller_test", "src/main.zig");
|
||||||
|
// lib.setBuildMode(b.standardReleaseOptions());
|
||||||
|
// exe.setTarget(target);
|
||||||
|
// exe.install();
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
[chuniio]
|
||||||
|
chusan=1
|
||||||
|
|
||||||
[io3]
|
[io3]
|
||||||
test=0x31
|
test=0x31
|
||||||
service=0x32
|
service=0x32
|
||||||
|
157
src/main.zig
157
src/main.zig
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user