Bemanitools v5.26 release

This commit is contained in:
icex2
2019-09-27 22:36:50 +02:00
commit cbd7720349
718 changed files with 62731 additions and 0 deletions

263
src/imports/SMX.h Normal file
View File

@@ -0,0 +1,263 @@
#ifndef SMX_H
#define SMX_H
#ifndef __cplusplus
#include <stdbool.h>
#endif
#include <stdint.h>
#ifdef SMX_EXPORTS
#define SMX_API __declspec(dllexport)
#else
#define SMX_API __declspec(dllimport)
#endif
#ifdef __cplusplus
#define SMX_EXTERN_C extern "C"
#else
#define SMX_EXTERN_C
#endif
struct SMXInfo;
struct SMXConfig;
enum SensorTestMode;
enum SMXUpdateCallbackReason;
struct SMXSensorTestModeData;
// All functions are nonblocking. Getters will return the most recent state. Setters will
// return immediately and do their work in the background. No functions return errors, and
// setting data on a pad which isn't connected will have no effect.
// Initialize, and start searching for devices.
//
// UpdateCallback will be called when something happens: connection or disconnection, inputs
// changed, configuration updated, test data updated, etc. It doesn't specify what's changed,
// and the user should check all state that it's interested in.
//
// This is called asynchronously from a helper thread, so the receiver must be thread-safe.
typedef void SMXUpdateCallback(int pad, enum SMXUpdateCallbackReason reason, void *pUser);
SMX_EXTERN_C SMX_API void SMX_Start(SMXUpdateCallback UpdateCallback, void *pUser);
// Shut down and disconnect from all devices. This will wait for any user callbacks to complete,
// and no user callbacks will be called after this returns. This must not be called from within
// the update callback.
SMX_EXTERN_C SMX_API void SMX_Stop();
// Set a function to receive diagnostic logs. By default, logs are written to stdout.
// This can be called before SMX_Start, so it affects any logs sent during initialization.
typedef void SMXLogCallback(const char *log);
SMX_EXTERN_C SMX_API void SMX_SetLogCallback(SMXLogCallback callback);
// Get info about a pad. Use this to detect which pads are currently connected.
SMX_EXTERN_C SMX_API void SMX_GetInfo(int pad, struct SMXInfo *info);
// Get a mask of the currently pressed panels.
SMX_EXTERN_C SMX_API uint16_t SMX_GetInputState(int pad);
// Update the lights. Both pads are always updated together. lightsData is a list of 8-bit RGB
// colors, one for each LED. Each panel has lights in the following order:
//
// 0123
// 4567
// 89AB
// CDEF
//
// Panels are in the following order:
//
// 012 9AB
// 345 CDE
// 678 F01
//
// With 18 panels, 16 LEDs per panel and 3 bytes per LED, each light update has 864 bytes of data.
//
// Lights will update at up to 30 FPS. If lights data is sent more quickly, a best effort will be
// made to send the most recent lights data available, but the panels won't update more quickly.
//
// The panels will return to automatic lighting if no lights are received for a while, so applications
// controlling lights should send light updates continually, even if the lights aren't changing.
SMX_EXTERN_C SMX_API void SMX_SetLights(const char lightsData[864]);
// By default, the panels light automatically when stepped on. If a lights command is sent by
// the application, this stops happening to allow the application to fully control lighting.
// If no lights update is received for a few seconds, automatic lighting is reenabled by the
// panels.
//
// SMX_ReenableAutoLights can be called to immediately reenable auto-lighting, without waiting
// for the timeout period to elapse. Games don't need to call this, since the panels will return
// to auto-lighting mode automatically after a brief period of no updates.
SMX_EXTERN_C SMX_API void SMX_ReenableAutoLights();
// Get the current controller's configuration.
//
// Return true if a configuration is available. If false is returned, no panel is connected
// and no data will be set.
SMX_EXTERN_C SMX_API bool SMX_GetConfig(int pad, struct SMXConfig *config);
// Update the current controller's configuration. This doesn't block, and the new configuration will
// be sent in the background. SMX_GetConfig will return the new configuration as soon as this call
// returns, without waiting for it to actually be sent to the controller.
SMX_EXTERN_C SMX_API void SMX_SetConfig(int pad, const struct SMXConfig *config);
// Reset a pad to its original configuration.
SMX_EXTERN_C SMX_API void SMX_FactoryReset(int pad);
// Request an immediate panel recalibration. This is normally not necessary, but can be helpful
// for diagnostics.
SMX_EXTERN_C SMX_API void SMX_ForceRecalibration(int pad);
// Set a panel test mode and request test data. This is used by the configuration tool.
SMX_EXTERN_C SMX_API void SMX_SetTestMode(int pad, enum SensorTestMode mode);
SMX_EXTERN_C SMX_API bool SMX_GetTestData(int pad, struct SMXSensorTestModeData *data);
// Return the build version of the DLL, which is based on the git tag at build time. This
// is only intended for diagnostic logging, and it's also the version we show in SMXConfig.
SMX_EXTERN_C SMX_API const char *SMX_Version();
// General info about a connected controller. This can be retrieved with SMX_GetInfo.
struct SMXInfo
{
// True if we're fully connected to this controller. If this is false, the other
// fields won't be set.
bool m_bConnected;
// This device's serial number. This can be used to distinguish devices from each
// other if more than one is connected. This is a null-terminated string instead
// of a C++ string for C# marshalling.
char m_Serial[33];
// This device's firmware version.
uint16_t m_iFirmwareVersion;
};
enum SMXUpdateCallbackReason {
// This is called when a generic state change happens: connection or disconnection, inputs changed,
// test data updated, etc. It doesn't specify what's changed. We simply check the whole state.
SMXUpdateCallback_Updated,
// This is called when SMX_FactoryReset completes, indicating that SMX_GetConfig will now return
// the reset configuration.
SMXUpdateCallback_FactoryResetCommandComplete
};
// The configuration for a connected controller. This can be retrieved with SMX_GetConfig
// and modified with SMX_SetConfig.
//
// The order and packing of this struct corresponds to the configuration packet sent to
// the master controller, so it must not be changed.
struct SMXConfig
{
#if 0
// These fields are unused and must be left at their existing values.
uint8_t unused1 = 0xFF, unused2 = 0xFF;
uint8_t unused3 = 0xFF, unused4 = 0xFF;
uint8_t unused5 = 0xFF, unused6 = 0xFF;
// Panel thresholds are labelled by their numpad position, eg. Panel8 is up.
// If m_iFirmwareVersion is 1, Panel7 corresponds to all of up, down, left and
// right, and Panel2 corresponds to UpLeft, UpRight, DownLeft and DownRight. For
// later firmware versions, each panel is configured independently.
//
// Setting a value to 0xFF disables that threshold.
uint16_t masterDebounceMilliseconds = 0;
uint8_t panelThreshold7Low = 0xFF, panelThreshold7High = 0xFF; // was "cardinal"
uint8_t panelThreshold4Low = 0xFF, panelThreshold4High = 0xFF; // was "center"
uint8_t panelThreshold2Low = 0xFF, panelThreshold2High = 0xFF; // was "corner"
// These are internal tunables and should be left unchanged.
uint16_t panelDebounceMicroseconds = 4000;
uint16_t autoCalibrationPeriodMilliseconds = 1000;
uint8_t autoCalibrationMaxDeviation = 100;
uint8_t badSensorMinimumDelaySeconds = 15;
uint16_t autoCalibrationAveragesPerUpdate = 60;
uint8_t unused7 = 0xFF, unused8 = 0xFF;
uint8_t panelThreshold1Low = 0xFF, panelThreshold1High = 0xFF; // was "up"
// Which sensors on each panel to enable. This can be used to disable sensors that
// we know aren't populated. This is packed, with four sensors on two pads per byte:
// enabledSensors[0] & 1 is the first sensor on the first pad, and so on.
uint8_t enabledSensors[5];
// How long the master controller will wait for a lights command before assuming the
// game has gone away and resume auto-lights. This is in 128ms units.
uint8_t autoLightsTimeout = 1000/128; // 1 second
// The color to use for each panel when auto-lighting in master mode. This doesn't
// apply when the pads are in autonomous lighting mode (no master), since they don't
// store any configuration by themselves. These colors should be scaled to the 0-170
// range.
uint8_t stepColor[3*9];
// The rotation of the panel, where 0 is the standard rotation, 1 means the panel is
// rotated right 90 degrees, 2 is rotated 180 degrees, and 3 is rotated 270 degrees.
// This value is unused.
uint8_t panelRotation;
// This is an internal tunable that should be left unchanged.
uint16_t autoCalibrationSamplesPerAverage = 500;
// The firmware version of the master controller. Where supported (version 2 and up), this
// will always read back the firmware version. This will default to 0xFF on version 1, and
// we'll always write 0xFF here so it doesn't change on that firmware version.
//
// We don't need this since we can read the "I" command which also reports the version, but
// this allows panels to also know the master version.
uint8_t masterVersion = 0xFF;
// The version of this config packet. This can be used by the firmware to know which values
// have been filled in. Any values not filled in will always be 0xFF, which can be tested
// for, but that doesn't work for values where 0xFF is a valid value. This value is unrelated
// to the firmware version, and just indicates which fields in this packet have been set.
// Note that we don't need to increase this any time we add a field, only when it's important
// that we be able to tell if a field is set or not.
//
// Versions:
// - 0xFF: This is a config packet from before configVersion was added.
// - 0x00: configVersion added
// - 0x02: panelThreshold0Low through panelThreshold8High added
uint8_t configVersion = 0x02;
// The remaining thresholds (configVersion >= 2).
uint8_t unused9[10];
uint8_t panelThreshold0Low, panelThreshold0High;
uint8_t panelThreshold3Low, panelThreshold3High;
uint8_t panelThreshold5Low, panelThreshold5High;
uint8_t panelThreshold6Low, panelThreshold6High;
uint8_t panelThreshold8Low, panelThreshold8High;
#endif
};
//static_assert(sizeof(SMXConfig) == 84, "Expected 84 bytes");
// The values (except for Off) correspond with the protocol and must not be changed.
enum SensorTestMode {
SensorTestMode_Off = 0,
// Return the raw, uncalibrated value of each sensor.
SensorTestMode_UncalibratedValues = '0',
// Return the calibrated value of each sensor.
SensorTestMode_CalibratedValues = '1',
// Return the sensor noise value.
SensorTestMode_Noise = '2',
// Return the sensor tare value.
SensorTestMode_Tare = '3',
};
// Data for the current SensorTestMode. The interpretation of sensorLevel depends on the mode.
struct SMXSensorTestModeData
{
// If false, sensorLevel[n][*] is zero because we didn't receive a response from that panel.
bool bHaveDataFromPanel[9];
int16_t sensorLevel[9][4];
bool bBadSensorInput[9][4];
// The DIP switch settings on each panel. This is used for diagnostics
// displays.
int iDIPSwitchPerPanel[9];
};
#endif

7
src/imports/avs-ea3.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef IMPORTS_AVS_EA3_H
#define IMPORTS_AVS_EA3_H
void ea3_boot(struct property_node *conf);
void ea3_shutdown(void);
#endif

202
src/imports/avs.h Normal file
View File

@@ -0,0 +1,202 @@
#ifndef IMPORTS_AVS_H
#define IMPORTS_AVS_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
enum property_create_flag {
PROPERTY_FLAG_READ = 0x1,
PROPERTY_FLAG_WRITE = 0x2,
PROPERTY_FLAG_CREATE = 0x4,
PROPERTY_FLAG_BINARY = 0x8,
PROPERTY_FLAG_APPEND = 0x10,
};
enum property_node_traversal {
TRAVERSE_PARENT = 0,
TRAVERSE_FIRST_CHILD = 1,
TRAVERSE_FIRST_ATTR = 2,
TRAVERSE_FIRST_SIBLING = 3,
TRAVERSE_NEXT_SIBLING = 4,
TRAVERSE_PREVIOUS_SIBLING = 5,
TRAVERSE_LAST_SIBLING = 6,
TRAVERSE_NEXT_SEARCH_RESULT = 7,
TRAVERSE_PREV_SEARCH_RESULT = 8,
};
enum property_type {
PROPERTY_TYPE_VOID = 1,
PROPERTY_TYPE_S8 = 2,
PROPERTY_TYPE_U8 = 3,
PROPERTY_TYPE_S16 = 4,
PROPERTY_TYPE_U16 = 5,
PROPERTY_TYPE_S32 = 6,
PROPERTY_TYPE_U32 = 7,
PROPERTY_TYPE_S64 = 8,
PROPERTY_TYPE_U64 = 9,
PROPERTY_TYPE_BIN = 10,
PROPERTY_TYPE_STR = 11
};
struct property;
struct property_node;
struct avs_net_interface {
uint8_t mac_addr[6];
uint8_t unknown[30];
};
enum psmap_type {
PSMAP_TYPE_S8 = 2,
PSMAP_TYPE_U8 = 3,
PSMAP_TYPE_S16 = 4,
PSMAP_TYPE_U16 = 5,
PSMAP_TYPE_S32 = 6,
PSMAP_TYPE_U32 = 7,
PSMAP_TYPE_S64 = 8,
PSMAP_TYPE_U64 = 9,
PSMAP_TYPE_STR = 10,
/* Used on avs 803 instead of value 10 */
PSMAP_TYPE_STR_LEGACY = 11,
PSMAP_TYPE_ATTR = 45,
PSMAP_TYPE_BOOL = 50,
};
#define PSMAP_FLAG_HAVE_DEFAULT 0x01
struct property_psmap {
uint8_t type;
uint8_t flags; /* A guess. Might just be a bool. */
uint16_t offset;
uint32_t size;
const char *path;
intptr_t xdefault;
};
#define PSMAP_BEGIN(name) \
struct property_psmap name[] = {
#define PSMAP_REQUIRED(type, xstruct, field, path) \
{ \
type, \
0, \
offsetof(xstruct, field), \
sizeof( ((xstruct *) 0)->field ), \
path, \
0, \
}, \
#define PSMAP_OPTIONAL(type, xstruct, field, path, xdefault) \
{ \
type, \
PSMAP_FLAG_HAVE_DEFAULT, \
offsetof(xstruct, field), \
sizeof( ((xstruct *) 0)->field ), \
path, \
(intptr_t) xdefault, \
}, \
#define PSMAP_END \
{ 0xFF, 0, 0, 0, NULL, 0 } \
};
#if AVS_VERSION >= 1500
# define AVS_LOG_WRITER(name, chars, nchars, ctx) \
void name(const char * chars , uint32_t nchars , void * ctx )
typedef void (*avs_log_writer_t)(const char *chars, uint32_t nchars,
void *ctx);
#else
# define AVS_LOG_WRITER(name, chars, nchars, ctx) \
void name(void * ctx , const char * chars , uint32_t nchars )
typedef void (*avs_log_writer_t)(void *ctx, const char *chars,
uint32_t nchars);
#endif
typedef int (*avs_reader_t)(uint32_t context, void *bytes, size_t nbytes);
#if AVS_VERSION >= 1600
/* "avs" and "std" heaps have been unified */
typedef void (*avs_boot_t)(
struct property_node *config, void *com_heap, size_t sz_com_heap,
void *reserved, avs_log_writer_t log_writer, void *log_context);
void avs_boot(
struct property_node *config, void *com_heap, size_t sz_com_heap,
void *reserved, avs_log_writer_t log_writer, void *log_context);
#else
typedef void (*avs_boot_t)(
struct property_node *config, void *std_heap, size_t sz_std_heap,
void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer,
void* log_context);
void avs_boot(
struct property_node *config, void *std_heap, size_t sz_std_heap,
void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer,
void *log_context);
#endif
void avs_shutdown(void);
void log_body_fatal(const char *module, const char *fmt, ...);
void log_body_info(const char *module, const char *fmt, ...);
void log_body_misc(const char *module, const char *fmt, ...);
void log_body_warning(const char *module, const char *fmt, ...);
void log_boot(avs_log_writer_t log_writer, void *log_context);
void log_change_level(int level);
int avs_net_ctrl(int ioctl, void *bytes, uint32_t nbytes);
int avs_thread_create(int (*proc)(void *), void *ctx, uint32_t sz_stack,
unsigned int priority);
void avs_thread_destroy(int thread_id);
void avs_thread_exit(int result);
void avs_thread_join(int thread_id, int *result);
uint32_t property_read_query_memsize(
avs_reader_t reader, uint32_t context, int unk0, int unk1);
struct property *property_create(
int flags, void *buffer, uint32_t buffer_size);
struct property_node *property_search(
struct property *prop, struct property_node *root, const char *path);
int property_insert_read(
struct property *prop, struct property_node *root, avs_reader_t reader,
uint32_t context);
int property_mem_write(struct property *prop, void *bytes, int nbytes);
void *property_desc_to_buffer(struct property *prop);
void property_file_write(struct property *prop, const char *path);
int property_set_flag(struct property *prop, int flags, int mask);
void property_destroy(struct property *prop);
int property_psmap_import(struct property *prop, struct property_node *root,
void *dest, const struct property_psmap *psmap);
int property_psmap_export(struct property *prop, struct property_node *root,
const void *src, const struct property_psmap *psmap);
struct property_node *property_node_clone(
struct property *new_parent, int unk0,
struct property_node *src, bool deep);
struct property_node *property_node_create(
struct property *prop, struct property_node *parent, int type,
const char *key, ...);
void property_node_name(
struct property_node *node, char *chars, int nchars);
const char *property_node_refdata(struct property_node *node);
int property_node_refer(struct property *prop,
struct property_node *node, const char *name,
enum property_type type, void *bytes, uint32_t nbytes);
void property_node_remove(struct property_node *node);
enum property_type property_node_type(
struct property_node *node);
struct property_node *property_node_traversal(
struct property_node *node, enum property_node_traversal direction);
void property_node_datasize(struct property_node* node);
bool std_getenv(const char *key, char *val, uint32_t nbytes);
void std_setenv(const char *key, const char *val);
void *avs_fs_mount (char* mountpoint, char* fsroot, void* fstype, int flags);
#endif

11
src/imports/eapki.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef IMPORTS_EAPKI_H
#define IMPORTS_EAPKI_H
#include <stdbool.h>
#include "imports/avs.h"
typedef bool (*dll_entry_init_t)(char *, struct property_node *);
typedef bool (*dll_entry_main_t)(void);
#endif

View File

@@ -0,0 +1,5 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot
ea3_shutdown

View File

@@ -0,0 +1,36 @@
LIBRARY libavs-win32
EXPORTS
avs_boot
avs_net_ctrl
avs_shutdown
avs_thread_create
avs_thread_destroy
avs_thread_exit
avs_thread_join
log_body_fatal
log_body_info
log_body_misc
log_body_warning
log_boot
log_change_level
property_create
property_desc_to_buffer
property_destroy
property_file_write
property_insert_read
property_mem_write
property_read_query_memsize
property_search
property_set_flag
property_node_clone
property_node_create
property_node_name
property_node_refer
property_node_remove
property_node_type
property_node_traversal
property_node_refdata
std_getenv
std_setenv

View File

@@ -0,0 +1,5 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @47 NONAME
ea3_shutdown @130 NONAME

View File

@@ -0,0 +1,28 @@
LIBRARY libavs-win32
EXPORTS
avs_boot @22 NONAME
avs_net_ctrl @107 NONAME
avs_shutdown @140 NONAME
avs_thread_create @156 NONAME
avs_thread_destroy @158 NONAME
avs_thread_exit @159 NONAME
avs_thread_join @161 NONAME
log_assert_body @196 NONAME
log_body_misc @199 NONAME
log_body_info @198 NONAME
log_body_warning @200 NONAME
log_body_fatal @197 NONAME
property_create @245 NONAME
property_desc_to_buffer @246 NONAME
property_destroy @247 NONAME
property_insert_read @255 NONAME
property_node_create @266 NONAME
property_node_refer @278 NONAME
property_node_remove @279 NONAME
property_psmap_import @288 NONAME
property_psmap_export @287 NONAME
property_read_query_memsize @291 NONAME
property_search @294 NONAME
std_getenv @308 NONAME
std_setenv @322 NONAME

View File

@@ -0,0 +1,5 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @94 NONAME
ea3_shutdown @97 NONAME

View File

@@ -0,0 +1,27 @@
LIBRARY libavs-win32
EXPORTS
avs_boot @237 NONAME
avs_net_ctrl @15 NONAME
avs_shutdown @333 NONAME
avs_thread_create @183 NONAME
avs_thread_destroy @76 NONAME
avs_thread_exit @147 NONAME
avs_thread_join @92 NONAME
log_body_misc @44 NONAME
log_body_info @339 NONAME
log_body_warning @219 NONAME
log_body_fatal @128 NONAME
property_create @256 NONAME
property_desc_to_buffer @201 NONAME
property_destroy @264 NONAME
property_insert_read @23 NONAME
property_node_create @316 NONAME
property_node_refer @268 NONAME
property_node_remove @129 NONAME
property_psmap_import @102 NONAME
property_psmap_export @110 NONAME
property_read_query_memsize @100 NONAME
property_search @244 NONAME
std_getenv @226 NONAME
std_setenv @114 NONAME

View File

@@ -0,0 +1,5 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @8 NONAME
ea3_shutdown @9 NONAME

View File

@@ -0,0 +1,24 @@
LIBRARY libavs-win32
EXPORTS
avs_boot @298 NONAME
avs_net_ctrl @100 NONAME
avs_shutdown @299 NONAME
avs_thread_create @6 NONAME
avs_thread_destroy @8 NONAME
avs_thread_join @13 NONAME
log_body_info @363 NONAME
log_body_misc @364 NONAME
log_body_warning @362 NONAME
log_body_fatal @365 NONAME
property_create @129 NONAME
property_desc_to_buffer @131 NONAME
property_destroy @130 NONAME
property_insert_read @133 NONAME
property_node_remove @148 NONAME
property_psmap_import @163 NONAME
property_psmap_export @164 NONAME
property_read_query_memsize @161 NONAME
property_search @146 NONAME
std_getenv @208 NONAME
std_setenv @209 NONAME

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @8 NONAME
ea3_get_boot_status @11 NONAME
ea3_get_pp_status @10 NONAME
ea3_shutdown @9 NONAME

View File

@@ -0,0 +1,32 @@
LIBRARY libavs-win32
EXPORTS
avs_boot @285 NONAME
avs_fs_close @65 NONAME
avs_fs_lseek @59 NONAME
avs_fs_lseek64 @60 NONAME
avs_fs_open @58 NONAME
avs_fs_read @61 NONAME
avs_net_ctrl @98 NONAME
avs_shutdown @286 NONAME
avs_thread_create @6 NONAME
avs_thread_destroy @8 NONAME
avs_thread_exit @12 NONAME
avs_thread_join @13 NONAME
log_body_fatal @355 NONAME
log_body_info @357 NONAME
log_body_misc @358 NONAME
log_body_warning @356 NONAME
property_create @127 NONAME
property_desc_to_buffer @129 NONAME
property_destroy @128 NONAME
property_insert_read @131 NONAME
property_node_create @145 NONAME
property_node_refer @158 NONAME
property_node_remove @146 NONAME
property_psmap_export @162 NONAME
property_psmap_import @161 NONAME
property_read_query_memsize @159 NONAME
property_search @144 NONAME
std_getenv @207 NONAME
std_setenv @208 NONAME

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @7 NONAME
ea3_shutdown @8 NONAME
ea3_get_pp_status @9 NONAME
ea3_get_boot_status @10 NONAME

View File

@@ -0,0 +1,28 @@
LIBRARY libavs-win32
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_net_ctrl @98 NONAME
property_create @124 NONAME
property_destroy @125 NONAME
property_desc_to_buffer @126 NONAME
property_insert_read @128 NONAME
property_search @141 NONAME
property_node_create @142 NONAME
property_node_remove @143 NONAME
property_node_refer @155 NONAME
property_read_query_memsize @156 NONAME
property_psmap_export @159 NONAME
property_psmap_import @158 NONAME
std_getenv @204 NONAME
std_setenv @205 NONAME
avs_boot @283 NONAME
avs_shutdown @284 NONAME
log_body_fatal @361 NONAME
log_body_warning @362 NONAME
log_body_info @363 NONAME
log_body_misc @364 NONAME

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot @8 NONAME
ea3_shutdown @9 NONAME
ea3_get_pp_status @10 NONAME
ea3_get_boot_status @11 NONAME

View File

@@ -0,0 +1,27 @@
LIBRARY libavs-win32
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_net_ctrl @119 NONAME
property_create @145 NONAME
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_search @162 NONAME
property_node_create @163 NONAME
property_node_remove @164 NONAME
property_node_refer @176 NONAME
property_read_query_memsize @177 NONAME
property_psmap_import @179 NONAME
property_psmap_export @180 NONAME
std_getenv @212 NONAME
std_setenv @213 NONAME
avs_boot @298 NONAME
avs_shutdown @299 NONAME
log_body_fatal @379 NONAME
log_body_warning @380 NONAME
log_body_info @381 NONAME
log_body_misc @382 NONAME

View File

@@ -0,0 +1,5 @@
LIBRARY avs2-ea3
EXPORTS
ea3_boot @37 NONAME
ea3_shutdown @38 NONAME

View File

@@ -0,0 +1,28 @@
LIBRARY avs2-core
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_fs_mount @76 NONAME
avs_net_ctrl @119 NONAME
property_create @145 NONAME
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_search @162 NONAME
property_node_create @163 NONAME
property_node_remove @164 NONAME
property_node_refer @176 NONAME
property_read_query_memsize @177 NONAME
property_psmap_import @179 NONAME
property_psmap_export @180 NONAME
std_getenv @212 NONAME
std_setenv @213 NONAME
avs_boot @298 NONAME
avs_shutdown @299 NONAME
log_body_fatal @379 NONAME
log_body_warning @380 NONAME
log_body_info @381 NONAME
log_body_misc @382 NONAME

View File

@@ -0,0 +1,5 @@
LIBRARY libavs-win32-ea3
EXPORTS
ea3_boot
ea3_shutdown

View File

@@ -0,0 +1,37 @@
LIBRARY libavs-win32
EXPORTS
avs_boot
avs_net_ctrl
avs_shutdown
avs_thread_create
avs_thread_destroy
avs_thread_exit
avs_thread_join
log_body_fatal
log_body_info
log_body_misc
log_body_warning
log_boot
log_change_level
property_create
property_desc_to_buffer
property_destroy
property_file_write
property_insert_read
property_mem_write
property_read_query_memsize
property_search
property_set_flag
property_node_clone
property_node_create
property_node_datasize
property_node_name
property_node_refer
property_node_remove
property_node_type
property_node_traversal
property_node_refdata
std_getenv
std_setenv

View File

@@ -0,0 +1,17 @@
LIBRARY SMX
EXPORTS
SMX_Start
SMX_Stop
SMX_SetLogCallback
SMX_GetInfo
SMX_GetInputState
SMX_SetLights
SMX_ReenableAutoLights
SMX_GetConfig
SMX_SetConfig
SMX_FactoryReset
SMX_ForceRecalibration
SMX_SetTestMode
SMX_GetTestData
SMX_Version

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win64-ea3
EXPORTS
ea3_boot @8 NONAME
ea3_get_boot_status @11 NONAME
ea3_get_pp_status @10 NONAME
ea3_shutdown @9 NONAME

View File

@@ -0,0 +1,32 @@
LIBRARY libavs-win64
EXPORTS
avs_boot @285 NONAME
avs_fs_close @65 NONAME
avs_fs_lseek @59 NONAME
avs_fs_lseek64 @60 NONAME
avs_fs_open @58 NONAME
avs_fs_read @61 NONAME
avs_net_ctrl @98 NONAME
avs_shutdown @286 NONAME
avs_thread_create @6 NONAME
avs_thread_destroy @8 NONAME
avs_thread_exit @12 NONAME
avs_thread_join @13 NONAME
log_body_fatal @355 NONAME
log_body_info @357 NONAME
log_body_misc @358 NONAME
log_body_warning @356 NONAME
property_create @127 NONAME
property_desc_to_buffer @129 NONAME
property_destroy @128 NONAME
property_insert_read @131 NONAME
property_node_create @145 NONAME
property_node_refer @158 NONAME
property_node_remove @146 NONAME
property_psmap_export @162 NONAME
property_psmap_import @161 NONAME
property_read_query_memsize @159 NONAME
property_search @144 NONAME
std_getenv @207 NONAME
std_setenv @208 NONAME

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win64-ea3
EXPORTS
ea3_boot @7 NONAME
ea3_shutdown @8 NONAME
ea3_get_pp_status @9 NONAME
ea3_get_boot_status @10 NONAME

View File

@@ -0,0 +1,28 @@
LIBRARY libavs-win64
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_net_ctrl @98 NONAME
property_create @124 NONAME
property_destroy @125 NONAME
property_desc_to_buffer @126 NONAME
property_insert_read @128 NONAME
property_search @141 NONAME
property_node_create @142 NONAME
property_node_remove @143 NONAME
property_node_refer @155 NONAME
property_read_query_memsize @156 NONAME
property_psmap_export @159 NONAME
property_psmap_import @158 NONAME
std_getenv @204 NONAME
std_setenv @205 NONAME
avs_boot @283 NONAME
avs_shutdown @284 NONAME
log_body_fatal @361 NONAME
log_body_warning @362 NONAME
log_body_info @363 NONAME
log_body_misc @364 NONAME

View File

@@ -0,0 +1,7 @@
LIBRARY libavs-win64-ea3
EXPORTS
ea3_boot @8 NONAME
ea3_shutdown @9 NONAME
ea3_get_pp_status @10 NONAME
ea3_get_boot_status @11 NONAME

View File

@@ -0,0 +1,27 @@
LIBRARY libavs-win64
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_net_ctrl @119 NONAME
property_create @145 NONAME
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_search @162 NONAME
property_node_create @163 NONAME
property_node_remove @164 NONAME
property_node_refer @176 NONAME
property_read_query_memsize @177 NONAME
property_psmap_import @179 NONAME
property_psmap_export @180 NONAME
std_getenv @212 NONAME
std_setenv @213 NONAME
avs_boot @298 NONAME
avs_shutdown @299 NONAME
log_body_fatal @379 NONAME
log_body_warning @380 NONAME
log_body_info @381 NONAME
log_body_misc @382 NONAME

View File

@@ -0,0 +1,5 @@
LIBRARY avs2-ea3
EXPORTS
ea3_boot @37 NONAME
ea3_shutdown @38 NONAME

View File

@@ -0,0 +1,28 @@
LIBRARY avs2-core
EXPORTS
avs_thread_create @5 NONAME
avs_thread_destroy @7 NONAME
avs_thread_exit @11 NONAME
avs_thread_join @12 NONAME
avs_fs_mount @76 NONAME
avs_net_ctrl @119 NONAME
property_create @145 NONAME
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_search @162 NONAME
property_node_create @163 NONAME
property_node_remove @164 NONAME
property_node_refer @176 NONAME
property_read_query_memsize @177 NONAME
property_psmap_import @179 NONAME
property_psmap_export @180 NONAME
std_getenv @212 NONAME
std_setenv @213 NONAME
avs_boot @298 NONAME
avs_shutdown @299 NONAME
log_body_fatal @379 NONAME
log_body_warning @380 NONAME
log_body_info @381 NONAME
log_body_misc @382 NONAME

86
src/main/acio/acio.h Normal file
View File

@@ -0,0 +1,86 @@
#ifndef AC_IO_AC_IO_H
#define AC_IO_AC_IO_H
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "acio/icca.h"
#include "acio/kfca.h"
#define AC_IO_SOF 0xAA
#define AC_IO_ESCAPE 0xFF
#define AC_IO_RESPONSE_FLAG 0x80
#define AC_IO_BROADCAST 0x70
#define ac_io_u16(x) _byteswap_ushort(x)
#define ac_io_u32(x) _byteswap_ulong(x)
enum ac_io_cmd {
AC_IO_CMD_ASSIGN_ADDRS = 0x0001,
AC_IO_CMD_GET_VERSION = 0x0002,
AC_IO_CMD_START_UP = 0x0003,
AC_IO_CMD_KEEPALIVE = 0x0080,
/* Yet unknown command encountered first on jubeat (1) */
AC_IO_CMD_UNKN_00FF = 0x00FF,
AC_IO_CMD_CLEAR = 0x0100,
};
enum ac_io_node_type {
AC_IO_NODE_TYPE_H44B = 0x04010000,
AC_IO_NODE_TYPE_ICCA = 0x03000000,
/* same as ICCA */
AC_IO_NODE_TYPE_ICCB = 0x03000000,
AC_IO_NODE_TYPE_LED_STRIP = 0x04020000,
AC_IO_NODE_TYPE_LED_SPIKE = 0x05010000,
AC_IO_NODE_TYPE_KFCA = 0x09060000,
AC_IO_NODE_TYPE_BI2A = 0x0d060000,
};
#pragma pack(push, 1)
struct ac_io_version {
/* Names taken from some debug text in libacio.dll */
uint32_t type;
uint8_t flag;
uint8_t major;
uint8_t minor;
uint8_t revision;
char product_code[4];
char date[16];
char time[16];
};
struct ac_io_message {
uint8_t addr; /* High bit: clear = req, set = resp */
union {
struct {
uint16_t code;
uint8_t seq_no;
uint8_t nbytes;
union {
uint8_t raw[0xFF];
uint8_t count;
uint8_t status;
struct ac_io_version version;
struct ac_io_icca_misc icca_misc;
struct ac_io_icca_state icca_state;
struct ac_io_kfca_poll_in kfca_poll_in;
struct ac_io_kfca_poll_out kfca_poll_out;
};
} cmd;
struct {
uint8_t nbytes;
uint8_t raw[0xFF]; /* 0xFFucked if I know */
} bcast;
};
};
#pragma pack(pop)
#endif

19
src/main/acio/h44b.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef ACIO_H44B_H
#define ACIO_H44B_H
#include <stdint.h>
enum ac_io_h44b_cmd {
AC_IO_H44B_CMD_SET_OUTPUTS = 0x0122,
};
struct ac_io_h44b_output {
uint8_t front_rgb[3];
uint8_t top_rgb[3];
uint8_t left_rgb[3];
uint8_t right_rgb[3];
uint8_t title_rgb[3];
uint8_t woofer_rgb[3];
};
#endif

68
src/main/acio/icca.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef AC_IO_ICCA_H
#define AC_IO_ICCA_H
#include <stdint.h>
enum ac_io_icca_cmd {
/* Yet unknown command encountered first on jubeat (1) */
AC_IO_ICCA_CMD_UNKN_0120 = 0x0120,
AC_IO_ICCA_CMD_QUEUE_LOOP_START = 0x0130,
AC_IO_ICCA_CMD_ENGAGE = 0x0131,
AC_IO_ICCA_CMD_POLL = 0x0134,
AC_IO_ICCA_CMD_SET_SLOT_STATE = 0x0135,
AC_IO_ICCA_CMD_BEGIN_KEYPAD = 0x013A,
AC_IO_ICCA_CMD_POLL_FELICA = 0x0161,
};
enum ac_io_icca_slot_state {
AC_IO_ICCA_SLOT_STATE_OPEN = 0x11,
AC_IO_ICCA_SLOT_STATE_EJECT = 0x12,
AC_IO_ICCA_SLOT_STATE_CLOSE = 0,
};
enum ac_io_icca_sensor_state {
/* Card eject event fired once after slot state is set to eject the card */
AC_IO_ICCA_SENSOR_STATE_CARD_EJECTED = 0x50,
AC_IO_ICCA_SENSOR_MASK_FRONT_ON = (1 << 4),
AC_IO_ICCA_SENSOR_MASK_BACK_ON = (1 << 5)
};
enum ac_io_icca_keypad_mask {
AC_IO_ICCA_KEYPAD_MASK_EMPTY = (1 << 0),
AC_IO_ICCA_KEYPAD_MASK_3 = (1 << 1),
AC_IO_ICCA_KEYPAD_MASK_6 = (1 << 2),
AC_IO_ICCA_KEYPAD_MASK_9 = (1 << 3),
AC_IO_ICCA_KEYPAD_MASK_0 = (1 << 8),
AC_IO_ICCA_KEYPAD_MASK_1 = (1 << 9),
AC_IO_ICCA_KEYPAD_MASK_4 = (1 << 10),
AC_IO_ICCA_KEYPAD_MASK_7 = (1 << 11),
AC_IO_ICCA_KEYPAD_MASK_00 = (1 << 12),
AC_IO_ICCA_KEYPAD_MASK_2 = (1 << 13),
AC_IO_ICCA_KEYPAD_MASK_5 = (1 << 14),
AC_IO_ICCA_KEYPAD_MASK_8 = (1 << 15),
};
#pragma pack(push, 1)
struct ac_io_icca_misc {
uint8_t unknown;
uint8_t subcmd;
};
struct ac_io_icca_state {
/* Similar to the struct returned by libacio, but not quite the same */
uint8_t status_code;
uint8_t sensor_state;
uint8_t uid[8];
uint8_t card_type;
uint8_t keypad_started;
uint8_t key_events[2];
uint16_t key_state;
};
#pragma pack(pop)
#endif

48
src/main/acio/iccb.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef AC_IO_ICCB_H
#define AC_IO_ICCB_H
#include <stdint.h>
enum ac_io_iccb_cmd {
/* found on jubeat prop, sent after acio init req, maybe fw update? */
AC_IO_ICCB_CMD_UNK_0100 = 0x0100,
/* found on jubeat prop, sent right after queue loop start */
AC_IO_ICCB_CMD_UNK_0116 = 0x0116,
/* found on jubeat prop, sent after 0100 req */
AC_IO_ICCB_CMD_UNK_0120 = 0x0120,
AC_IO_ICCB_CMD_QUEUE_LOOP_START = 0x0130,
AC_IO_ICCB_CMD_POLL = 0x0134,
AC_IO_ICCB_CMD_UNK_135 = 0x0135,
AC_IO_ICCB_CMD_SLEEP = 0x013A,
AC_IO_ICCB_CMD_READ_CARD = 0x0161
};
enum ac_io_iccb_sensor_state {
AC_IO_ICCB_SENSOR_CARD = 0x02,
AC_IO_ICCB_SENSOR_NO_CARD = 0x04
};
enum ac_io_iccb_card_type {
AC_IO_ICCB_CARD_TYPE_ISO15696 = 0x0,
AC_IO_ICCB_CARD_TYPE_FELICA = 0x1,
};
#pragma pack(push, 1)
struct ac_io_iccb_misc {
uint8_t unknown;
uint8_t subcmd;
};
struct ac_io_iccb_state {
uint8_t sensor_state;
uint8_t card_type;
uint8_t uid[8];
uint8_t unk2;
uint8_t unk3;
uint8_t unk4[4];
};
#pragma pack(pop)
#endif

46
src/main/acio/kfca.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef AC_IO_KFCA_H
#define AC_IO_KFCA_H
#define AC_IO_CMD_KFCA_POLL 0x0113
#define AC_IO_CMD_KFCA_UNK_0120 0x0120
#define AC_IO_CMD_KFCA_UNK_0128 0x0128
#define AC_IO_KFCA_IN_GPIO_SYS_COIN 0x04
/* ... AC_IO_KFCA_IN_GPIO_SYS_COIN2 0x08 (maybe?) */
#define AC_IO_KFCA_IN_GPIO_SYS_TEST 0x10
#define AC_IO_KFCA_IN_GPIO_SYS_SERVICE 0x20
#define AC_IO_KFCA_IN_GPIO_0_C 0x0001
#define AC_IO_KFCA_IN_GPIO_0_B 0x0002
#define AC_IO_KFCA_IN_GPIO_0_A 0x0004
#define AC_IO_KFCA_IN_GPIO_0_START 0x0008
#define AC_IO_KFCA_IN_GPIO_0_HEADPHONE 0x0020
#define AC_IO_KFCA_IN_GPIO_1_FX_R 0x0008
#define AC_IO_KFCA_IN_GPIO_1_FX_L 0x0010
#define AC_IO_KFCA_IN_GPIO_1_D 0x0020
#pragma pack(push, 1)
struct ac_io_kfca_poll_in {
/* ADC data is 10 bits. Low 6 bits of ADCs 1 through 3 are zero, low 6 bits
of ADC 0 is the system GPIOs (test, service, coin). */
union {
uint16_t gpio_sys;
struct {
uint16_t adc[4];
uint16_t gpio[4];
};
};
};
struct ac_io_kfca_poll_out {
uint32_t gpio;
uint8_t pwm[18];
};
#pragma pack(pop)
#endif

View File

@@ -0,0 +1,9 @@
libs += aciodrv
libs_aciodrv := \
src_aciodrv := \
device.c \
icca.c \
port.c \

335
src/main/aciodrv/device.c Normal file
View File

@@ -0,0 +1,335 @@
#define LOG_MODULE "aciodrv-device"
#include <string.h>
#include "aciodrv/device.h"
#include "aciodrv/port.h"
#include "util/hex.h"
#include "util/log.h"
/* Enable to dump all data to the logger */
//#define AC_IO_MSG_LOG
static uint8_t aciodrv_device_msg_counter = 1;
static uint8_t aciodrv_device_node_count;
static char aviodrv_device_node_products[16][4];
static bool aciodrv_device_init(void)
{
uint8_t init_seq[1] = {AC_IO_SOF};
/* init/reset the device by sending 0xAA until 0xAA is returned */
int read = 0;
do {
if (aciodrv_port_write(init_seq, sizeof(init_seq)) <= 0) {
return false;
}
read = aciodrv_port_read(init_seq, sizeof(init_seq));
} while (read == 0);
if (read > 0) {
/* empty buffer by reading all data */
while (read > 0) {
read = aciodrv_port_read(init_seq, sizeof(init_seq));
}
return read == 0;
} else {
return false;
}
}
#ifdef AC_IO_MSG_LOG
static void aciodrv_device_log_buffer(const char* msg, const uint8_t* buffer,
int length)
{
char str[4096];
hex_encode_uc((const void*) buffer, length, str, sizeof(str));
log_misc("%s, length %d: %s", msg, length, str);
}
#endif
static bool aciodrv_device_send(const uint8_t* buffer, int length)
{
uint8_t send_buf[512];
int send_buf_pos = 0;
uint8_t checksum = 0;
if (length > sizeof(send_buf)) {
log_warning("Send buffer overflow");
return false;
}
#ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Send (1)", buffer, length);
#endif
send_buf[send_buf_pos++] = AC_IO_SOF;
/* TODO overrun checks */
for (int i = 0; i < length; i++) {
if (buffer[i] == AC_IO_SOF || buffer[i] == AC_IO_ESCAPE) {
send_buf[send_buf_pos++] = AC_IO_ESCAPE;
send_buf[send_buf_pos++] = ~buffer[i];
} else {
send_buf[send_buf_pos++] = buffer[i];
}
checksum += buffer[i];
}
/* we have to escape the checksum as well! */
if (checksum == AC_IO_SOF || checksum == AC_IO_ESCAPE) {
send_buf[send_buf_pos++] = AC_IO_ESCAPE;
send_buf[send_buf_pos++] = ~checksum;
} else {
send_buf[send_buf_pos++] = checksum;
}
#ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Send (2)", send_buf, send_buf_pos);
#endif
if (aciodrv_port_write(send_buf, send_buf_pos) != send_buf_pos) {
log_warning("Sending data with length %d failed", send_buf_pos);
return false;
}
return true;
}
static int aciodrv_device_receive(uint8_t* buffer, int size)
{
uint8_t recv_buf[512];
int recv_size = 0;
int read = 0;
uint8_t checksum = 0;
int result_size = 0;
/* reading a byte stream, we are getting a varying amount
of 0xAAs before we get a valid message. */
recv_buf[0] = AC_IO_SOF;
do {
read = aciodrv_port_read(recv_buf, 1);
} while (recv_buf[0] == AC_IO_SOF);
if (read > 0) {
size += 1;
/* recv_buf[0] is already the first byte of the message.
now read until nothing's left */
recv_size++;
size--;
/* important: we have to know how much data we expect
and have to read until we reach the requested amount.
Because this can be interrupted by 0 reads and we
need to handle escaping (which relies on an up to
date recv_buf[recv_size]) we loop until we get a
non-zero read. */
while (size > 0) {
do {
read = aciodrv_port_read(recv_buf + recv_size, 1);
} while (read == 0);
if (read < 0) {
break;
}
/* check for escape byte. these don't count towards the
size we expect! */
if (recv_buf[recv_size] == AC_IO_ESCAPE)
{
/* next byte is our real data
overwrite escape byte */
do {
read = aciodrv_port_read(recv_buf + recv_size, 1);
} while (read == 0);
if (read < 0) {
break;
}
recv_buf[recv_size] = ~recv_buf[recv_size];
}
recv_size += read;
size -= read;
}
#ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Recv (1)", recv_buf, recv_size);
#endif
/* recv_size - 1: omit checksum for checksum calc */
for (int i = 0; i < recv_size - 1; i++) {
checksum += recv_buf[i];
buffer[i] = recv_buf[i];
}
result_size = recv_size - 1;
#ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Recv (2)", buffer, result_size);
#endif
if (checksum != recv_buf[recv_size - 1]) {
log_warning("Invalid message checksum: %02X != %02X",
checksum, recv_buf[recv_size - 1]);
return -1;
}
return result_size;
}
return -1;
}
static uint8_t aciodrv_device_enum_nodes(void)
{
struct ac_io_message msg;
msg.addr = 0x00;
msg.cmd.code = ac_io_u16(AC_IO_CMD_ASSIGN_ADDRS);
msg.cmd.nbytes = 1;
msg.cmd.count = 0;
if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) {
log_warning("Enumerating nodes failed");
return 0;
}
log_info("Enumerated %d nodes", msg.cmd.count);
return msg.cmd.count;
}
static bool aciodrv_device_get_version(uint8_t node_id, char product[4])
{
struct ac_io_message msg;
msg.addr = node_id;
msg.cmd.code = ac_io_u16(AC_IO_CMD_GET_VERSION);
msg.cmd.nbytes = 0;
if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) +
sizeof(struct ac_io_version))) {
log_warning("Get version of node %d failed", node_id);
return false;
}
log_info("Node %d: type %d, flag %d, version %d.%d.%d, product %c%c%c%c, "
"build date: %s %s",
node_id,
msg.cmd.version.type,
msg.cmd.version.flag,
msg.cmd.version.major,
msg.cmd.version.minor,
msg.cmd.version.revision,
msg.cmd.version.product_code[0],
msg.cmd.version.product_code[1],
msg.cmd.version.product_code[2],
msg.cmd.version.product_code[3],
msg.cmd.version.date,
msg.cmd.version.time);
memcpy(product, msg.cmd.version.product_code, 4);
return true;
}
static bool aciodrv_device_start_node(uint8_t node_id)
{
struct ac_io_message msg;
msg.addr = node_id;
msg.cmd.code = ac_io_u16(AC_IO_CMD_START_UP);
msg.cmd.nbytes = 0;
if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) {
log_warning("Starting node %d failed", node_id);
return false;
}
log_info("Started node %d, status: %d", node_id, msg.cmd.status);
return true;
}
bool aciodrv_device_open(const char* port, int baud)
{
if (!aciodrv_port_open(port, baud)) {
return false;
}
if (!aciodrv_device_init()) {
return false;
}
aciodrv_device_node_count = aciodrv_device_enum_nodes();
if (aciodrv_device_node_count == 0) {
return false;
}
for (uint8_t i = 0; i < aciodrv_device_node_count; i++) {
if (!aciodrv_device_get_version(i + 1, aviodrv_device_node_products[i])) {
return false;
}
}
for (uint8_t i = 0; i < aciodrv_device_node_count; i++) {
if (!aciodrv_device_start_node(i + 1)) {
return false;
}
}
return true;
}
uint8_t aciodrv_device_get_node_count(void)
{
return aciodrv_device_node_count;
}
bool aciodrv_device_get_node_product_ident(uint8_t node_id, char product[4])
{
if (aciodrv_device_node_count == 0 || node_id > aciodrv_device_node_count) {
return false;
}
memcpy(product, aviodrv_device_node_products[node_id], 4);
return true;
}
bool aciodrv_send_and_recv(struct ac_io_message* msg, int resp_size)
{
msg->cmd.seq_no = aciodrv_device_msg_counter++;
if (aciodrv_device_send((uint8_t*) msg,
offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes) <= 0) {
return false;
}
uint16_t req_code = msg->cmd.code;
if (aciodrv_device_receive((uint8_t*) msg,
resp_size) <= 0) {
return false;
}
if (req_code != msg->cmd.code) {
log_warning("Received invalid response %04X for request %04X",
msg->cmd.code, req_code);
return false;
}
return true;
}
void aciodrv_device_close(void)
{
aciodrv_port_close();
}

52
src/main/aciodrv/device.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef ACIODRV_DEVICE_H
#define ACIODRV_DEVICE_H
#include <stdbool.h>
#include "acio/acio.h"
/**
* Open an ACIO device connected to a serial port.
*
* @param port Port the device is connected to (e.g. "COM1")
* @param baud Baud rate for communication (e.g. 57600 for ICCA)
* @return True if opening the port and resetting the device was successful,
* false on error.
*/
bool aciodrv_device_open(const char* port, int baud);
/**
* Get the node count on the opened device.
*
* @return Total num of nodes enumerated on the ACIO device.
*/
uint8_t aciodrv_device_get_node_count(void);
/**
* Get the product identifier of an enumerated node.
*
* @param node_id Id of the node. Needs to be in range of the total node count.
* @param product Buffer to return the product id to.
* @return True on success, false on error. If True the variable product contains
* the identifier of the queried node.
*/
bool aciodrv_device_get_node_product_ident(uint8_t node_id, char product[4]);
/**
* Send a message to the ACIO bus and receive an answer.
* Use this to implement the protocol for each type of device that can be
* part of the bus.
*
* @param msg Msg to send to the bus. Make sure that the buffer backing
* this message is big enough to receive the response as well.
* @param resp_size Size of the expecting response.
* @return True on success, false on error.
*/
bool aciodrv_send_and_recv(struct ac_io_message* msg, int resp_size);
/**
* Close the previously opened ACIO device.
*/
void aciodrv_device_close(void);
#endif

107
src/main/aciodrv/icca.c Normal file
View File

@@ -0,0 +1,107 @@
#define LOG_MODULE "aciodrv-icca"
#include <string.h>
#include "aciodrv/device.h"
#include "util/log.h"
static bool aciodrv_icca_queue_loop_start(uint8_t node_id)
{
struct ac_io_message msg;
msg.addr = node_id;
msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_QUEUE_LOOP_START);
msg.cmd.nbytes = 1;
msg.cmd.status = 0;
if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) {
log_warning("Starting queue loop failed");
return false;
}
log_info("Started queue loop of node %d, status: %d",
node_id, msg.cmd.status);
return true;
}
bool aciodrv_icca_init(uint8_t node_id)
{
if (!aciodrv_icca_queue_loop_start(node_id + 1)) {
return false;
}
return true;
}
bool aciodrv_icca_set_state(uint8_t node_id, int slot_state,
struct ac_io_icca_state* state)
{
struct ac_io_message msg;
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_SET_SLOT_STATE);
msg.cmd.nbytes = 2;
/* buffer size of data we expect */
msg.cmd.raw[0] = sizeof(struct ac_io_icca_state);
msg.cmd.raw[1] = slot_state;
if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) +
msg.cmd.raw[0])) {
log_warning("Setting state of node %d failed", node_id + 1);
return false;
}
if (state != NULL) {
memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state));
}
return true;
}
bool aciodrv_icca_get_state(uint8_t node_id, struct ac_io_icca_state* state)
{
struct ac_io_message msg;
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_POLL);
msg.cmd.nbytes = 1;
/* buffer size of data we expect */
msg.cmd.count = sizeof(struct ac_io_icca_state);
if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) +
msg.cmd.count)) {
log_warning("Getting state of node %d failed", node_id + 1);
return false;
}
if (state != NULL) {
memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state));
}
return true;
}
bool aciodrv_icca_read_card(uint8_t node_id, struct ac_io_icca_state* state)
{
struct ac_io_message msg;
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_ENGAGE);
msg.cmd.nbytes = 1;
/* buffer size of data we expect */
msg.cmd.count = sizeof(struct ac_io_icca_state);
if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) +
msg.cmd.count)) {
log_warning("Reading card of node %d failed", node_id + 1);
return false;
}
if (state != NULL) {
memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state));
}
return true;
}

60
src/main/aciodrv/icca.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef ACIODRV_ICCA_H
#define ACIODRV_ICCA_H
#include "acio/icca.h"
/**
* Initialize an ICCA node.
*
* @param node_id Id of the node to initialize (0 based).
* @return True if successful, false on error.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_init(uint8_t node_id);
/**
* Set the state of on ICCA node.
*
* @param node_id Id of the node to set the state for (0 based).
* @param slot_state State of the slot (refer to corresponding enum).
* @param state Pointer to a state struct to return the current state to
* (optional, NULL for none).
* @return True on success, false on error.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_set_state(uint8_t node_id, int slot_state, struct ac_io_icca_state* state);
/**
* Get the current state of an ICCA node.
*
* @param node_id Id of the node to query (0 based).
* @param state Pointer to a state struct to return the current state to
* (optional, NULL for none).
* @return True on success, false on error.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_get_state(uint8_t node_id, struct ac_io_icca_state* state);
/**
* Trigger a card read action on the ICCA reader. Make sure to call this
* when you want to read the card. Just polling the state is not sufficient
* to get the most recent card data. Make sure to re-get the state after
* a read call. The state returned here might not be up to date for some reason.
*
* @param node_id Id of the node to query (0 based).
* @param state Pointer to a state struct to return the current state to
* (optional, NULL for none).
* @return True on success, false on error.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_read_card(uint8_t node_id, struct ac_io_icca_state* state);
#endif

157
src/main/aciodrv/port.c Normal file
View File

@@ -0,0 +1,157 @@
#define LOG_MODULE "aciodrv-port"
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>
#include "util/log.h"
static HANDLE aciodrv_port_fd;
bool aciodrv_port_open(const char* port, int baud)
{
COMMTIMEOUTS ct;
DCB dcb;
log_info("Opening ACIO on %s at %d baud", port, baud);
aciodrv_port_fd = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL, NULL);
if (aciodrv_port_fd == INVALID_HANDLE_VALUE) {
log_warning("Failed to open %s", port);
goto early_fail;
}
if (!SetCommMask(aciodrv_port_fd, EV_RXCHAR)) {
log_warning("SetCommMask failed");
goto fail;
}
if (!SetupComm(aciodrv_port_fd, 0x1000, 0x1000)) {
log_warning("SetupComm failed");
goto fail;
}
if (!PurgeComm(aciodrv_port_fd,
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) {
log_warning("PurgeComm failed");
goto fail;
}
ct.ReadTotalTimeoutConstant = 0;
ct.WriteTotalTimeoutConstant = 0;
ct.ReadIntervalTimeout = -1;
ct.ReadTotalTimeoutMultiplier = 0;
ct.WriteTotalTimeoutMultiplier = 0;
if (!SetCommTimeouts(aciodrv_port_fd, &ct)) {
log_warning("SetCommTimeouts failed");
goto fail;
}
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(aciodrv_port_fd, &dcb)) {
log_warning("GetCommState failed");
goto fail;
}
dcb.BaudRate = baud;
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fErrorChar = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fAbortOnError = FALSE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.XonChar = 17;
dcb.XoffChar = 19;
dcb.XonLim = 100;
dcb.XoffLim = 100;
if (!SetCommState(aciodrv_port_fd, &dcb)) {
log_warning("SetCommState failed");
goto fail;
}
if (!EscapeCommFunction(aciodrv_port_fd, SETDTR)) {
log_warning("SETDTR failed: err = %lu", GetLastError());
goto fail;
}
log_info("Opened ACIO device on %s", port);
return true;
fail:
CloseHandle(aciodrv_port_fd);
early_fail:
aciodrv_port_fd = NULL;
return false;
}
int aciodrv_port_read(void *bytes, int nbytes)
{
DWORD nread;
if (aciodrv_port_fd == NULL) {
return -1;
}
if (!ClearCommError(aciodrv_port_fd, NULL, NULL)) {
log_warning("ClearCommError failed");
return -1;
}
if (!ReadFile(aciodrv_port_fd, bytes, nbytes, &nread, NULL)) {
log_warning("ReadFile failed: err = %lu", GetLastError());
return -1;
}
return nread;
}
int aciodrv_port_write(const void *bytes, int nbytes)
{
DWORD nwrit;
if (aciodrv_port_fd == NULL) {
return -1;
}
if (!WriteFile(aciodrv_port_fd, bytes, nbytes, &nwrit, NULL)) {
log_warning("WriteFile failed: err = %lu", GetLastError());
return -1;
}
return nwrit;
}
void aciodrv_port_close(void)
{
if (aciodrv_port_fd != NULL) {
CloseHandle(aciodrv_port_fd);
}
}

42
src/main/aciodrv/port.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef ACIODRV_PORT_H
#define ACIODRV_PORT_H
#include <stdint.h>
#include <stdbool.h>
/**
* Open a serial port for communication with a ACIO device.
*
* @param port Port the device is connected to (e.g. "COM1")
* @param baud Baud rate for communication (e.g. 57600 for ICCA)
* @return True if opening the com port was successful, false on error.
* @note This will open and setup the com port, only.
*/
bool aciodrv_port_open(const char* port, int baud);
/**
* Read data from the opened com port.
*
* @param bytes Pointer to an allocated buffer to read the data into.
* @param nbytes Number of bytes to read. Has to be less or equal the allocated
* buffer size.
* @return Number of bytes read on success or -1 on error.
*/
int aciodrv_port_read(void *bytes, int nbytes);
/**
* Write data to the opened com port.
*
* @param bytes Pointer to an allocated buffer with data to write.
* @param nbytes Number of bytes to write. Has to be equal or less the size
* of the allocated buffer.
* @return Number of bytes written on success or -1 on error.
*/
int aciodrv_port_write(const void *bytes, int nbytes);
/**
* Close the previously opened com port.
*/
void aciodrv_port_close(void);
#endif

View File

@@ -0,0 +1,11 @@
libs += acioemu
src_acioemu := \
addr.c \
emu.c \
h44b.c \
hdxs.c \
icca.c \
iccb.c \
pipe.c \

39
src/main/acioemu/addr.c Normal file
View File

@@ -0,0 +1,39 @@
#include <stdint.h>
#include "acio/acio.h"
#include "acioemu/addr.h"
#include "acioemu/emu.h"
#include "util/log.h"
void ac_io_emu_cmd_assign_addrs(
struct ac_io_emu *emu,
const struct ac_io_message *req,
uint8_t node_count)
{
struct ac_io_message resp;
uint16_t cmd;
log_assert(emu != NULL);
log_assert(req != NULL);
cmd = ac_io_u16(req->cmd.code);
if (cmd != AC_IO_CMD_ASSIGN_ADDRS) {
log_warning(
"Address 0 expects address assignment cmd, got %04x",
cmd);
return;
}
memset(&resp, 0, sizeof(resp));
resp.addr = 0;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.count);
resp.cmd.count = node_count;
ac_io_emu_response_push(emu, &resp, 0);
}

15
src/main/acioemu/addr.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef AC_IO_EMU_ADDR_H
#define AC_IO_EMU_ADDR_H
#include "acio/acio.h"
#include "acioemu/emu.h"
#include <stdint.h>
void ac_io_emu_cmd_assign_addrs(
struct ac_io_emu *emu,
const struct ac_io_message *req,
uint8_t node_count);
#endif

194
src/main/acioemu/emu.c Normal file
View File

@@ -0,0 +1,194 @@
#include <windows.h> /* Usermode API */
#include <ntdef.h> /* Kernel-mode API for ioctls */
#include <devioctl.h>
#include <ntddser.h>
#include <stdbool.h>
#include <stdint.h>
#include <wchar.h>
#include "acioemu/emu.h"
#include "acioemu/pipe.h"
#include "hook/iohook.h"
#include "util/log.h"
#include "util/str.h"
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp);
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp);
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp);
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp);
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp);
void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename)
{
log_assert(emu != NULL);
log_assert(filename != NULL);
memset(emu, 0, sizeof(*emu));
emu->fd = iohook_open_dummy_fd();
emu->wfilename = wstr_dup(filename);
wstr_narrow(filename, &emu->filename);
ac_io_in_init(&emu->in);
ac_io_out_init(&emu->out);
}
void ac_io_emu_fini(struct ac_io_emu *emu)
{
log_assert(emu != NULL);
free(emu->filename);
free(emu->wfilename);
if (emu->fd != NULL) {
CloseHandle(emu->fd);
}
memset(emu, 0, sizeof(*emu));
}
bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp)
{
log_assert(emu != NULL);
log_assert(irp != NULL);
if (irp->op == IRP_OP_OPEN) {
return wstr_eq(emu->wfilename, irp->open_filename);
} else {
return irp->fd == emu->fd;
}
}
HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp)
{
log_assert(irp != NULL);
switch (irp->op) {
case IRP_OP_OPEN: return ac_io_emu_open(emu, irp);
case IRP_OP_CLOSE: return ac_io_emu_close(emu, irp);
case IRP_OP_READ: return ac_io_emu_read(emu, irp);
case IRP_OP_WRITE: return ac_io_emu_write(emu, irp);
case IRP_OP_IOCTL: return ac_io_emu_ioctl(emu, irp);
case IRP_OP_FSYNC: return S_FALSE;
default: return E_NOTIMPL;
}
}
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp)
{
irp->fd = emu->fd;
log_info("%s: ACIO port opened", emu->filename);
return S_FALSE;
}
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp)
{
log_info("%s: ACIO port closed", emu->filename);
return S_FALSE;
}
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp)
{
ac_io_in_drain(&emu->in, &irp->read);
return S_FALSE;
}
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp)
{
const struct ac_io_message *msg;
for (;;) {
ac_io_out_supply(&emu->out, &irp->write);
if (!ac_io_out_have_message(&emu->out)) {
break;
}
msg = ac_io_out_get_message(&emu->out);
if (msg != NULL) {
break;
}
ac_io_in_supply(&emu->in, NULL, 0);
ac_io_out_consume_message(&emu->out);
}
return ac_io_out_have_message(&emu->out) ? S_OK : S_FALSE;
}
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp)
{
SERIAL_STATUS *status;
log_assert(irp != NULL);
switch (irp->ioctl) {
case IOCTL_SERIAL_GET_COMMSTATUS:
if (irp->read.bytes == NULL) {
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL");
return E_INVALIDARG;
}
if (irp->read.nbytes < sizeof(*status)) {
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small");
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
status = (SERIAL_STATUS *) irp->read.bytes;
status->Errors = 0;
status->AmountInInQueue = ac_io_in_is_msg_pending(&emu->in);
irp->read.pos = sizeof(*status);
return S_FALSE;
default:
return S_FALSE;
}
}
const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu)
{
log_assert(emu != NULL);
return ac_io_out_get_message(&emu->out);
}
void ac_io_emu_request_pop(struct ac_io_emu *emu)
{
log_assert(emu != NULL);
ac_io_out_consume_message(&emu->out);
}
void ac_io_emu_response_push(
struct ac_io_emu *emu,
const struct ac_io_message *resp,
uint64_t delay_us)
{
log_assert(emu != NULL);
log_assert(resp != NULL);
ac_io_in_supply(&emu->in, resp, delay_us);
}
void ac_io_emu_response_push_thunk(
struct ac_io_emu *emu,
ac_io_in_thunk_t thunk,
void *ctx,
uint64_t delay_us)
{
log_assert(emu != NULL);
log_assert(thunk != NULL);
ac_io_in_supply_thunk(&emu->in, thunk, ctx, delay_us);
}

39
src/main/acioemu/emu.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef ACIOEMU_EMU_H
#define ACIOEMU_EMU_H
#include <windows.h>
#include <stdbool.h>
#include <wchar.h>
#include "acio/acio.h"
#include "acioemu/pipe.h"
#include "hook/iohook.h"
struct ac_io_emu {
HANDLE fd;
wchar_t *wfilename;
char *filename;
struct ac_io_in in;
struct ac_io_out out;
};
void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename);
void ac_io_emu_fini(struct ac_io_emu *emu);
bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp);
HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp);
const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu);
void ac_io_emu_request_pop(struct ac_io_emu *emu);
void ac_io_emu_response_push(
struct ac_io_emu *emu,
const struct ac_io_message *resp,
uint64_t delay_ms);
void ac_io_emu_response_push_thunk(
struct ac_io_emu *emu,
ac_io_in_thunk_t thunk,
void *ctx,
uint64_t delay_ms);
#endif

118
src/main/acioemu/h44b.c Normal file
View File

@@ -0,0 +1,118 @@
#define LOG_MODULE "acioemu-h44b"
#include "acioemu/h44b.h"
#include <windows.h> /* for _BitScanForward */
#include <stdint.h>
#include <string.h>
#include "acio/h44b.h"
#include "acioemu/emu.h"
#include "bemanitools/jbio.h"
#include "util/hex.h"
static void ac_io_emu_h44b_cmd_send_version(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req);
static void ac_io_emu_h44b_send_status(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req,
uint8_t status);
void ac_io_emu_h44b_init(
struct ac_io_emu_h44b *h44b,
struct ac_io_emu *emu,
uint8_t unit_no)
{
memset(h44b, 0, sizeof(*h44b));
h44b->emu = emu;
h44b->unit_no = unit_no;
}
void ac_io_emu_h44b_dispatch_request(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req)
{
uint16_t cmd_code;
cmd_code = ac_io_u16(req->cmd.code);
switch (cmd_code) {
case AC_IO_CMD_GET_VERSION:
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
ac_io_emu_h44b_cmd_send_version(h44b, req);
break;
case AC_IO_CMD_START_UP:
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
ac_io_emu_h44b_send_status(h44b, req, 0x00);
break;
case AC_IO_H44B_CMD_SET_OUTPUTS:
/* Not using the struct ac_io_h44b_output here */
for (int i = 0; i < 6; i++) {
jb_io_set_rgb_led((enum jb_io_rgb_led) i,
req->cmd.raw[i * 3],
req->cmd.raw[i * 3 + 1],
req->cmd.raw[i * 3 + 2]);
}
jb_io_write_outputs();
ac_io_emu_h44b_send_status(h44b, req, 0x00);
break;
default:
log_warning("Unknown ACIO message %04x on h44b node, addr=%d",
cmd_code, req->addr);
break;
}
}
static void ac_io_emu_h44b_cmd_send_version(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.version);
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_H44B);
resp.cmd.version.flag = 0x00;
resp.cmd.version.major = 0x01;
resp.cmd.version.minor = 0x00;
resp.cmd.version.revision = 0x02;
memcpy(resp.cmd.version.product_code, "H44B",
sizeof(resp.cmd.version.product_code));
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
ac_io_emu_response_push(h44b->emu, &resp, 0);
}
static void ac_io_emu_h44b_send_status(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req,
uint8_t status)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.status);
resp.cmd.status = status;
ac_io_emu_response_push(h44b->emu, &resp, 0);
}

26
src/main/acioemu/h44b.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef AC_IO_EMU_H44B_H
#define AC_IO_EMU_H44B_H
#include <stdbool.h>
#include <stdint.h>
#include "acioemu/emu.h"
struct ac_io_emu_h44b {
struct ac_io_emu *emu;
uint8_t unit_no;
// TODO
};
void acioemu_h44b_init(void);
void ac_io_emu_h44b_init(
struct ac_io_emu_h44b *h44b,
struct ac_io_emu *emu,
uint8_t unit_no);
void ac_io_emu_h44b_dispatch_request(
struct ac_io_emu_h44b *h44b,
const struct ac_io_message *req);
#endif

133
src/main/acioemu/hdxs.c Normal file
View File

@@ -0,0 +1,133 @@
#define LOG_MODULE "acioemu-hdxs"
#include "acio/acio.h"
#include "acioemu/emu.h"
#include "acioemu/hdxs.h"
#include "util/log.h"
static void ac_io_emu_hdxs_cmd_send_version(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req);
static void ac_io_emu_hdxs_send_empty(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req);
static void ac_io_emu_hdxs_send_status(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req,
uint8_t status);
void ac_io_emu_hdxs_init(
struct ac_io_emu_hdxs *hdxs,
struct ac_io_emu *emu)
{
log_assert(hdxs != NULL);
log_assert(emu != NULL);
hdxs->emu = emu;
}
void ac_io_emu_hdxs_dispatch_request(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req)
{
uint16_t cmd_code;
log_assert(hdxs != NULL);
log_assert(req != NULL);
cmd_code = ac_io_u16(req->cmd.code);
switch (cmd_code) {
case AC_IO_CMD_GET_VERSION:
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
ac_io_emu_hdxs_cmd_send_version(hdxs, req);
break;
case AC_IO_CMD_START_UP:
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
ac_io_emu_hdxs_send_status(hdxs, req, 0x00);
break;
case AC_IO_CMD_CLEAR:
log_misc("AC_IO_CMD_CLEAR(%d)", req->addr);
case 0x110:
case 0x112:
case 0x128:
ac_io_emu_hdxs_send_status(hdxs, req, 0x00);
break;
case AC_IO_CMD_KEEPALIVE:
ac_io_emu_hdxs_send_empty(hdxs, req);
break;
default:
log_warning(
"Unknown ACIO message %04x on HDXS node, addr=%d",
cmd_code,
req->addr);
break;
}
}
static void ac_io_emu_hdxs_cmd_send_version(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.version);
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_LED_STRIP);
resp.cmd.version.flag = 0x00;
resp.cmd.version.major = 0x01;
resp.cmd.version.minor = 0x06;
resp.cmd.version.revision = 0x00;
memcpy(resp.cmd.version.product_code, "HDXS",
sizeof(resp.cmd.version.product_code));
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
ac_io_emu_response_push(hdxs->emu, &resp, 0);
}
static void ac_io_emu_hdxs_send_empty(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = 0;
ac_io_emu_response_push(hdxs->emu, &resp, 0);
}
static void ac_io_emu_hdxs_send_status(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req,
uint8_t status)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.status);
resp.cmd.status = status;
ac_io_emu_response_push(hdxs->emu, &resp, 0);
}

21
src/main/acioemu/hdxs.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef AC_IO_EMU_HDXS_H
#define AC_IO_EMU_HDXS_H
#include "acio/acio.h"
#include "acioemu/emu.h"
struct ac_io_emu_hdxs {
struct ac_io_emu *emu;
// TODO ops vtbl
};
void ac_io_emu_hdxs_init(
struct ac_io_emu_hdxs *hdxs,
struct ac_io_emu *emu);
void ac_io_emu_hdxs_dispatch_request(
struct ac_io_emu_hdxs *hdxs,
const struct ac_io_message *req);
#endif

385
src/main/acioemu/icca.c Normal file
View File

@@ -0,0 +1,385 @@
#define LOG_MODULE "acioemu-icca"
#include "acioemu/icca.h"
#include <windows.h> /* for _BitScanForward */
#include <stdint.h>
#include <string.h>
#include "acio/icca.h"
#include "acioemu/emu.h"
#include "acioemu/icca.h"
#include "bemanitools/eamio.h"
#include "util/time.h"
enum ac_io_icca_subcmd {
AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE = 0x00,
AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN = 0x11,
AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT = 0x12,
};
enum ac_io_icca_flag {
AC_IO_ICCA_FLAG_FRONT_SENSOR = 0x10,
AC_IO_ICCA_FLAG_REAR_SENSOR = 0x20,
AC_IO_ICCA_FLAG_SOLENOID = 0x40
};
enum ac_io_icca_status_code {
AC_IO_ICCA_STATUS_FAULT = 0x00,
AC_IO_ICCA_STATUS_IDLE = 0x01,
AC_IO_ICCA_STATUS_GOT_UID = 0x02
};
static void ac_io_emu_icca_cmd_send_version(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req);
static void ac_io_emu_icca_send_state(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req,
uint64_t delay_us);
static void ac_io_emu_icca_send_empty(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req);
static void ac_io_emu_icca_send_status(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req,
uint8_t status);
void ac_io_emu_icca_init(
struct ac_io_emu_icca *icca,
struct ac_io_emu *emu,
uint8_t unit_no)
{
memset(icca, 0, sizeof(*icca));
icca->emu = emu;
icca->unit_no = unit_no;
// queue must be started
icca->fault = true;
}
void ac_io_emu_icca_dispatch_request(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req)
{
uint16_t cmd_code;
uint64_t delay_us;
cmd_code = ac_io_u16(req->cmd.code);
switch (cmd_code) {
case AC_IO_CMD_GET_VERSION:
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
ac_io_emu_icca_cmd_send_version(icca, req);
break;
case AC_IO_CMD_START_UP:
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
icca->detected_new_reader = false;
ac_io_emu_icca_send_status(icca, req, 0x00);
break;
case AC_IO_CMD_CLEAR:
log_misc("AC_IO_ICCA_CMD_CLEAR(%d)", req->addr);
ac_io_emu_icca_send_status(icca, req, 0x00);
break;
case AC_IO_CMD_KEEPALIVE:
ac_io_emu_icca_send_empty(icca, req);
break;
case AC_IO_ICCA_CMD_QUEUE_LOOP_START:
log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr);
// queue started, reset error state
icca->fault = false;
ac_io_emu_icca_send_status(icca, req, 0x00);
icca->polling_started = true;
break;
case AC_IO_CMD_UNKN_00FF:
log_misc("AC_IO_CMD_UNKN_00FF(%d)", req->addr);
ac_io_emu_icca_send_status(icca, req, 0x00);
break;
case AC_IO_ICCA_CMD_UNKN_0120:
log_misc("AC_IO_ICCA_CMD_UNKN_0120(%d)", req->addr);
ac_io_emu_icca_send_status(icca, req, 0x00);
break;
case AC_IO_ICCA_CMD_BEGIN_KEYPAD:
log_misc("AC_IO_ICCA_CMD_BEGIN_KEYPAD(%d)", req->addr);
ac_io_emu_icca_send_status(icca, req, 0x00);
icca->keypad_started = true;
break;
case AC_IO_ICCA_CMD_ENGAGE:
ac_io_emu_icca_send_state(icca, req, 0);
break;
case AC_IO_ICCA_CMD_SET_SLOT_STATE:
{
struct ac_io_icca_misc* misc =
(struct ac_io_icca_misc*) &req->cmd.raw;
uint8_t cmd;
switch (misc->subcmd) {
case AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE:
cmd = EAM_IO_CARD_SLOT_CMD_CLOSE;
break;
case AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN:
cmd = EAM_IO_CARD_SLOT_CMD_OPEN;
break;
case AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT:
cmd = EAM_IO_CARD_SLOT_CMD_EJECT;
icca->engaged = false;
break;
case 3:
cmd = EAM_IO_CARD_SLOT_CMD_READ;
break;
default:
cmd = 0xFF;
log_warning("Unhandled slot command %X, node %d",
misc->subcmd, icca->unit_no);
break;
}
if (cmd != 0xFF) {
if (!eam_io_card_slot_cmd(icca->unit_no, cmd)) {
log_warning("Eamio failed to handle slot cmd %d for node %d",
cmd, icca->unit_no);
}
}
/* response with current slot state */
ac_io_emu_icca_send_status(icca, req, misc->subcmd);
break;
}
case AC_IO_ICCA_CMD_POLL:
delay_us = time_get_elapsed_us(time_get_counter() - icca->time_counter_last_poll);
/* emulating delay implemented by hardware. do not delay messages that exceed a certain threshold. */
if (delay_us > 16000) {
delay_us = 0;
}
icca->time_counter_last_poll = time_get_counter();
ac_io_emu_icca_send_state(icca, req, delay_us);
break;
case AC_IO_ICCA_CMD_POLL_FELICA:
icca->detected_new_reader = true;
ac_io_emu_icca_send_status(icca, req, 0x01);
break;
default:
log_warning("Unknown ACIO message %04x on ICCA node, addr=%d",
cmd_code, req->addr);
break;
}
}
static void ac_io_emu_icca_cmd_send_version(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.version);
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCA);
resp.cmd.version.flag = 0x00;
resp.cmd.version.major = 0x01;
resp.cmd.version.minor = 0x06;
resp.cmd.version.revision = 0x00;
memcpy(resp.cmd.version.product_code, "ICCA",
sizeof(resp.cmd.version.product_code));
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
ac_io_emu_response_push(icca->emu, &resp, 0);
}
static void ac_io_emu_icca_send_empty(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = 0;
ac_io_emu_response_push(icca->emu, &resp, 0);
}
static void ac_io_emu_icca_send_status(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req,
uint8_t status)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.status);
resp.cmd.status = status;
ac_io_emu_response_push(icca->emu, &resp, 0);
}
static void ac_io_emu_icca_send_state(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req,
uint64_t delay_us)
{
struct ac_io_message resp;
struct ac_io_icca_state *body;
unsigned long bit;
uint8_t event;
uint16_t keypad;
uint16_t keypad_rise;
uint8_t sensor_state;
bool card_full_insert;
if (!eam_io_poll(icca->unit_no)) {
log_warning("Polling eamio failed");
}
memset(&resp, 0, sizeof(resp));
keypad = eam_io_get_keypad_state(icca->unit_no);
sensor_state = eam_io_get_sensor_state(icca->unit_no);
keypad_rise = keypad & (icca->last_keypad ^ keypad);
card_full_insert = sensor_state & (1 << EAM_IO_SENSOR_FRONT) &&
sensor_state & (1 << EAM_IO_SENSOR_BACK);
if (sensor_state != icca->last_sensor) {
if (card_full_insert) {
if (!eam_io_card_slot_cmd(icca->unit_no,
EAM_IO_CARD_SLOT_CMD_READ)) {
log_warning("EAM_IO_CARD_SLOT_CMD_READ to unit %d failed",
icca->unit_no);
}
icca->card_result = eam_io_read_card(
icca->unit_no,
icca->uid,
sizeof(icca->uid));
// fault if sensor says to read but we got no card
icca->fault = (icca->card_result == EAM_IO_CARD_NONE);
} else {
icca->fault = false;
}
}
icca->last_sensor = sensor_state;
icca->last_keypad = keypad;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(struct ac_io_icca_state);
body = (struct ac_io_icca_state *) &resp.cmd.raw;
if (icca->fault) {
body->status_code = AC_IO_ICCA_STATUS_FAULT;
} else if (card_full_insert) {
body->status_code = AC_IO_ICCA_STATUS_GOT_UID;
} else {
body->status_code = AC_IO_ICCA_STATUS_IDLE;
}
body->sensor_state = 0;
if (sensor_state & (1 << EAM_IO_SENSOR_FRONT)) {
body->sensor_state |= AC_IO_ICCA_FLAG_FRONT_SENSOR;
}
if (sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
body->sensor_state |= AC_IO_ICCA_FLAG_REAR_SENSOR;
}
if (icca->engaged) {
body->sensor_state |= AC_IO_ICCA_FLAG_SOLENOID;
}
memcpy(body->uid, icca->uid, sizeof(body->uid));
body->card_type = 0;
if (body->status_code == AC_IO_ICCA_STATUS_GOT_UID){
if (icca->detected_new_reader){
// sensor_state actually refers to cardtype for wavepass readers
// EAM_IO_CARD_ISO15696 = 1 -> 0
// EAM_IO_CARD_FELICA = 2 -> 1
body->card_type = icca->card_result - 1;
body->sensor_state = icca->card_result - 1;
}
}
if (keypad_rise) {
if (icca->key_events[0]) {
event = (icca->key_events[0] + 0x10) & 0xF0;
} else {
event = 0x00;
}
_BitScanForward(&bit, keypad_rise);
event |= 0x80 | bit;
icca->key_events[1] = icca->key_events[0];
icca->key_events[0] = event;
}
// this doesn't seem to be an error code. If this is not set to 0x03
// on slotted readers (only?), the game throws an unknown status error
if (icca->keypad_started) {
body->keypad_started = 0x03;
} else {
body->keypad_started = 0x00;
}
body->key_events[0] = icca->key_events[0];
body->key_events[1] = icca->key_events[1];
body->key_state = ac_io_u16(keypad);
// replace status code if polling hasn't started
// this fixes SDVX IC CARD boot errors
if (!icca->polling_started) {
body->status_code = AC_IO_ICCA_STATUS_FAULT;
}
ac_io_emu_response_push(icca->emu, &resp, delay_us);
}

34
src/main/acioemu/icca.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef AC_IO_EMU_ICCA_H
#define AC_IO_EMU_ICCA_H
#include <stdbool.h>
#include <stdint.h>
#include "acioemu/emu.h"
struct ac_io_emu_icca {
struct ac_io_emu *emu;
uint8_t unit_no;
bool fault;
bool engaged;
uint8_t last_sensor;
uint16_t last_keypad;
uint8_t key_events[2];
uint8_t uid[8];
uint8_t card_result;
bool detected_new_reader;
bool keypad_started;
bool polling_started;
uint64_t time_counter_last_poll;
};
void ac_io_emu_icca_init(
struct ac_io_emu_icca *icca,
struct ac_io_emu *emu,
uint8_t unit_no);
void ac_io_emu_icca_dispatch_request(
struct ac_io_emu_icca *icca,
const struct ac_io_message *req);
#endif

227
src/main/acioemu/iccb.c Normal file
View File

@@ -0,0 +1,227 @@
#define LOG_MODULE "acioemu-iccb"
#include "acioemu/iccb.h"
#include <windows.h> /* for _BitScanForward */
#include <stdint.h>
#include <string.h>
#include "acio/iccb.h"
#include "acioemu/emu.h"
#include "bemanitools/eamio.h"
static void ac_io_emu_iccb_cmd_send_version(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req);
static void ac_io_emu_iccb_send_state(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req);
static void ac_io_emu_iccb_send_empty(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req);
static void ac_io_emu_iccb_send_status(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req,
uint8_t status);
void ac_io_emu_iccb_init(
struct ac_io_emu_iccb *iccb,
struct ac_io_emu *emu,
uint8_t unit_no)
{
memset(iccb, 0, sizeof(*iccb));
iccb->emu = emu;
iccb->unit_no = unit_no;
}
void ac_io_emu_iccb_dispatch_request(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req)
{
uint16_t cmd_code;
cmd_code = ac_io_u16(req->cmd.code);
switch (cmd_code) {
case AC_IO_CMD_GET_VERSION:
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
ac_io_emu_iccb_cmd_send_version(iccb, req);
break;
case AC_IO_CMD_START_UP:
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
ac_io_emu_iccb_send_status(iccb, req, 0x00);
break;
case AC_IO_CMD_KEEPALIVE:
ac_io_emu_iccb_send_empty(iccb, req);
break;
case AC_IO_ICCB_CMD_QUEUE_LOOP_START:
log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr);
ac_io_emu_iccb_send_status(iccb, req, 0x00);
break;
case AC_IO_ICCB_CMD_UNK_0100:
case AC_IO_ICCB_CMD_UNK_0116:
case AC_IO_ICCB_CMD_UNK_0120:
log_misc("AC_IO_ICCB_CMD_UNK_%04X(%d)", cmd_code, req->addr);
ac_io_emu_iccb_send_status(iccb, req, 0x00);
break;
case AC_IO_ICCB_CMD_SLEEP:
ac_io_emu_iccb_send_status(iccb, req, 0x00);
break;
case AC_IO_ICCB_CMD_UNK_135:
/* log_misc("AC_IO_ICCB_CMD_UNK_135"); */
ac_io_emu_iccb_send_state(iccb, req);
break;
case AC_IO_ICCB_CMD_POLL:
/* log_misc("AC_IO_ICCB_CMD_POLL"); */
ac_io_emu_iccb_send_state(iccb, req);
break;
case AC_IO_ICCB_CMD_READ_CARD:
/* log_misc("AC_IO_ICCB_CMD_READ_CARD"); */
ac_io_emu_iccb_send_state(iccb, req);
break;
default:
log_warning("Unknown ACIO message %04x on ICCB node, addr=%d",
cmd_code, req->addr);
break;
}
}
static void ac_io_emu_iccb_cmd_send_version(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.version);
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCB);
resp.cmd.version.flag = 0x00;
resp.cmd.version.major = 0x01;
resp.cmd.version.minor = 0x05;
resp.cmd.version.revision = 0x01;
memcpy(resp.cmd.version.product_code, "ICCB",
sizeof(resp.cmd.version.product_code));
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
ac_io_emu_response_push(iccb->emu, &resp, 0);
}
static void ac_io_emu_iccb_send_empty(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = 0;
ac_io_emu_response_push(iccb->emu, &resp, 0);
}
static void ac_io_emu_iccb_send_status(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req,
uint8_t status)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.status);
resp.cmd.status = status;
ac_io_emu_response_push(iccb->emu, &resp, 0);
}
static void ac_io_emu_iccb_send_state(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req)
{
struct ac_io_message resp;
struct ac_io_iccb_state *body;
bool sensor;
/* state update */
sensor = eam_io_get_sensor_state(iccb->unit_no);
if (sensor != iccb->last_sensor) {
if (sensor) {
iccb->card_result = eam_io_read_card(
iccb->unit_no,
iccb->uid,
sizeof(iccb->uid));
// fault if sensor says to read but we got no card
iccb->fault = (iccb->card_result == EAM_IO_CARD_NONE);
} else {
iccb->fault = false;
}
}
iccb->last_sensor = sensor;
if (iccb->fault) {
memset(iccb->uid, 0, sizeof(iccb->uid));
}
/* create response */
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(struct ac_io_iccb_state);
body = (struct ac_io_iccb_state *) &resp.cmd.raw;
if (sensor) {
body->sensor_state = AC_IO_ICCB_SENSOR_CARD;
} else {
body->sensor_state = AC_IO_ICCB_SENSOR_NO_CARD;
}
if (!iccb->fault) {
memcpy(body->uid, iccb->uid, sizeof(body->uid));
}
body->card_type = 0x30 | (iccb->card_result - 1);
body->unk2 = 0;
// this doesn't seem to be an error code. If this is not set to 0x03
// on slotted readers (only?), the game throws an unknown status error
body->unk3 = 0x03;
memset(body->unk4, 0, sizeof(body->unk4));
ac_io_emu_response_push(iccb->emu, &resp, 0);
}

27
src/main/acioemu/iccb.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef AC_IO_EMU_ICCB_H
#define AC_IO_EMU_ICCB_H
#include <stdbool.h>
#include <stdint.h>
#include "acioemu/emu.h"
struct ac_io_emu_iccb {
struct ac_io_emu *emu;
uint8_t unit_no;
bool fault;
bool last_sensor;
uint8_t uid[8];
uint8_t card_result;
};
void ac_io_emu_iccb_init(
struct ac_io_emu_iccb *iccb,
struct ac_io_emu *emu,
uint8_t unit_no);
void ac_io_emu_iccb_dispatch_request(
struct ac_io_emu_iccb *iccb,
const struct ac_io_message *req);
#endif

366
src/main/acioemu/pipe.c Normal file
View File

@@ -0,0 +1,366 @@
#define LOG_MODULE "acioemu-emu"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "acioemu/pipe.h"
#include "util/defs.h"
#include "util/list.h"
#include "util/mem.h"
#include "util/time.h"
static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in,
uint64_t delay_us);
static void ac_io_in_queued_populate(
struct ac_io_in_queued *iq,
const struct ac_io_message *msg);
static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b);
static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b);
static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b);
static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out);
static bool ac_io_out_detect_command_eof(struct ac_io_out *out);
static bool ac_io_out_check_sum(struct ac_io_out *out);
static bool ac_io_out_accept_message(struct ac_io_out *out);
static bool ac_io_out_reject_message(struct ac_io_out *out);
static bool ac_io_enable_legacy_mode = false;
static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in,
uint64_t delay_us)
{
struct ac_io_in_queued *iq;
/* TODO ordered insert by scheduled_time */
iq = xmalloc(sizeof(*iq));
iq->iobuf.bytes = iq->bytes;
iq->iobuf.nbytes = 1;
iq->iobuf.pos = 0;
iq->scheduled_time = time_get_counter();
iq->delay_us = delay_us;
iq->bytes[0] = AC_IO_SOF;
list_append(&in->queue, &iq->node);
return iq;
}
static void ac_io_in_queued_populate(
struct ac_io_in_queued *iq,
const struct ac_io_message *msg)
{
uint8_t checksum;
const uint8_t *src;
size_t nbytes;
size_t i;
if (msg->addr == AC_IO_BROADCAST) {
nbytes = offsetof(struct ac_io_message, bcast.raw) + msg->bcast.nbytes;
} else {
nbytes = offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes;
}
src = (const uint8_t *) msg;
checksum = 0;
for (i = 0 ; i < nbytes ; i++) {
ac_io_in_queued_putc(iq, src[i]);
checksum += src[i];
}
ac_io_in_queued_putc(iq, checksum);
}
static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b)
{
if (b == AC_IO_SOF || b == AC_IO_ESCAPE) {
iq->bytes[iq->iobuf.nbytes++] = AC_IO_ESCAPE;
iq->bytes[iq->iobuf.nbytes++] = ~b;
} else {
iq->bytes[iq->iobuf.nbytes++] = b;
}
}
void ac_io_legacy_mode(void)
{
log_info("Running acioemu legacy mode");
ac_io_enable_legacy_mode = true;
}
void ac_io_in_init(struct ac_io_in *in)
{
list_init(&in->queue);
}
void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg,
uint64_t delay_us)
{
struct ac_io_in_queued *dest;
dest = ac_io_in_queued_alloc(in, delay_us);
dest->thunk = NULL;
if (msg == NULL) {
return;
}
ac_io_in_queued_populate(dest, msg);
}
void ac_io_in_supply_thunk(
struct ac_io_in *in,
ac_io_in_thunk_t thunk,
void *ctx,
uint64_t delay_us)
{
struct ac_io_in_queued *dest;
dest = ac_io_in_queued_alloc(in, delay_us);
dest->thunk = thunk;
dest->thunk_ctx = ctx;
}
void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest)
{
struct ac_io_in_queued *iq;
struct list_node *node;
struct ac_io_message msg;
size_t nmoved;
uint64_t now;
uint64_t elapsed_us;
now = time_get_counter();
do {
node = list_peek_head(&in->queue);
if (node == NULL) {
break;
}
iq = containerof(node, struct ac_io_in_queued, node);
elapsed_us = time_get_elapsed_us(
now > iq->scheduled_time ? now - iq->scheduled_time : 0);
if (elapsed_us < iq->delay_us) {
break;
}
if (iq->thunk != NULL) {
iq->thunk(iq->thunk_ctx, &msg);
iq->thunk = NULL;
ac_io_in_queued_populate(iq, &msg);
}
nmoved = iobuf_move(dest, &iq->iobuf);
if (iq->iobuf.pos == iq->iobuf.nbytes) {
list_pop_head(&in->queue);
free(iq);
}
/* Legacy mode for old libacio versions like DistorteD:
spit out single responses instead of combining them into a single
io buffer. Some old libacio versions expect separate messages and
will error (code 0x00000002) on multiple messages in a single buffer
because they just read the first message and drop the remaining ones.
If using legacy mode on newer libacio versions (e.g. Copula),
the game will error with the same error code.
*/
if (ac_io_enable_legacy_mode) {
break;
}
} while (nmoved > 0);
}
bool ac_io_in_is_msg_pending(const struct ac_io_in *in)
{
return list_peek_head_const(&in->queue) != NULL;
}
void ac_io_out_init(struct ac_io_out *out)
{
out->pos = 0;
out->in_frame = false;
out->escape = false;
out->have_message = false;
}
void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src)
{
log_assert(!ac_io_out_have_message(out));
while (src->pos < src->nbytes) {
if (!ac_io_out_supply_byte(out, src->bytes[src->pos++])) {
break;
}
}
}
static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b)
{
if (out->in_frame) {
return ac_io_out_supply_frame_byte(out, b);
} else {
if (b == AC_IO_SOF) {
out->in_frame = true;
} else if (b == AC_IO_ESCAPE) {
log_warning("Framing error: Escape byte outside frame");
}
return true;
}
}
static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b)
{
if (out->escape) {
out->escape = false;
if (b == AC_IO_SOF || b == AC_IO_ESCAPE) {
log_warning("Framing error: Control byte after escape byte");
}
b = ~b;
} else if (b == AC_IO_ESCAPE) {
out->escape = true;
return true;
} else if (b == AC_IO_SOF) {
if (out->pos == 0) {
/* Got autobaud/empty message */
out->have_message = true;
return false;
} else {
log_warning("Truncated message");
out->pos = 0;
return true;
}
}
/* Payload byte */
out->bytes[out->pos++] = b;
/* Handle contextually-implied end-of-packet events */
if (out->pos > offsetof(struct ac_io_message, addr)) {
if (out->msg.addr == AC_IO_BROADCAST) {
return ac_io_out_detect_broadcast_eof(out);
} else {
return ac_io_out_detect_command_eof(out);
}
}
return true;
}
static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out)
{
size_t end;
if (out->pos > offsetof(struct ac_io_message, bcast.nbytes)) {
end = offsetof(struct ac_io_message,bcast.raw)
+ out->msg.bcast.nbytes
+ 1;
if (out->pos == end) {
return ac_io_out_check_sum(out);
}
}
return true;
}
static bool ac_io_out_detect_command_eof(struct ac_io_out *out)
{
size_t end;
if (out->pos > offsetof(struct ac_io_message, cmd.nbytes)) {
end = offsetof(struct ac_io_message, cmd.raw)
+ out->msg.cmd.nbytes
+ 1;
if (out->pos == end) {
return ac_io_out_check_sum(out);
}
}
return true;
}
static bool ac_io_out_check_sum(struct ac_io_out *out)
{
uint8_t checksum;
size_t i;
checksum = 0;
for (i = 0 ; i < out->pos - 1 ; i++) {
checksum += out->bytes[i];
}
if (checksum == out->bytes[out->pos - 1]) {
return ac_io_out_accept_message(out);
} else {
log_warning("Checksum bad: expected %02x got %02x",
checksum, out->bytes[out->pos - 1]);
return ac_io_out_reject_message(out);
}
}
static bool ac_io_out_accept_message(struct ac_io_out *out)
{
out->have_message = true;
return false;
}
static bool ac_io_out_reject_message(struct ac_io_out *out)
{
ac_io_out_consume_message(out);
return true;
}
bool ac_io_out_have_message(const struct ac_io_out *out)
{
return out->have_message;
}
const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out)
{
if (out->pos == 0) {
/* Autobaud byte/empty frame */
return NULL;
} else {
return &out->msg;
}
}
void ac_io_out_consume_message(struct ac_io_out *out)
{
if (out->pos != 0) {
out->in_frame = false;
}
out->pos = 0;
out->have_message = false;
out->escape = false;
}

66
src/main/acioemu/pipe.h Normal file
View File

@@ -0,0 +1,66 @@
#ifndef ACIOEMU_PIPE_H
#define ACIOEMU_PIPE_H
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "acio/acio.h"
#include "util/iobuf.h"
#include "util/list.h"
#include "util/log.h"
/* This uses the USB convention where OUT and IN are from the host's (game's)
perspective. So an OUT transaction comes in to us and vice versa.
(I made those terms up, not Konami). */
typedef void (*ac_io_in_thunk_t)(void *ctx, struct ac_io_message *msg);
struct ac_io_in_queued {
struct list_node node;
struct const_iobuf iobuf;
uint64_t scheduled_time;
uint64_t delay_us;
ac_io_in_thunk_t thunk;
void *thunk_ctx;
uint8_t bytes[sizeof(struct ac_io_message)];
};
struct ac_io_in {
struct list queue;
};
struct ac_io_out {
union {
uint8_t bytes[sizeof(struct ac_io_message)];
struct ac_io_message msg;
};
size_t pos;
bool in_frame;
bool escape;
bool have_message;
};
void ac_io_legacy_mode(void);
void ac_io_in_init(struct ac_io_in *in);
void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg,
uint64_t delay);
void ac_io_in_supply_thunk(
struct ac_io_in *in,
ac_io_in_thunk_t thunk,
void *ctx,
uint64_t delay);
void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest);
bool ac_io_in_is_msg_pending(const struct ac_io_in *in);
void ac_io_out_init(struct ac_io_out *out);
void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src);
bool ac_io_out_have_message(const struct ac_io_out *out);
const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out);
void ac_io_out_consume_message(struct ac_io_out *out);
#endif

View File

@@ -0,0 +1,9 @@
exes += aciotest
libs_aciotest := \
aciodrv \
util \
src_aciotest := \
icca.c \
main.c \

View File

@@ -0,0 +1,16 @@
#ifndef ACIOTEST_HANDLER_H
#define ACIOTEST_HANDLER_H
static const uint8_t aciotest_handler_max = 16;
/**
* Handler interface for an ACIO device.
*/
struct aciotest_handler_node_handler
{
void* ctx;
bool (*init)(uint8_t node_id, void** ctx);
bool (*update)(uint8_t node_id, void* ctx);
};
#endif

83
src/main/aciotest/icca.c Normal file
View File

@@ -0,0 +1,83 @@
#include "aciotest/icca.h"
#include <stdlib.h>
#include <stdio.h>
#include "aciodrv/icca.h"
bool aciotest_icca_handler_init(uint8_t node_id, void** ctx)
{
*ctx = malloc(sizeof(uint32_t));
*((uint32_t*) *ctx) = 0;
return aciodrv_icca_init(node_id);
}
bool aciotest_icca_handler_update(uint8_t node_id, void* ctx)
{
if (*((uint32_t*) ctx) == 0) {
*((uint32_t*) ctx) = 1;
/* eject cards that were left in the reader */
if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
}
}
struct ac_io_icca_state state;
if (!aciodrv_icca_get_state(node_id, &state)) {
return false;
}
printf(">>> ICCA %d:\n"
"status_code: %d\n"
"sensor_state: %d\n"
"keypad_started: %d\n"
"card_type: %d\n"
"UUID: %02X%02X%02X%02X%02X%02X%02X%02X\n"
"key_state: %04X\n"
"key_events[0]: %02X\n"
"key_events[1]: %02X\n",
node_id, state.status_code, state.sensor_state, state.keypad_started,
state.card_type,
state.uid[0], state.uid[1], state.uid[2], state.uid[3],
state.uid[4], state.uid[5], state.uid[6], state.uid[7],
state.key_state, state.key_events[0], state.key_events[1]);
/* eject card with "empty" key */
if (state.key_state & AC_IO_ICCA_KEYPAD_MASK_EMPTY) {
if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
}
}
/* allow new card to be inserted when slot is clear */
if ( !(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
!(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_OPEN, NULL)) {
return false;
}
}
/* lock the card when fully inserted */
if ( (state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_CLOSE, NULL)) {
return false;
}
if (!aciodrv_icca_read_card(node_id, NULL)) {
return false;
}
}
return true;
}

10
src/main/aciotest/icca.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef ACIOTEST_ICCA_H
#define ACIOTEST_ICCA_H
#include <stdint.h>
#include <stdbool.h>
bool aciotest_icca_handler_init(uint8_t node_id, void** ctx);
bool aciotest_icca_handler_update(uint8_t node_id, void* ctx);
#endif

120
src/main/aciotest/main.c Normal file
View File

@@ -0,0 +1,120 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>
#include "aciodrv/device.h"
#include "aciotest/handler.h"
#include "aciotest/icca.h"
#include "util/log.h"
static uint8_t aciotest_cnt = 0;
/**
* Enumerate supported ACIO nodes based on their product id.
*/
static bool aciotest_assign_handler(char product[4],
struct aciotest_handler_node_handler* handler)
{
if (!memcmp(product, "ICCA", 4)
|| !memcmp(product, "ICCB", 4)
|| !memcmp(product, "ICCC", 4)
){
handler->init = aciotest_icca_handler_init;
handler->update = aciotest_icca_handler_update;
return true;
}
return false;
}
/**
* Tool to test real ACIO hardware.
*/
int main(int argc, char** argv)
{
if (argc < 3) {
printf(
"aciotest, build "__DATE__ " " __TIME__ "\n"
"Usage: %s <com port str> <baud rate>\n"
"Example for two slotted readers: %s COM1 57600\n",
argv[0], argv[0]);
return -1;
}
log_to_writer(log_writer_stdout, NULL);
if (!aciodrv_device_open(argv[1], atoi(argv[2]))) {
printf("Opening acio device failed\n");
return -1;
}
printf("Opening acio device successful\n");
uint8_t node_count = aciodrv_device_get_node_count();
printf("Enumerated %d nodes\n", node_count);
struct aciotest_handler_node_handler handler[aciotest_handler_max];
memset(&handler, 0,
sizeof(struct aciotest_handler_node_handler) * aciotest_handler_max);
for (uint8_t i = 0; i < node_count; i++) {
char product[4];
aciodrv_device_get_node_product_ident(i, product);
printf("> %d: %c%c%c%c\n", i + 1,
product[0], product[1], product[2], product[3]);
if (!aciotest_assign_handler(product, &handler[i])) {
printf("ERROR: Unsupported acio node product %c%c%c%c on node %d\n",
product[0], product[1], product[2], product[3], i);
}
}
for (uint8_t i = 0; i < aciotest_handler_max; i++) {
if (handler[i].init != NULL) {
if (!handler[i].init(i, &handler[i].ctx)) {
printf("ERROR: Initializing node %d failed\n", i);
handler[i].update = NULL;
}
}
}
printf(">>> Initializing done, press enter to start update loop <<<\n");
if (getchar() != '\n') {
return 0;
}
while (true) {
system("cls");
printf("%d\n", aciotest_cnt++);
for (uint8_t i = 0; i < aciotest_handler_max; i++) {
if (handler[i].update != NULL) {
if (!handler[i].update(i, handler[i].ctx)) {
printf("ERROR: Updating node %d, removed from loop\n", i);
handler[i].update = NULL;
Sleep(5000);
}
}
}
/* avoid cpu banging */
Sleep(20);
}
return 0;
}

View File

@@ -0,0 +1,49 @@
#ifndef BEMANITOOLS_SDVXIO_H
#define BEMANITOOLS_SDVXIO_H
/* IO emulation provider for BeatStream */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
enum bst_io_in_gpio_sys_bit {
SDVX_IO_IN_GPIO_SYS_COIN = 2,
SDVX_IO_IN_GPIO_SYS_TEST = 4,
SDVX_IO_IN_GPIO_SYS_SERVICE = 5,
};
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void bst_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your BST IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool bst_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your SDVX IO emulation DLL */
void bst_io_fini(void);
/* Read input state. Returns true if successful. */
bool bst_io_read_input(void);
/* Get state of coin, test, service inputs */
uint8_t bst_io_get_input(void);
// TODO: Lighting
#endif

View File

@@ -0,0 +1,84 @@
#ifndef BEMANITOOLS_DDRIO_H
#define BEMANITOOLS_DDRIO_H
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
enum ddr_pad_bit {
DDR_TEST = 0x04,
DDR_COIN = 0x05,
DDR_SERVICE = 0x06,
DDR_P2_START = 0x08,
DDR_P2_UP = 0x09,
DDR_P2_DOWN = 0x0A,
DDR_P2_LEFT = 0x0B,
DDR_P2_RIGHT = 0x0C,
DDR_P2_MENU_LEFT = 0x0E,
DDR_P2_MENU_RIGHT = 0x0F,
DDR_P2_MENU_UP = 0x02,
DDR_P2_MENU_DOWN = 0x03,
DDR_P1_START = 0x10,
DDR_P1_UP = 0x11,
DDR_P1_DOWN = 0x12,
DDR_P1_LEFT = 0x13,
DDR_P1_RIGHT = 0x14,
DDR_P1_MENU_LEFT = 0x16,
DDR_P1_MENU_RIGHT = 0x17,
DDR_P1_MENU_UP = 0x00,
DDR_P1_MENU_DOWN = 0x01,
};
/* p3io controls menu btn and marquee lights
extio controls neons and stage lights. */
enum p3io_light_bit {
LIGHT_P1_MENU = 0x00,
LIGHT_P2_MENU = 0x01,
LIGHT_P2_LOWER_LAMP = 0x04,
LIGHT_P2_UPPER_LAMP = 0x05,
LIGHT_P1_LOWER_LAMP = 0x06,
LIGHT_P1_UPPER_LAMP = 0x07
};
enum extio_light_bit {
LIGHT_NEONS = 0x0E,
LIGHT_P2_RIGHT = 0x13,
LIGHT_P2_LEFT = 0x14,
LIGHT_P2_DOWN = 0x15,
LIGHT_P2_UP = 0x16,
LIGHT_P1_RIGHT = 0x1B,
LIGHT_P1_LEFT = 0x1C,
LIGHT_P1_DOWN = 0x1D,
LIGHT_P1_UP = 0x1E
};
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void ddr_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your DDR IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool ddr_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
uint32_t ddr_io_read_pad(void);
void ddr_io_set_lights_extio(uint32_t extio_lights);
void ddr_io_set_lights_p3io(uint32_t p3io_lights);
void ddr_io_fini(void);
#endif

View File

@@ -0,0 +1,132 @@
#ifndef BEMANITOOLS_EAM_H
#define BEMANITOOLS_EAM_H
/* Card reader emulator API. You may replace the stock EAMIO.DLL supplied by
Bemanitools with your own custom implementation, which should implement the
interface contract defined in this header file. */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
/* Scan codes for the so-called "10 key" button panel on each card reader. Each
scan code corresponds to a bit position within the 16-bit bitfield that you
return from eam_io_get_keypad_state(). */
enum eam_io_keypad_scan_code {
EAM_IO_KEYPAD_0 = 0,
EAM_IO_KEYPAD_1 = 1,
EAM_IO_KEYPAD_4 = 2,
EAM_IO_KEYPAD_7 = 3,
EAM_IO_KEYPAD_00 = 4,
EAM_IO_KEYPAD_2 = 5,
EAM_IO_KEYPAD_5 = 6,
EAM_IO_KEYPAD_8 = 7,
EAM_IO_KEYPAD_DECIMAL = 8,
EAM_IO_KEYPAD_3 = 9,
EAM_IO_KEYPAD_6 = 10,
EAM_IO_KEYPAD_9 = 11,
EAM_IO_KEYPAD_COUNT = 12, /* Not an actual scan code */
};
/* Emulating the sensors of a slotted card reader. The reader has one
sensor at the front that detects if a card is getting inserted or
if the card is not fully removed. When the back sensor is triggered
the card is locked in the slot and its data is read. */
enum eam_io_sensor_state {
EAM_IO_SENSOR_FRONT = 0,
EAM_IO_SENSOR_BACK = 1,
};
/* Different commands for the (slotted) reader. The game triggers one
of these actions and the card slot as to execute it. When non-slotted
readers are emulated, these states are not used/set. */
enum eam_io_card_slot_cmd {
EAM_IO_CARD_SLOT_CMD_CLOSE = 0,
EAM_IO_CARD_SLOT_CMD_OPEN = 1,
EAM_IO_CARD_SLOT_CMD_EJECT = 2,
EAM_IO_CARD_SLOT_CMD_READ = 3,
};
/* Emulating of the card type for new readers. */
enum eam_io_read_card_result {
EAM_IO_CARD_NONE = 0,
EAM_IO_CARD_ISO15696 = 1,
EAM_IO_CARD_FELICA = 2,
};
/* A private function pointer table returned by the stock EAMIO.DLL
implementation and consumed by config.exe. The contents of this table are
undocumented and subject to change without notice. */
struct eam_io_config_api;
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void eam_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your card reader emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool eam_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your card reader emulation DLL. */
void eam_io_fini(void);
/* Return the state of the number pad on your reader. This function will be
called frequently. See enum eam_io_keypad_scan_code above for the meaning of
each bit within the return value.
This function will be called even if the running game does not actually have
a number pad on the real cabinet (e.g. Jubeat).
unit_no is either 0 or 1. Games with only a single reader (jubeat, popn,
drummania) will only use unit_no 0. */
uint16_t eam_io_get_keypad_state(uint8_t unit_no);
/* Indicate which sensors (front and back) are triggered for a slotted reader
(refer to enum). To emulate non-slotted readers, just set both sensors
to on to indicate the card is in range of the reader. This function
will be called frequently. */
uint8_t eam_io_get_sensor_state(uint8_t unit_no);
/* Read a card ID. This function is only called when the return value of
eam_io_get_sensor_state() changes from false to true, so you may take your
time and perform file I/O etc, within reason. You must return exactly eight
bytes into the buffer pointed to by card_id. */
uint8_t eam_io_read_card(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes);
/* Send a command to the card slot. This is called by the game to execute
certain actions on a slotted reader (refer to enum). When emulating
wave pass readers, this is function is never called. */
bool eam_io_card_slot_cmd(uint8_t unit_no, uint8_t cmd);
/* This function is called frequently. Update your device and states in here */
bool eam_io_poll(uint8_t unit_no);
/* Return a pointer to an internal configuration API for use by config.exe.
Custom implementations should return NULL. */
const struct eam_io_config_api *eam_io_get_config_api(void);
#endif

View File

@@ -0,0 +1,47 @@
#ifndef BEMANITOOLS_GLUE_H
#define BEMANITOOLS_GLUE_H
/* Common definitions for integration bindings */
#include <stdint.h>
#ifdef __GNUC__
/* Bemanitools is compiled with GCC (MinGW, specifically) as of version 5 */
#define LOG_CHECK_FMT __attribute__(( format(printf, 2, 3) ))
#else
/* Compile it out for MSVC plebs */
#define LOG_CHECK_FMT
#endif
/* An AVS-style logger function. Comes in four flavors: misc, info, warning,
and fatal, with increasing severity. Fatal loggers do not return, they
abort the running process after writing their message to the log.
"module" is an arbitrary short string identifying the source of the log
message. The name of the calling DLL is a good default choice for this
string, although you might want to identify a module within your DLL here
instead.
"fmt" is a printf-style format string. Depending on the context in which
your DLL is running you might end up calling a logger function exported
from libavs, which has its own printf implementation (including a number of
proprietary extensions), so don't use any overly exotic formats. */
typedef void (*log_formatter_t)(const char *module, const char *fmt, ...)
LOG_CHECK_FMT;
/* An API for spawning threads. This API is defined by libavs, although
Bemanitools itself may supply compatible implementations of these functions
to your DLL, depending on the context in which it runs.
NOTE: You may only use the logging functions from a thread where Bemanitools
calls you, or a thread that you create using this API. Failure to observe
this restriction will cause the process to crash. This is a limitation of
libavs itself, not Bemanitools. */
typedef int (*thread_create_t)(int (*proc)(void *), void *ctx,
uint32_t stack_sz, unsigned int priority);
typedef void (*thread_join_t)(int thread_id, int *result);
typedef void (*thread_destroy_t)(int thread_id);
#endif

View File

@@ -0,0 +1,155 @@
#ifndef BEMANITOOLS_IIDXIO_H
#define BEMANITOOLS_IIDXIO_H
/* IO emulation provider for beatmania IIDX. */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
/* Bit mapping for the "pad" word */
enum iidx_io_sys_bit {
IIDX_IO_SYS_TEST = 0x00,
IIDX_IO_SYS_SERVICE = 0x01,
IIDX_IO_SYS_COIN = 0x02
};
enum iidx_io_panel_bit {
IIDX_IO_PANEL_P1_START = 0x00,
IIDX_IO_PANEL_P2_START = 0x01,
IIDX_IO_PANEL_VEFX = 0x02,
IIDX_IO_PANEL_EFFECT = 0x03
};
enum iidx_io_key_bit {
IIDX_IO_KEY_P1_1 = 0x00,
IIDX_IO_KEY_P1_2 = 0x01,
IIDX_IO_KEY_P1_3 = 0x02,
IIDX_IO_KEY_P1_4 = 0x03,
IIDX_IO_KEY_P1_5 = 0x04,
IIDX_IO_KEY_P1_6 = 0x05,
IIDX_IO_KEY_P1_7 = 0x06,
IIDX_IO_KEY_P2_1 = 0x07,
IIDX_IO_KEY_P2_2 = 0x08,
IIDX_IO_KEY_P2_3 = 0x09,
IIDX_IO_KEY_P2_4 = 0x0A,
IIDX_IO_KEY_P2_5 = 0x0B,
IIDX_IO_KEY_P2_6 = 0x0C,
IIDX_IO_KEY_P2_7 = 0x0D
};
/* Bit mapping for the P1 and P2 deck lights */
enum iidx_io_deck_light {
IIDX_IO_DECK_LIGHT_P1_1 = 0,
IIDX_IO_DECK_LIGHT_P1_2 = 1,
IIDX_IO_DECK_LIGHT_P1_3 = 2,
IIDX_IO_DECK_LIGHT_P1_4 = 3,
IIDX_IO_DECK_LIGHT_P1_5 = 4,
IIDX_IO_DECK_LIGHT_P1_6 = 5,
IIDX_IO_DECK_LIGHT_P1_7 = 6,
IIDX_IO_DECK_LIGHT_P2_1 = 8,
IIDX_IO_DECK_LIGHT_P2_2 = 9,
IIDX_IO_DECK_LIGHT_P2_3 = 10,
IIDX_IO_DECK_LIGHT_P2_4 = 11,
IIDX_IO_DECK_LIGHT_P2_5 = 12,
IIDX_IO_DECK_LIGHT_P2_6 = 13,
IIDX_IO_DECK_LIGHT_P2_7 = 14,
};
/* Bit mapping for the front panel lights */
enum iidx_io_panel_light {
IIDX_IO_PANEL_LIGHT_P1_START = 0,
IIDX_IO_PANEL_LIGHT_P2_START = 1,
IIDX_IO_PANEL_LIGHT_VEFX = 2,
IIDX_IO_PANEL_LIGHT_EFFECT = 3,
};
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void iidx_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your IIDX IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool iidx_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your IIDX IO emulation DLL */
void iidx_io_fini(void);
/* Set the deck lighting state. See enum iidx_io_deck_light above. */
void iidx_io_ep1_set_deck_lights(uint16_t deck_lights);
/* Set front panel lighting state. See enum iidx_io_panel_light above. */
void iidx_io_ep1_set_panel_lights(uint8_t panel_lights);
/* Set state of the eight halogens above the marquee. */
void iidx_io_ep1_set_top_lamps(uint8_t top_lamps);
/* Switch the top neons on or off. */
void iidx_io_ep1_set_top_neons(bool top_neons);
/* Transmit the lighting state to the lighting controller. This function is
called immediately after all of the other iidx_io_ep1_set_*() functions.
Return false in the event of an IO error. This will lock the game into an
IO error screen. */
bool iidx_io_ep1_send(void);
/* Read input state from the input controller. This function is called
immediately before all of the iidx_io_ep2_get_*() functions.
Return false in the event of an IO error. This will lock the game into an
IO error screen. */
bool iidx_io_ep2_recv(void);
/* Get absolute turntable position, expressed in 1/256ths of a rotation.
player_no is either 0 or 1. */
uint8_t iidx_io_ep2_get_turntable(uint8_t player_no);
/* Get slider position, where 0 is the bottom position and 15 is the topmost
position. slider_no is a number between 0 (leftmost) and 4 (rightmost). */
uint8_t iidx_io_ep2_get_slider(uint8_t slider_no);
/* Get the state of the system buttons. See enums above. */
uint8_t iidx_io_ep2_get_sys(void);
/* Get the state of the panel buttons. See enums above. */
uint8_t iidx_io_ep2_get_panel(void);
/* Get the state of the 14 key buttons. See enums above. */
uint16_t iidx_io_ep2_get_keys(void);
/* Write a nine-character string to the 16-segment display. This happens on a
different schedule to all of the other IO operations, so you should initiate
the communication as soon as this function is called */
bool iidx_io_ep3_write_16seg(const char *text);
#endif

View File

@@ -0,0 +1,87 @@
#ifndef BEMANITOOLS_INPUT_H
#define BEMANITOOLS_INPUT_H
/* Generic input API. This header file defines the public API for geninput.dll.
You may use geninput to supply generic input mapping services for controls
that your custom IO DLLs do not natively provide. For instance, you might
want to make a custom IIDXIO.DLL that interfaces with your own 16-segment
LCD marquee device while still using the stock IIDXIO.DLL input and lighting
code, which uses the generic services provided by geninput.dll.
All other exports from geninput.dll are undocumented and subject to change
without notice. */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
/* Supply logging functions to geninput. You should pass on the logging
functions that are supplied to your own custom DLLs.
This is the only function that can safely be called before input_init(). */
void input_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize the generic input subsystem. You must pass on the thread
management functions that have been supplied to your DLL.
Calling any geninput functions other than input_set_loggers() before calling
input_init() will probably crash the running process.
You will also need to call mapper_config_load() with the appropriate
game_type parameter, otherwise you will not receive any input, and any
attempts to set a light output level will have no effect. */
void input_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down the generic input subsystem. After calling this function, no
geninput functions other than input_set_loggers() or input_init() may be
called. */
void input_fini(void);
/* Load input mappings for a particular game, as configured from config.exe.
Currently recognized game types are:
ddr: Dance Dance Revolution
dm: Drum Mania
gf: Guitar Freaks
iidx: beatmania IIDX
pnm: pop'n music
sdvx: Sound Voltex
ju: jubeat
Returns true if a suitable config file was found and successfully loaded. */
bool mapper_config_load(const char *game_type);
/* Return the absolute position of an analog spinner, expressed in 1/256ths of
a complete rotation. */
uint8_t mapper_read_analog(uint8_t analog);
/* Map the current state of all attached input devices to a 64-bit bit field.
The exact layout of this bit field varies between game types, although we
try to approximate the contents of each emulated IO PCB's own state packet
as closely as is reasonably practical. */
uint64_t mapper_update(void);
/* Set the intensity of any light on a controller corresponding to a particular
software-controlled light on an arcade cabinet, where 0 is off and 255 is
full intensity. Consult the header files for the light identifiers used for
each game type. The mappings between these light identifiers and the actual
lights on the user's controller (if any) are configured by the user by means
of the config.exe program.
Note that any calls to this function do not take effect until the next call
to mapper_update(). */
void mapper_write_light(uint8_t light, uint8_t intensity);
#endif

View File

@@ -0,0 +1,83 @@
#ifndef BEMANITOOLS_JBIO_H
#define BEMANITOOLS_JBIO_H
/* IO emulation provider for jubeat. */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
/* input bit mappings. Panels on the controller are
panel 1 top left corner down to panel 16 bottom right corner */
enum jb_io_panel_bit {
JB_IO_PANEL_01 = 0x00,
JB_IO_PANEL_02 = 0x01,
JB_IO_PANEL_03 = 0x02,
JB_IO_PANEL_04 = 0x03,
JB_IO_PANEL_05 = 0x04,
JB_IO_PANEL_06 = 0x05,
JB_IO_PANEL_07 = 0x06,
JB_IO_PANEL_08 = 0x07,
JB_IO_PANEL_09 = 0x08,
JB_IO_PANEL_10 = 0x09,
JB_IO_PANEL_11 = 0x0A,
JB_IO_PANEL_12 = 0x0B,
JB_IO_PANEL_13 = 0x0C,
JB_IO_PANEL_14 = 0x0D,
JB_IO_PANEL_15 = 0x0E,
JB_IO_PANEL_16 = 0x0F,
};
/* Bit mappings for "system" inputs */
enum jb_io_sys_bit {
JB_IO_SYS_TEST = 0x00,
JB_IO_SYS_SERVICE = 0x01,
JB_IO_SYS_COIN = 0x02,
};
/* RGB led units to address */
enum jb_io_rgb_led {
JB_IO_RGB_LED_FRONT = 0,
JB_IO_RGB_LED_TOP = 1,
JB_IO_RGB_LED_LEFT = 2,
JB_IO_RGB_LED_RIGHT = 3,
JB_IO_RGB_LED_TITLE = 4,
JB_IO_RGB_LED_WOOFER = 5
};
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void jb_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your JB IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
jb_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool jb_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your JB IO emulation DLL */
void jb_io_fini(void);
/* TODO doc */
bool jb_io_read_inputs(void);
bool jb_io_write_outputs(void);
uint8_t jb_io_get_sys_inputs(void);
uint16_t jb_io_get_panel_inputs(void);
void jb_io_set_rgb_led(enum jb_io_rgb_led unit, uint8_t r, uint8_t g, uint8_t b);
#endif

View File

@@ -0,0 +1,95 @@
#ifndef BEMANITOOLS_SDVXIO_H
#define BEMANITOOLS_SDVXIO_H
/* IO emulation provider for SOUND VOLTEX */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
enum sdvx_io_in_gpio_sys_bit {
SDVX_IO_IN_GPIO_SYS_COIN = 2,
SDVX_IO_IN_GPIO_SYS_TEST = 4,
SDVX_IO_IN_GPIO_SYS_SERVICE = 5,
};
enum sdvx_io_in_gpio_0_bit {
SDVX_IO_IN_GPIO_0_C = 0,
SDVX_IO_IN_GPIO_0_B = 1,
SDVX_IO_IN_GPIO_0_A = 2,
SDVX_IO_IN_GPIO_0_START = 3,
SDVX_IO_IN_GPIO_0_HEADPHONE = 4,
};
enum sdvx_io_in_gpio_1_bit {
SDVX_IO_IN_GPIO_1_FX_R = 3,
SDVX_IO_IN_GPIO_1_FX_L = 4,
SDVX_IO_IN_GPIO_1_D = 5,
};
enum sdvx_io_out_gpio_bit {
SDVX_IO_OUT_GPIO_D = 0,
SDVX_IO_OUT_GPIO_FX_L = 1,
SDVX_IO_OUT_GPIO_FX_R = 2,
SDVX_IO_OUT_GPIO_START = 12,
SDVX_IO_OUT_GPIO_A = 13,
SDVX_IO_OUT_GPIO_B = 14,
SDVX_IO_OUT_GPIO_C = 15,
};
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void sdvx_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your SDVX IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool sdvx_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your SDVX IO emulation DLL */
void sdvx_io_fini(void);
/* Set state of the GPIO (on/off) lights (see bit definitions above) */
void sdvx_io_set_gpio_lights(uint32_t gpio_lights);
/* Set state of a PWM (dimmable) light channel. These come in groups of three
(red, green, blue). There are a six group of three PWM channels, for a
total of 18 channels (0 through 17). */
void sdvx_io_set_pwm_light(uint8_t light_no, uint8_t intensity);
/* Transmit the light state to the IOPCB */
bool sdvx_io_write_output(void);
/* Read input state */
bool sdvx_io_read_input(void);
/* Get state of coin, test, service inputs */
uint8_t sdvx_io_get_input_gpio_sys(void);
/* Get gameplay button state. Parameter selects GPIO bank 0 or 1. See bit
definitions above for details. */
uint16_t sdvx_io_get_input_gpio(uint8_t gpio_bank);
/* Get a 10-bit (!) spinner position, where spinner_no is 0 or 1.
High six bits are ignored. */
uint16_t sdvx_io_get_spinner_pos(uint8_t spinner_no);
#endif

View File

@@ -0,0 +1,56 @@
#ifndef BEMANITOOLS_VEFXIO_H
#define BEMANITOOLS_VEFXIO_H
/* IO emulation provider for beatmania IIDX Effector Panel. */
#include <stdbool.h>
#include <stdint.h>
#include "bemanitools/glue.h"
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
void vefx_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
/* Initialize your IIDX IO emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
bool vefx_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
/* Shut down your IIDX IO emulation DLL */
void vefx_io_fini(void);
/* Read input state from the input controller. This function is called
immediately before the vefx_io_get_slider() function.
Return false in the event of an IO error. This will lock the game into an
IO error screen.
If making a custom driver, ppad can be used to update regular IO if needed
See iidxio.c for mappings. */
bool vefx_io_recv(uint64_t* ppad);
/* Get slider position, where 0 is the bottom position and 15 is the topmost
position. slider_no is a number between 0 (leftmost) and 4 (rightmost). */
uint8_t vefx_io_get_slider(uint8_t slider_no);
/* Write a nine-character string to the 16-segment display. This happens on a
different schedule to all of the other IO operations, so you should initiate
the communication as soon as this function is called */
bool vefx_io_write_16seg(const char *text);
#endif

View File

@@ -0,0 +1,20 @@
avsdlls += bsthook
deplibs_bsthook := \
avs \
libs_bsthook := \
acioemu \
bstio \
hook \
hooklib \
util \
eamio \
src_bsthook := \
acio.c \
dllmain.c \
gfx.c \
kfca.c \
settings.c \

93
src/main/bsthook/acio.c Normal file
View File

@@ -0,0 +1,93 @@
#include <windows.h>
#include <ntdef.h>
#include <devioctl.h>
#include <ntddser.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include "acioemu/addr.h"
#include "acioemu/emu.h"
#include "acioemu/icca.h"
#include "bsthook/acio.h"
#include "bsthook/kfca.h"
#include "hook/iohook.h"
#include "imports/avs.h"
#include "util/defs.h"
#include "util/iobuf.h"
#include "util/log.h"
#include "util/str.h"
static struct ac_io_emu ac_io_emu;
static struct ac_io_emu_icca ac_io_emu_icca;
void ac_io_bus_init(void)
{
ac_io_emu_init(&ac_io_emu, L"COM2");
ac_io_emu_icca_init(&ac_io_emu_icca, &ac_io_emu, 0);
kfca_init(&ac_io_emu);
}
void ac_io_bus_fini(void)
{
ac_io_emu_fini(&ac_io_emu);
}
HRESULT ac_io_bus_dispatch_irp(struct irp *irp)
{
const struct ac_io_message *msg;
HRESULT hr;
log_assert(irp != NULL);
if (!ac_io_emu_match_irp(&ac_io_emu, irp)) {
return irp_invoke_next(irp);
}
for (;;) {
hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp);
if (hr != S_OK) {
return hr;
}
msg = ac_io_emu_request_peek(&ac_io_emu);
switch (msg->addr) {
case 0:
ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2);
break;
case 1:
ac_io_emu_icca_dispatch_request(&ac_io_emu_icca, msg);
break;
case 2:
kfca_dispatch_request(msg);
break;
case AC_IO_BROADCAST:
log_warning("Broadcast(?) message on BST ACIO bus?");
break;
default:
log_warning("ACIO message on unhandled bus address: %d",
msg->addr);
break;
}
ac_io_emu_request_pop(&ac_io_emu);
}
}

12
src/main/bsthook/acio.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef IIDXHOOK_AC_IO_H
#define IIDXHOOK_AC_IO_H
#include <windows.h>
#include "hook/iohook.h"
void ac_io_bus_init(void);
void ac_io_bus_fini(void);
HRESULT ac_io_bus_dispatch_irp(struct irp *irp);
#endif

View File

@@ -0,0 +1,4 @@
LIBRARY bsthook
EXPORTS
DllMain@12 @1 NONAME

136
src/main/bsthook/dllmain.c Normal file
View File

@@ -0,0 +1,136 @@
#include <windows.h>
#include <stdbool.h>
#include "bemanitools/bstio.h"
#include "bemanitools/eamio.h"
#include "hook/iohook.h"
#include "hooklib/app.h"
#include "hooklib/rs232.h"
#include "imports/avs.h"
#include "bsthook/acio.h"
#include "bsthook/gfx.h"
#include "bsthook/settings.h"
#include "util/cmdline.h"
#include "util/defs.h"
#include "util/log.h"
static const irp_handler_t bsthook_handlers[] = {
ac_io_bus_dispatch_irp,
};
static bool my_dll_entry_init(char *sidcode, struct property_node *config);
static bool my_dll_entry_main(void);
static bool my_dll_entry_init(char *sidcode, struct property_node *config)
{
bool ok;
log_info("--- Begin bsthook dll_entry_init ---");
ac_io_bus_init();
log_info("Starting up BeatStream IO backend");
bst_io_set_loggers(
log_body_misc,
log_body_info,
log_body_warning,
log_body_fatal);
ok = bst_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy);
if (!ok) {
goto bst_io_fail;
}
eam_io_set_loggers(
log_body_misc,
log_body_info,
log_body_warning,
log_body_fatal);
ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy);
if (!ok) {
goto eam_io_fail;
}
log_info("--- End bsthook dll_entry_init ---");
return app_hook_invoke_init(sidcode, config);
eam_io_fail:
bst_io_fini();
bst_io_fail:
ac_io_bus_fini();
return false;
}
static bool my_dll_entry_main(void)
{
bool result;
result = app_hook_invoke_main();
log_info("Shutting down card reader backend");
eam_io_fini();
log_info("Shutting down SDVX IO backend");
bst_io_fini();
ac_io_bus_fini();
return result;
}
BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx)
{
int i;
int argc;
char **argv;
if (reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
log_to_external(
log_body_misc,
log_body_info,
log_body_warning,
log_body_fatal);
args_recover(&argc, &argv);
for (i = 1 ; i < argc ; i++) {
if (argv[i][0] != '-') {
continue;
}
switch (argv[i][1]) {
case 'w':
gfx_set_windowed();
break;
}
}
args_free(argc, argv);
app_hook_init(my_dll_entry_init, my_dll_entry_main);
iohook_init(bsthook_handlers, lengthof(bsthook_handlers));
rs232_hook_init();
gfx_init();
settings_hook_init();
return TRUE;
}

83
src/main/bsthook/gfx.c Normal file
View File

@@ -0,0 +1,83 @@
#include <windows.h>
#include <d3d9.h>
#include <stdbool.h>
#include "hook/com-proxy.h"
#include "hook/pe.h"
#include "hook/table.h"
#include "sdvxhook/gfx.h"
#include "util/defs.h"
#include "util/log.h"
static HRESULT STDCALL my_CreateDevice(
IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags,
D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev);
static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver);
static IDirect3D9 * (STDCALL *real_Direct3DCreate9)(UINT sdk_ver);
static const struct hook_symbol gfx_hook_syms[] = {
{
.name = "Direct3DCreate9",
.patch = my_Direct3DCreate9,
.link = (void **) &real_Direct3DCreate9
},
};
static bool gfx_windowed;
static HRESULT STDCALL my_CreateDevice(
IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags,
D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev)
{
IDirect3D9 *real = COM_PROXY_UNWRAP(self);
HRESULT hr;
log_misc("IDirect3D9::CreateDevice hook hit");
if (gfx_windowed) {
pp->Windowed = TRUE;
pp->FullScreen_RefreshRateInHz = 0;
}
hr = IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev);
return hr;
}
static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver)
{
IDirect3D9 *api;
IDirect3D9Vtbl *api_vtbl;
struct com_proxy *api_proxy;
log_info("Direct3DCreate9 hook hit");
api = real_Direct3DCreate9(sdk_ver);
api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl));
api_vtbl = api_proxy->vptr;
api_vtbl->CreateDevice = my_CreateDevice;
return (IDirect3D9 *) api_proxy;
}
void gfx_init(void)
{
hook_table_apply(
NULL,
"d3d9.dll",
gfx_hook_syms,
lengthof(gfx_hook_syms));
log_info("Inserted graphics hooks");
}
void gfx_set_windowed(void)
{
gfx_windowed = true;
}

7
src/main/bsthook/gfx.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef BSTHOOK_GFX_H
#define BSTHOOK_GFX_H
void gfx_init(void);
void gfx_set_windowed(void);
#endif

136
src/main/bsthook/kfca.c Normal file
View File

@@ -0,0 +1,136 @@
#include <windows.h>
#include <stdint.h>
#include <string.h>
#include "acio/acio.h"
#include "acioemu/emu.h"
#include "bemanitools/bstio.h"
#include "util/defs.h"
static void kfca_send_version(const struct ac_io_message *req);
static void kfca_report_status(const struct ac_io_message *req, uint8_t status);
static void kfca_report_nil(const struct ac_io_message *req);
static void kfca_poll(const struct ac_io_message *req);
static struct ac_io_emu *kfca_ac_io_emu;
void kfca_init(struct ac_io_emu *emu)
{
kfca_ac_io_emu = emu;
}
void kfca_dispatch_request(const struct ac_io_message *req)
{
uint16_t cmd_code;
cmd_code = ac_io_u16(req->cmd.code);
switch (cmd_code) {
case AC_IO_CMD_GET_VERSION:
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
kfca_send_version(req);
break;
case AC_IO_CMD_START_UP:
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
kfca_report_status(req, 0x00);
break;
case AC_IO_CMD_KFCA_POLL:
kfca_poll(req);
break;
case AC_IO_CMD_KFCA_UNK_0120:
log_misc("AC_IO_CMD_KFCA_UNK_%04X(%d)", cmd_code, req->addr);
kfca_report_status(req, 0x00);
break;
case AC_IO_CMD_KFCA_UNK_0128:
log_misc("AC_IO_CMD_KFCA_UNK_%04X(%d)", cmd_code, req->addr);
kfca_report_nil(req);
break;
default:
log_warning("Unknown ACIO message %04x on KFCA mode, addr=%d",
cmd_code, req->addr);
break;
}
}
static void kfca_send_version(const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.version);
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_KFCA);
resp.cmd.version.flag = 0x00;
resp.cmd.version.major = 0x01;
resp.cmd.version.minor = 0x01;
resp.cmd.version.revision = 0x00;
memcpy(resp.cmd.version.product_code, "KFCA",
sizeof(resp.cmd.version.product_code));
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0);
}
static void kfca_report_status(const struct ac_io_message *req, uint8_t status)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(resp.cmd.status);
resp.cmd.status = status;
ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0);
}
static void kfca_report_nil(const struct ac_io_message *req)
{
struct ac_io_message resp;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = 0;
ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0);
}
static void kfca_poll(const struct ac_io_message *req)
{
struct ac_io_message resp;
struct ac_io_kfca_poll_in *pin;
bst_io_read_input();
pin = &resp.cmd.kfca_poll_in;
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
resp.cmd.code = req->cmd.code;
resp.cmd.seq_no = req->cmd.seq_no;
resp.cmd.nbytes = sizeof(*pin);
memset(pin, 0, sizeof(*pin));
pin->gpio_sys = ac_io_u16(bst_io_get_input());
ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0);
}

11
src/main/bsthook/kfca.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef BSTHOOK_KFCA_H
#define BSTHOOK_KFCA_H
#include "acio/acio.h"
#include "acioemu/emu.h"
void kfca_init(struct ac_io_emu *in);
void kfca_dispatch_request(const struct ac_io_message *req);
#endif

View File

@@ -0,0 +1,91 @@
#define LOG_MODULE "settings-hook"
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "hook/table.h"
#include "util/defs.h"
#include "util/log.h"
#include "util/str.h"
/* ------------------------------------------------------------------------- */
static HANDLE STDCALL my_CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
static HANDLE (STDCALL *real_CreateFileA)(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
/* ------------------------------------------------------------------------- */
static const struct hook_symbol settings_hook_syms[] = {
{
.name = "CreateFileA",
.patch = my_CreateFileA,
.link = (void **) &real_CreateFileA
},
};
/* ------------------------------------------------------------------------- */
static HANDLE STDCALL my_CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile)
{
if ( lpFileName != NULL &&
lpFileName[0] == 'e' &&
lpFileName[1] == ':') {
HANDLE handle;
char new_path[MAX_PATH];
strcpy(new_path, lpFileName);
new_path[1] = '\\';
log_misc("Remapped settings path %s", new_path);
handle = real_CreateFileA(new_path, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
return handle;
}
return real_CreateFileA(lpFileName, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
}
/* ------------------------------------------------------------------------- */
void settings_hook_init(void)
{
hook_table_apply(
NULL,
"kernel32.dll",
settings_hook_syms,
lengthof(settings_hook_syms));
log_info("Inserted settings hooks");
}

View File

@@ -0,0 +1,10 @@
#ifndef BSTHOOK_SETTINGS_H
#define BSTHOOK_SETTINGS_H
/**
* Remaps the paths for the settings drive e:\
* to the local folder e\.
*/
void settings_hook_init(void);
#endif

8
src/main/bstio/Module.mk Normal file
View File

@@ -0,0 +1,8 @@
dlls += bstio
libs_bstio := \
geninput \
src_bstio := \
bstio.c \

42
src/main/bstio/bstio.c Normal file
View File

@@ -0,0 +1,42 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "bemanitools/bstio.h"
#include "bemanitools/glue.h"
#include "bemanitools/input.h"
static uint8_t bst_io_gpio_sys;
void bst_io_set_loggers(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal)
{
input_set_loggers(misc, info, warning, fatal);
}
bool bst_io_init(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy)
{
input_init(thread_create, thread_join, thread_destroy);
mapper_config_load("bst");
return true;
}
void bst_io_fini(void)
{
input_fini();
}
bool bst_io_read_input(void)
{
bst_io_gpio_sys = mapper_update();
return true;
}
uint8_t bst_io_get_input(void)
{
return bst_io_gpio_sys;
}

8
src/main/bstio/bstio.def Normal file
View File

@@ -0,0 +1,8 @@
LIBRARY bstio
EXPORTS
bst_io_fini
bst_io_get_input
bst_io_init
bst_io_read_input
bst_io_set_loggers

View File

@@ -0,0 +1,11 @@
libs += cconfig
libs_cconfig := \
util \
src_cconfig := \
cconfig-hook.c \
cconfig-util.c \
cconfig.c \
cmd.c \
conf.c \

View File

@@ -0,0 +1,96 @@
#include <string.h>
#include "cconfig/cconfig-util.h"
#include "cconfig/cmd.h"
#include "cconfig/conf.h"
#include "cconfig/cconfig-hook.h"
#include "util/cmdline.h"
#include "util/log.h"
bool cconfig_hook_config_init(struct cconfig* config, const char* usage_header,
enum cconfig_cmd_usage_out cmd_usage_out)
{
bool success;
int argc;
char **argv;
enum cconfig_conf_error conf_error;
char* config_path;
success = true;
args_recover(&argc, &argv);
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
goto failure_usage;
}
}
config_path = NULL;
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], "--config")) {
if (i + 1 >= argc) {
log_fatal("--config parameter not followed by a config file "
"path param");
goto failure;
}
config_path = argv[i + 1];
break;
}
}
if (config_path) {
log_misc("Loading config file: %s", config_path);
conf_error = cconfig_conf_load_from_file(config, config_path, false);
if (conf_error == CCONFIG_CONF_ERROR_NO_SUCH_FILE) {
/* Create default config */
if (cconfig_conf_save_to_file(config, config_path) !=
CCONFIG_CONF_SUCCESS) {
log_fatal("Creating default config file '%s' failed",
config_path);
goto failure;
} else {
log_info("Default configuration '%s' created. Restart "
"application", config_path);
goto failure;
}
} else if (conf_error != CCONFIG_CONF_SUCCESS) {
log_fatal("Error loading config file '%s': %d", config_path,
conf_error);
goto failure;
}
log_misc("Config state after file loading:");
cconfig_util_log(config, log_impl_misc);
}
log_misc("Parsing override config parameters from cmd");
/* Override defaults or values loaded from file with values from cmd */
if (!cconfig_cmd_parse(config, "-p", argc, argv, false)) {
log_fatal("Error parsing cmd args for config values");
goto failure_usage;
}
log_misc("Config state after cmd parameter overrides:");
cconfig_util_log(config, log_impl_misc);
goto success;
failure_usage:
cconfig_cmd_print_usage(config, usage_header, cmd_usage_out);
failure:
success = false;
success:
args_free(argc, argv);
return success;
}

View File

@@ -0,0 +1,10 @@
#ifndef CCONFIG_HOOK_H
#define CCONFIG_HOOK_H
#include "cconfig/cconfig.h"
#include "cconfig/cmd.h"
bool cconfig_hook_config_init(struct cconfig* config, const char* usage_header,
enum cconfig_cmd_usage_out cmd_usage_out);
#endif

View File

@@ -0,0 +1,211 @@
#define LOG_MODULE "cconfig-util"
#include <stdio.h>
#include <string.h>
#include "cconfig/cconfig-util.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/mem.h"
bool cconfig_util_get_int(struct cconfig* config, const char* key, int32_t* ret,
int32_t default_value)
{
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
entry = cconfig_get(config, key);
if (entry) {
if (sscanf(entry->value, "%d", ret) == 1) {
return true;
}
}
*ret = default_value;
return false;
}
bool cconfig_util_get_float(struct cconfig* config, const char* key, float* ret,
float default_value)
{
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
entry = cconfig_get(config, key);
if (entry) {
if (sscanf(entry->value, "%f", ret) == 1) {
return true;
}
}
*ret = default_value;
return false;
}
bool cconfig_util_get_bool(struct cconfig* config, const char* key, bool* ret,
bool default_value)
{
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
entry = cconfig_get(config, key);
if (entry) {
if (!strcmp(entry->value, "true")) {
*ret = true;
return true;
} else if (!strcmp(entry->value, "false")) {
*ret = false;
return true;
}
}
*ret = default_value;
return false;
}
bool cconfig_util_get_str(struct cconfig* config, const char* key,
char* buffer, size_t len, const char* default_value)
{
struct cconfig_entry* entry;
size_t str_len;
log_assert(config);
log_assert(key);
entry = cconfig_get(config, key);
if (entry) {
str_len = strlen(entry->value);
if (str_len <= len) {
strcpy(buffer, entry->value);
return true;
}
}
strcpy(buffer, default_value);
return false;
}
bool cconfig_util_get_data(struct cconfig* config, const char* key,
uint8_t* buffer, size_t len, const uint8_t* default_value)
{
size_t res_len;
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
log_assert(len);
entry = cconfig_get(config, key);
if (entry) {
res_len = strlen(entry->value);
res_len = res_len / 2 + res_len % 2;
if (len <= res_len) {
if (hex_decode(buffer, len, entry->value, strlen(entry->value))) {
return true;
}
}
}
memcpy(buffer, default_value, len);
return false;
}
void cconfig_util_set_int(struct cconfig* config, const char* key,
int32_t value, const char* desc)
{
char* str;
size_t str_len;
log_assert(config);
log_assert(key);
log_assert(desc);
str_len = snprintf(NULL, 0, "%d", value) + 1;
str = xmalloc(str_len);
snprintf(str, str_len, "%d", value);
cconfig_set(config, key, str, desc);
free(str);
}
void cconfig_util_set_float(struct cconfig* config, const char* key,
float value, const char* desc)
{
char* str;
size_t str_len;
log_assert(config);
log_assert(key);
log_assert(desc);
str_len = snprintf(NULL, 0, "%f", value) + 1;
str = xmalloc(str_len);
snprintf(str, str_len, "%f", value);
cconfig_set(config, key, str, desc);
free(str);
}
void cconfig_util_set_bool(struct cconfig* config, const char* key, bool value,
const char* desc)
{
log_assert(config);
log_assert(key);
log_assert(desc);
cconfig_set(config, key, value ? "true" : "false", desc);
}
void cconfig_util_set_str(struct cconfig* config, const char* key,
const char* value, const char* desc)
{
log_assert(config);
log_assert(key);
log_assert(desc);
log_assert(value);
cconfig_set(config, key, value, desc);
}
void cconfig_util_set_data(struct cconfig* config, const char* key,
const uint8_t* value, size_t len, const char* desc)
{
char* str;
size_t str_len;
log_assert(config);
log_assert(key);
log_assert(desc);
str_len = len * 2 + 1;
str = xmalloc(str_len);
hex_encode_uc(value, len, str, str_len);
cconfig_set(config, key, str, desc);
free(str);
}
void cconfig_util_log(struct cconfig* config, log_formatter_t log_formatter)
{
for (uint32_t i = 0; i < config->nentries; i++) {
log_formatter(LOG_MODULE, "%s=%s", config->entries[i].key,
config->entries[i].value);
}
}

View File

@@ -0,0 +1,44 @@
#ifndef CCONFIG_UTIL_H
#define CCONFIG_UTIL_H
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "cconfig/cconfig.h"
#include "util/log.h"
bool cconfig_util_get_int(struct cconfig* config, const char* key, int32_t* ret,
int32_t default_value);
bool cconfig_util_get_float(struct cconfig* config, const char* key, float* ret,
float default_value);
bool cconfig_util_get_bool(struct cconfig* config, const char* key, bool* ret,
bool default_value);
bool cconfig_util_get_str(struct cconfig* config, const char* key,
char* buffer, size_t len, const char* default_value);
bool cconfig_util_get_data(struct cconfig* config, const char* key,
uint8_t* buffer, size_t len, const uint8_t* default_value);
void cconfig_util_set_int(struct cconfig* config, const char* key,
int32_t value, const char* desc);
void cconfig_util_set_float(struct cconfig* config, const char* key,
float value, const char* desc);
void cconfig_util_set_bool(struct cconfig* config, const char* key, bool value,
const char* desc);
void cconfig_util_set_str(struct cconfig* config, const char* key,
const char* value, const char* desc);
void cconfig_util_set_data(struct cconfig* config, const char* key,
const uint8_t* value, size_t len, const char* desc);
void cconfig_util_log(struct cconfig* config, log_formatter_t log_formatter);
#endif

109
src/main/cconfig/cconfig.c Normal file
View File

@@ -0,0 +1,109 @@
#include <string.h>
#include "cconfig/cconfig.h"
#include "util/log.h"
#include "util/mem.h"
#include "util/str.h"
static struct cconfig_entry* cconfig_extend_config(struct cconfig* config)
{
config->nentries++;
config->entries = xrealloc(config->entries,
config->nentries * sizeof(struct cconfig_entry));
memset(&config->entries[config->nentries - 1], 0,
sizeof(struct cconfig_entry));
return &config->entries[config->nentries - 1];
}
struct cconfig* cconfig_init()
{
struct cconfig* config;
config = xmalloc(sizeof(struct cconfig));
memset(config, 0, sizeof(struct cconfig));
return config;
}
struct cconfig_entry* cconfig_get(struct cconfig* config, const char* key)
{
log_assert(config);
log_assert(key);
for (uint32_t i = 0; i < config->nentries; i++) {
if (!strcmp(config->entries[i].key, key)) {
return &config->entries[i];
}
}
return NULL;
}
void cconfig_set(struct cconfig* config, const char* key, const char* value,
const char* desc)
{
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
log_assert(value);
log_assert(desc);
entry = cconfig_get(config, key);
if (!entry) {
entry = cconfig_extend_config(config);
} else {
free(entry->key);
free(entry->value);
free(entry->desc);
memset(entry, 0, sizeof(struct cconfig_entry));
}
entry->key = str_dup(key);
entry->desc = str_dup(desc);
entry->value = str_dup(value);
}
void cconfig_set2(struct cconfig* config, const char* key, const char* value)
{
struct cconfig_entry* entry;
log_assert(config);
log_assert(key);
log_assert(value);
entry = cconfig_get(config, key);
if (!entry) {
entry = cconfig_extend_config(config);
} else {
free(entry->key);
free(entry->value);
}
entry->key = str_dup(key);
entry->value = str_dup(value);
/* Description optional, but do not wipe previous description if
available */
if (!entry->desc) {
entry->desc = "";
}
}
void cconfig_finit(struct cconfig* config)
{
for (uint32_t i = 0; i < config->nentries; i++) {
free(config->entries[i].key);
free(config->entries[i].value);
free(config->entries[i].desc);
}
free(config->entries);
free(config);
}

View File

@@ -0,0 +1,31 @@
#ifndef CCONFIG_H
#define CCONFIG_H
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
struct cconfig_entry {
char* key;
char* value;
char* desc;
};
struct cconfig {
uint32_t nentries;
struct cconfig_entry* entries;
};
struct cconfig* cconfig_init();
struct cconfig_entry* cconfig_get(struct cconfig* config,
const char* key);
void cconfig_set(struct cconfig* config, const char* key, const char* value,
const char* desc);
void cconfig_set2(struct cconfig* config, const char* key, const char* value);
void cconfig_finit(struct cconfig* config);
#endif

132
src/main/cconfig/cmd.c Normal file
View File

@@ -0,0 +1,132 @@
#define LOG_MODULE "cconfig-cmd"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "cconfig/cmd.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/str.h"
static void cconfig_cmd_usage_print(enum cconfig_cmd_usage_out output,
const char* fmt, ...)
{
char buffer[32768];
va_list ap;
va_start(ap, fmt);
switch (output) {
case CCONFIG_CMD_USAGE_OUT_STDOUT:
vfprintf(stdout, fmt, ap);
break;
case CCONFIG_CMD_USAGE_OUT_STDERR:
vfprintf(stderr, fmt, ap);
break;
case CCONFIG_CMD_USAGE_OUT_DBG:
_vsnprintf(buffer, sizeof(buffer), fmt, ap);
OutputDebugString(buffer);
break;
case CCONFIG_CMD_USAGE_OUT_LOG:
_vsnprintf(buffer, sizeof(buffer), fmt, ap);
log_info("%s", buffer);
break;
default:
log_assert(false);
break;
}
va_end(ap);
}
bool cconfig_cmd_parse(struct cconfig* config, const char* key_ident, int argc,
char** argv, bool add_params_if_absent)
{
bool no_error;
struct cconfig_entry* entry;
char* tmp;
char* cur_tok;
int ntok;
char* toks[2];
no_error = true;
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], key_ident)) {
if (i + 1 >= argc) {
no_error = false;
break;
}
/* Not another key ident is following */
if (!strcmp(argv[i + 1], key_ident)) {
no_error = false;
break;
}
++i;
tmp = str_dup(argv[i]);
ntok = 0;
cur_tok = strtok(tmp, "=");
while (cur_tok != NULL) {
toks[ntok] = cur_tok;
ntok++;
cur_tok = strtok(NULL, "=");
if (ntok == 2) {
break;
}
}
if (ntok != 2) {
/* Tokenizing key=value parameter error */
log_warning("Parsing parameter '%s' failed, ignore", argv[i]);
free(tmp);
no_error = false;
continue;
}
log_misc("Key: %s, Value: %s", toks[0], toks[1]);
entry = cconfig_get(config, toks[0]);
if (entry || add_params_if_absent) {
cconfig_set2(config, toks[0], toks[1]);
} else {
/* Ignore cmd params that are not found in config */
log_warning("Could not find cmd parameter with key '%s' in "
"config, ignored", toks[0]);
}
free(tmp);
}
}
return no_error;
}
void cconfig_cmd_print_usage(struct cconfig* config, const char* usage_header,
enum cconfig_cmd_usage_out output)
{
cconfig_cmd_usage_print(output, "%s\n", usage_header);
for (uint32_t i = 0; i < config->nentries; i++) {
cconfig_cmd_usage_print(output,
" %s: %s\n"
" default: %s\n",
config->entries[i].key,
config->entries[i].desc,
config->entries[i].value);
}
}

21
src/main/cconfig/cmd.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef CCONFIG_CMD_H
#define CCONFIG_CMD_H
#include <stdbool.h>
#include "cconfig/cconfig.h"
enum cconfig_cmd_usage_out {
CCONFIG_CMD_USAGE_OUT_STDOUT,
CCONFIG_CMD_USAGE_OUT_STDERR,
CCONFIG_CMD_USAGE_OUT_DBG,
CCONFIG_CMD_USAGE_OUT_LOG,
};
bool cconfig_cmd_parse(struct cconfig* config, const char* key_ident, int argc,
char** argv, bool add_params_if_absent);
void cconfig_cmd_print_usage(struct cconfig* config, const char* usage_header,
enum cconfig_cmd_usage_out output);
#endif

117
src/main/cconfig/conf.c Normal file
View File

@@ -0,0 +1,117 @@
#define LOG_MODULE "cconfig-conf"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cconfig/conf.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/str.h"
enum cconfig_conf_error cconfig_conf_load_from_file(struct cconfig* config,
const char* path, bool add_params_if_absent)
{
char* pos_lines;
char* pos_key_val;
char* ctx_lines;
char* ctx_key_val;
char* data;
size_t len;
if (!file_load(path, (void**) &data, &len, true)) {
/* If file does not exist, create one with default configuration
values */
if (path_exists(path)) {
return CCONFIG_CONF_ERROR_FILE_CORRUPTED;
} else {
return CCONFIG_CONF_ERROR_NO_SUCH_FILE;
}
}
pos_lines = strtok_r(data, "\n", &ctx_lines);
while (pos_lines != NULL) {
char* pos_line_dup;
char* key = NULL;
char* val = NULL;
int cnt = 0;
struct cconfig_entry* entry;
/* ignore comments and empty lines */
if (strlen(pos_lines) > 0 && pos_lines[0] != '#') {
pos_line_dup = str_dup(pos_lines);
pos_key_val = strtok_r(pos_line_dup, "=", &ctx_key_val);
log_misc("Line: %s", pos_lines);
while (pos_key_val != NULL) {
if (cnt == 0) {
key = pos_key_val;
} else if (cnt == 1) {
val = pos_key_val;
}
pos_key_val = strtok_r(NULL, "=", &ctx_key_val);
cnt++;
}
/* Key requiured, value can be NULL */
if (cnt != 1 && cnt != 2) {
log_warning("Invalid options line %s in options file %s",
pos_lines, path);
free(pos_line_dup);
free(data);
return CCONFIG_CONF_ERROR_PARSING;
}
/* NULL not allowed but empty string */
if (!val) {
val = "";
}
log_misc("Key: %s, Value: %s", key, val);
entry = cconfig_get(config, key);
if (entry || add_params_if_absent) {
cconfig_set2(config, key, val);
} else {
/* Ignore cmd params that are not found in config */
log_warning("Could not find parameter with key '%s' in "
"config, ignored", key);
}
free(pos_line_dup);
}
pos_lines = strtok_r(NULL, "\n", &ctx_lines);
}
free(data);
return CCONFIG_CONF_SUCCESS;
}
enum cconfig_conf_error cconfig_conf_save_to_file(struct cconfig* config,
const char* path)
{
FILE* file;
file = fopen(path, "wb+");
if (file == NULL) {
return CCONFIG_CONF_ERROR_NO_SUCH_FILE;
}
for (uint32_t i = 0; i < config->nentries; i++) {
fprintf(file, "# %s\n", config->entries[i].desc);
fprintf(file, "%s=%s\n\n", config->entries[i].key,
config->entries[i].value);
}
fclose(file);
return CCONFIG_CONF_SUCCESS;
}

16
src/main/cconfig/conf.h Normal file
View File

@@ -0,0 +1,16 @@
#include <stdbool.h>
#include "cconfig/cconfig.h"
enum cconfig_conf_error {
CCONFIG_CONF_SUCCESS = 0,
CCONFIG_CONF_ERROR_NO_SUCH_FILE = 1,
CCONFIG_CONF_ERROR_FILE_CORRUPTED = 2,
CCONFIG_CONF_ERROR_PARSING = 3,
};
enum cconfig_conf_error cconfig_conf_load_from_file(struct cconfig* config,
const char* path, bool add_params_if_absent);
enum cconfig_conf_error cconfig_conf_save_to_file(struct cconfig* config,
const char* path);

30
src/main/config/Module.mk Normal file
View File

@@ -0,0 +1,30 @@
exes += config
rc_config := config.rc
cppflags_config := -DUNICODE
libs_config := \
eamio \
geninput \
util \
ldflags_config := \
-lcomctl32 \
-lcomdlg32 \
-lgdi32 \
-mwindows \
src_config := \
analogs.c \
bind-adv.c \
bind.c \
bind-light.c \
buttons.c \
eam.c \
gametype.c \
lights.c \
main.c \
schema.c \
snap.c \
spinner.c \
usages.c \

540
src/main/config/analogs.c Normal file
View File

@@ -0,0 +1,540 @@
#include <windows.h>
#include <commctrl.h>
#include <stdbool.h>
#include <stdlib.h>
#include "config/resource.h"
#include "config/schema.h"
#include "config/usages.h"
#include "geninput/hid-mgr.h"
#include "geninput/input-config.h"
#include "geninput/mapper.h"
#include "util/array.h"
#include "util/defs.h"
#include "util/log.h"
#include "util/mem.h"
#include "util/str.h"
#define SENSITIVITY_SCALE 4
struct analogs_ui {
struct array children;
};
struct analog_ui {
const struct analog_def *def;
struct array hids;
struct array control_nos;
struct hid_stub *selected_hid;
uint8_t pos;
};
static INT_PTR CALLBACK analogs_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam);
static INT_PTR analogs_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp);
static INT_PTR analogs_ui_handle_activate(HWND hwnd);
static INT_PTR analogs_ui_handle_passivate(HWND hwnd);
static INT_PTR analogs_ui_handle_tick(HWND hwnd);
static INT_PTR analogs_ui_handle_fini(HWND hwnd);
static INT_PTR CALLBACK analog_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam);
static INT_PTR analog_ui_handle_init(HWND hwnd, struct analog_def *def);
static void analog_ui_handle_init_label(HWND hwnd);
static void analog_ui_handle_init_dev(HWND hwnd);
static void analog_ui_handle_init_sensitivity(HWND hwnd);
static bool analog_ui_match_device(struct hid_stub *hid);
static void analog_ui_populate_controls(HWND hwnd);
static INT_PTR analog_ui_handle_device_change(HWND hwnd);
static INT_PTR analog_ui_handle_control_change(HWND hwnd);
static INT_PTR analog_ui_handle_sensitivity_change(HWND hwnd);
static INT_PTR analog_ui_handle_tick(HWND hwnd);
static INT_PTR analog_ui_handle_fini(HWND hwnd);
HPROPSHEETPAGE analogs_ui_tab_create(HINSTANCE inst,
const struct schema *schema)
{
PROPSHEETPAGE psp;
memset(&psp, 0, sizeof(psp));
psp.dwSize = sizeof(psp);
psp.dwFlags = PSP_DEFAULT;
psp.hInstance = inst;
psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_ANALOGS);
psp.pfnDlgProc = analogs_ui_dlg_proc;
psp.lParam = (LPARAM) schema;
return CreatePropertySheetPage(&psp);
}
static INT_PTR CALLBACK analogs_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam)
{
const NMHDR *n;
switch (msg) {
case WM_INITDIALOG:
return analogs_ui_handle_init(hwnd, (PROPSHEETPAGE *) lparam);
case WM_NOTIFY:
n = (NMHDR *) lparam;
switch (n->code) {
case PSN_SETACTIVE:
return analogs_ui_handle_activate(hwnd);
case PSN_KILLACTIVE:
return analogs_ui_handle_passivate(hwnd);
}
return FALSE;
case WM_TIMER:
return analogs_ui_handle_tick(hwnd);
case WM_DESTROY:
return analogs_ui_handle_fini(hwnd);
}
return FALSE;
}
static INT_PTR analogs_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp)
{
struct analogs_ui *ui;
const struct schema *schema;
long ypos;
size_t i;
HINSTANCE inst;
HWND child;
RECT r;
ui = xmalloc(sizeof(*ui));
array_init(&ui->children);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui);
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
schema = (struct schema *) psp->lParam;
ypos = 0;
for (i = 0 ; i < schema->nanalogs ; i++) {
child = CreateDialogParam(inst, MAKEINTRESOURCE(IDD_ANALOG), hwnd,
analog_ui_dlg_proc, (LPARAM) &schema->analogs[i]);
GetWindowRect(child, &r);
SetWindowPos(child, HWND_BOTTOM, 0, ypos, 0, 0,
SWP_NOSIZE | SWP_SHOWWINDOW);
ypos += r.bottom - r.top;
*array_append(HWND, &ui->children) = child;
}
return TRUE;
}
static INT_PTR analogs_ui_handle_activate(HWND hwnd)
{
SetTimer(hwnd, 1, 17, NULL);
return TRUE;
}
static INT_PTR analogs_ui_handle_passivate(HWND hwnd)
{
KillTimer(hwnd, 1);
return TRUE;
}
static INT_PTR analogs_ui_handle_tick(HWND hwnd)
{
HWND child;
struct analogs_ui *ui;
size_t i;
mapper_update();
ui = (struct analogs_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
for (i = 0 ; i < ui->children.nitems ; i++) {
child = *array_item(HWND, &ui->children, i);
SendMessage(child, WM_USER, 0, 0);
}
return TRUE;
}
static INT_PTR analogs_ui_handle_fini(HWND hwnd)
{
struct analogs_ui *ui;
ui = (struct analogs_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
array_fini(&ui->children);
free(ui);
return TRUE;
}
static INT_PTR CALLBACK analog_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
case WM_INITDIALOG:
return analog_ui_handle_init(hwnd, (struct analog_def *) lparam);
case WM_USER:
return analog_ui_handle_tick(hwnd);
case WM_COMMAND:
switch (HIWORD(wparam)) {
case CBN_SELCHANGE:
switch (LOWORD(wparam)) {
case IDC_DEVICE:
return analog_ui_handle_device_change(hwnd);
case IDC_CONTROL:
return analog_ui_handle_control_change(hwnd);
default:
return FALSE;
}
default:
return FALSE;
}
case WM_HSCROLL:
if (GetDlgItem(hwnd, IDC_SENSITIVITY) == (HWND) lparam) {
return analog_ui_handle_sensitivity_change(hwnd);
} else {
return FALSE;
}
case WM_DESTROY:
return analog_ui_handle_fini(hwnd);
default:
return FALSE;
}
}
static INT_PTR analog_ui_handle_init(HWND hwnd, struct analog_def *def)
{
struct analog_ui *ui;
ui = xmalloc(sizeof(*ui));
ui->def = def;
array_init(&ui->hids);
array_init(&ui->control_nos);
ui->selected_hid = NULL;
ui->pos = 0;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui);
analog_ui_handle_init_label(hwnd);
analog_ui_handle_init_dev(hwnd);
analog_ui_handle_init_sensitivity(hwnd);
return TRUE;
}
static void analog_ui_handle_init_label(HWND hwnd)
{
struct analog_ui *ui;
wchar_t label[128];
HINSTANCE inst;
HWND box;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
box = GetDlgItem(hwnd, IDC_GROUP);
LoadString(inst, ui->def->label_rsrc, label, lengthof(label));
SendMessage(box, WM_SETTEXT, 0, (LPARAM) label);
}
static void analog_ui_handle_init_dev(HWND hwnd)
{
struct analog_ui *ui;
struct mapped_analog ma;
wchar_t *dev_name;
struct hid_stub *hid;
size_t nchars;
LRESULT index;
HWND dev_list;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
dev_list = GetDlgItem(hwnd, IDC_DEVICE);
SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) L"");
hid_mgr_lock();
ma.hid = NULL;
mapper_get_analog_map(ui->def->tag, &ma);
for (hid = hid_mgr_get_first_stub()
; hid != NULL
; hid = hid_mgr_get_next_stub(hid)) {
if (!analog_ui_match_device(hid)) {
continue;
}
if (!hid_stub_get_name(hid, NULL, &nchars)) {
continue;
}
dev_name = xmalloc(nchars * sizeof(*dev_name));
if (!hid_stub_get_name(hid, dev_name, &nchars)) {
free(dev_name);
continue;
}
index = SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) dev_name);
free(dev_name);
*array_append(struct hid_stub *, &ui->hids) = hid;
if (ma.hid == hid) {
SendMessage(dev_list, CB_SETCURSEL, index, 0);
ui->selected_hid = hid;
analog_ui_populate_controls(hwnd);
}
}
hid_mgr_unlock();
}
static void analog_ui_handle_init_sensitivity(HWND hwnd)
{
struct analog_ui *ui;
int pos;
HWND slider;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
slider = GetDlgItem(hwnd, IDC_SENSITIVITY);
pos = 256 * SENSITIVITY_SCALE + mapper_get_analog_sensitivity(ui->def->tag);
SendMessage(slider, TBM_SETTICFREQ, 256, 0);
SendMessage(slider, TBM_SETRANGE, FALSE,
MAKELPARAM(0, SENSITIVITY_SCALE * 256 * 2));
SendMessage(slider, TBM_SETPOS, TRUE, (LPARAM) pos);
EnableWindow(slider, !mapper_is_analog_absolute(ui->def->tag));
}
static bool analog_ui_match_device(struct hid_stub *hid)
{
struct hid_control *controls;
size_t ncontrols;
size_t i;
if (!hid_stub_get_controls(hid, NULL, &ncontrols)) {
goto size_fail;
}
controls = xmalloc(ncontrols * sizeof(*controls));
if (!hid_stub_get_controls(hid, controls, &ncontrols)) {
goto content_fail;
}
for (i = 0 ; i < ncontrols ; i++) {
if (controls[i].value_max - controls[i].value_min > 1) {
break;
}
}
free(controls);
return i < ncontrols;
content_fail:
free(controls);
size_fail:
return false;
}
static void analog_ui_populate_controls(HWND hwnd)
{
char usage_desc[512];
wchar_t *tmp;
struct analog_ui *ui;
struct mapped_analog ma;
struct hid_control *controls;
size_t ncontrols;
size_t i;
long nitems;
LRESULT index;
HWND controls_ctl;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
controls_ctl = GetDlgItem(hwnd, IDC_CONTROL);
nitems = (long) SendMessage(controls_ctl, CB_GETCOUNT, 0, 0);
InvalidateRect(controls_ctl, NULL, TRUE);
for (index = nitems ; index >= 0 ; index--) {
SendMessage(controls_ctl, CB_DELETESTRING, index, 0);
}
array_fini(&ui->control_nos);
array_init(&ui->control_nos);
if (ui->selected_hid == NULL) {
return;
}
if (!hid_stub_get_controls(ui->selected_hid, NULL, &ncontrols)) {
goto size_fail;
}
controls = xmalloc(sizeof(*controls) * ncontrols);
if (!hid_stub_get_controls(ui->selected_hid, controls, &ncontrols)) {
goto content_fail;
}
SendMessage(controls_ctl, CB_ADDSTRING, 0, (LPARAM) L"");
mapper_get_analog_map(ui->def->tag, &ma);
for (i = 0 ; i < ncontrols ; i++) {
if (controls[i].value_max - controls[i].value_min <= 1) {
continue;
}
usages_get(usage_desc, lengthof(usage_desc), controls[i].usage);
tmp = str_widen(usage_desc);
index = SendMessage(controls_ctl, CB_ADDSTRING, 0, (LPARAM) tmp);
free(tmp);
if (i == ma.control_no) {
SendMessage(controls_ctl, CB_SETCURSEL, index, 0);
}
*array_append(size_t, &ui->control_nos) = i;
}
free(controls);
return;
content_fail:
free(controls);
size_fail:
return;
}
static INT_PTR analog_ui_handle_device_change(HWND hwnd)
{
struct mapped_analog ma;
struct analog_ui *ui;
LRESULT index;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
index = SendMessage(GetDlgItem(hwnd, IDC_DEVICE), CB_GETCURSEL, 0, 0);
log_assert((size_t) index <= ui->hids.nitems);
if (index == 0) {
ui->selected_hid = NULL;
} else {
ui->selected_hid = *array_item(struct hid_stub *, &ui->hids, index - 1);
}
ma.hid = NULL;
mapper_set_analog_map(ui->def->tag, &ma);
hid_mgr_lock();
analog_ui_populate_controls(hwnd);
hid_mgr_unlock();
return TRUE;
}
static INT_PTR analog_ui_handle_control_change(HWND hwnd)
{
struct mapped_analog ma;
struct analog_ui *ui;
LRESULT index;
HWND slider;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
index = SendMessage(GetDlgItem(hwnd, IDC_CONTROL), CB_GETCURSEL, 0, 0);
log_assert((size_t) index <= ui->control_nos.nitems);
if (index == 0) {
ma.hid = NULL;
} else {
ma.hid = ui->selected_hid;
ma.control_no = *array_item(size_t, &ui->control_nos, index - 1);
}
mapper_set_analog_map(ui->def->tag, &ma);
slider = GetDlgItem(hwnd, IDC_SENSITIVITY);
EnableWindow(slider, !mapper_is_analog_absolute(ui->def->tag));
return TRUE;
}
static INT_PTR analog_ui_handle_sensitivity_change(HWND hwnd)
{
struct analog_ui *ui;
int pos;
HWND slider;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
slider = GetDlgItem(hwnd, IDC_SENSITIVITY);
pos = (int) SendMessage(slider, TBM_GETPOS, 0, 0);
mapper_set_analog_sensitivity(ui->def->tag, pos - 256 * SENSITIVITY_SCALE);
return TRUE;
}
static INT_PTR analog_ui_handle_tick(HWND hwnd)
{
struct analog_ui *ui;
HWND pos_ctl;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
pos_ctl = GetDlgItem(hwnd, IDC_POSITION);
ui->pos = mapper_read_analog(ui->def->tag);
SendMessage(pos_ctl, WM_USER, 0, (LPARAM) ui->pos);
return TRUE;
}
static INT_PTR analog_ui_handle_fini(HWND hwnd)
{
struct analog_ui *ui;
ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
array_fini(&ui->hids);
array_fini(&ui->control_nos);
free(ui);
return TRUE;
}

530
src/main/config/bind-adv.c Normal file
View File

@@ -0,0 +1,530 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "config/bind-adv.h"
#include "config/resource.h"
#include "config/usages.h"
#include "geninput/hid-mgr.h"
#include "util/array.h"
#include "util/defs.h"
#include "util/mem.h"
#include "util/str.h"
struct bind_adv_state {
uintptr_t timer_id;
struct mapped_action ma;
struct array devs;
struct hid_stub *cur_hid;
struct hid_control *ctls;
size_t nctls;
bool was_valid;
};
static INT_PTR CALLBACK bind_adv_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam);
static INT_PTR bind_adv_handle_init(HWND hwnd, struct bind_adv_state *state);
static void bind_adv_handle_init_devs(HWND hwnd);
static void bind_adv_handle_init_insert_dev(HWND hwnd, struct hid_stub *hid);
static INT_PTR bind_adv_handle_change_ctl(HWND hwnd);
static INT_PTR bind_adv_handle_change_dev(HWND hwnd);
static INT_PTR bind_adv_handle_change_range(HWND hwnd);
static INT_PTR bind_adv_handle_tick(HWND hwnd);
static INT_PTR bind_adv_handle_ok(HWND hwnd);
static INT_PTR bind_adv_handle_fini(HWND hwnd);
static bool bind_adv_get_dev_no(HWND hwnd, size_t *dev_no_out);
static bool bind_adv_get_ctl_no(HWND hwnd, size_t *ctl_no_out);
static bool bind_adv_get_range(HWND hwnd, int32_t *out_min, int32_t *out_max);
static bool bind_adv_get_int(HWND control, int32_t *out);
static bool bind_adv_is_valid(HWND hwnd);
static void bind_adv_validate(HWND hwnd);
static void bind_adv_select_dev(HWND hwnd, size_t dev_no);
static void bind_adv_select_ctl(HWND hwnd, size_t ctl_no);
static void bind_adv_set_range(HWND hwnd, int32_t range_min, int32_t range_max);
bool bind_adv(HINSTANCE inst, HWND hwnd, struct mapped_action *ma,
bool was_valid)
{
struct bind_adv_state state;
INT_PTR ok;
memset(&state, 0, sizeof(state));
state.ma = *ma;
state.was_valid = was_valid;
ok = DialogBoxParam(inst, MAKEINTRESOURCE(IDD_BIND_ADV), hwnd,
bind_adv_dlg_proc, (LPARAM) &state) != 0;
if (ok) {
*ma = state.ma;
}
return ok != 0;
}
static INT_PTR CALLBACK bind_adv_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
case WM_INITDIALOG:
return bind_adv_handle_init(hwnd, (struct bind_adv_state *) lparam);
case WM_COMMAND:
switch (LOWORD(wparam)) {
case IDC_CONTROL:
switch (HIWORD(wparam)) {
case CBN_SELCHANGE:
return bind_adv_handle_change_ctl(hwnd);
}
return FALSE;
case IDC_DEVICE:
switch (HIWORD(wparam)) {
case CBN_SELCHANGE:
return bind_adv_handle_change_dev(hwnd);
}
return FALSE;
case IDC_BINDING_MIN:
case IDC_BINDING_MAX:
switch (HIWORD(wparam)) {
case EN_CHANGE:
return bind_adv_handle_change_range(hwnd);
}
return FALSE;
case IDOK:
switch (HIWORD(wparam)) {
case BN_CLICKED:
return bind_adv_handle_ok(hwnd);
}
return FALSE;
case IDCANCEL:
switch (HIWORD(wparam)) {
case BN_CLICKED:
EndDialog(hwnd, FALSE);
return TRUE;
}
return FALSE;
}
return FALSE;
case WM_TIMER:
return bind_adv_handle_tick(hwnd);
case WM_DESTROY:
return bind_adv_handle_fini(hwnd);
}
return FALSE;
}
static INT_PTR bind_adv_handle_init(HWND hwnd, struct bind_adv_state *state)
{
struct hid_stub *hid;
size_t i;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) state);
bind_adv_handle_init_devs(hwnd);
state->timer_id = SetTimer(hwnd, 1, 17, NULL);
if (!state->was_valid) {
return TRUE;
}
for (i = 0 ; i < state->devs.nitems ; i++) {
hid = *array_item(struct hid_stub *, &state->devs, i);
if (hid == state->ma.hid) {
bind_adv_select_dev(hwnd, (int) i);
bind_adv_select_ctl(hwnd, state->ma.control_no);
bind_adv_set_range(hwnd, state->ma.value_min,
state->ma.value_max);
}
}
return TRUE;
}
static void bind_adv_handle_init_devs(HWND hwnd)
{
struct hid_stub *hid;
hid_mgr_lock();
for (hid = hid_mgr_get_first_stub()
; hid != NULL
; hid = hid_mgr_get_next_stub(hid)) {
bind_adv_handle_init_insert_dev(hwnd, hid);
}
hid_mgr_unlock();
}
static void bind_adv_handle_init_insert_dev(HWND hwnd, struct hid_stub *hid)
{
struct bind_adv_state *state;
wchar_t *name;
size_t nchars;
HWND devs;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
devs = GetDlgItem(hwnd, IDC_DEVICE);
if (!hid_stub_get_name(hid, NULL, &nchars)) {
goto size_fail;
}
name = xmalloc(sizeof(*name) * nchars);
if (!hid_stub_get_name(hid, name, &nchars)) {
goto name_fail;
}
*array_append(struct hid_stub *, &state->devs) = hid;
SendMessage(devs, CB_ADDSTRING, 0, (LPARAM) name);
name_fail:
free(name);
size_fail:
;
}
static INT_PTR bind_adv_handle_change_ctl(HWND hwnd)
{
size_t ctl_no;
if (bind_adv_get_ctl_no(hwnd, &ctl_no)) {
bind_adv_select_ctl(hwnd, ctl_no);
}
return TRUE;
}
static INT_PTR bind_adv_handle_change_dev(HWND hwnd)
{
size_t dev_no;
if (bind_adv_get_dev_no(hwnd, &dev_no)) {
bind_adv_select_dev(hwnd, dev_no);
}
return TRUE;
}
static INT_PTR bind_adv_handle_change_range(HWND hwnd)
{
bind_adv_validate(hwnd);
return TRUE;
}
static INT_PTR bind_adv_handle_tick(HWND hwnd)
{
struct bind_adv_state *state;
wchar_t wchars[16];
size_t ctl_no;
int32_t value;
HWND label;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (!bind_adv_get_ctl_no(hwnd, &ctl_no)) {
return TRUE;
}
hid_mgr_lock();
if (hid_stub_get_value(state->cur_hid, ctl_no, &value)) {
wstr_format(wchars, lengthof(wchars), L"%d", value);
label = GetDlgItem(hwnd, IDC_CURRENT);
SetWindowText(label, wchars);
}
hid_mgr_unlock();
return TRUE;
}
static INT_PTR bind_adv_handle_ok(HWND hwnd)
{
struct bind_adv_state *state;
size_t dev_no;
size_t ctl_no;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
/* bind_adv_validate() ought to ensure that OK is disabled whenever the
user's input is somehow invalid. Still, it never hurts to double check.
Ignore any OK clicks that we somehow receive with invalid input. */
if (!bind_adv_get_dev_no(hwnd, &dev_no)) {
return TRUE;
}
state->ma.hid = *array_item(struct hid_stub *, &state->devs, dev_no);
if (!bind_adv_get_ctl_no(hwnd, &ctl_no)) {
return TRUE;
}
state->ma.control_no = ctl_no;
if (!bind_adv_get_range(hwnd, &state->ma.value_min, &state->ma.value_max)) {
return TRUE;
}
/* Input OK, shut down this dialog. */
EndDialog(hwnd, TRUE);
return TRUE;
}
static INT_PTR bind_adv_handle_fini(HWND hwnd)
{
struct bind_adv_state *state;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
array_fini(&state->devs);
KillTimer(hwnd, state->timer_id);
return TRUE;
}
static bool bind_adv_get_dev_no(HWND hwnd, size_t *dev_no_out)
{
const struct bind_adv_state *state;
size_t dev_no;
HWND devs;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
devs = GetDlgItem(hwnd, IDC_DEVICE);
dev_no = SendMessage(devs, CB_GETCURSEL, 0, 0);
if (dev_no >= state->devs.nitems) {
return false;
}
if (dev_no_out != NULL) {
*dev_no_out = dev_no;
}
return true;
}
static bool bind_adv_get_ctl_no(HWND hwnd, size_t *ctl_no_out)
{
const struct bind_adv_state *state;
size_t ctl_no;
HWND ctls;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
ctls = GetDlgItem(hwnd, IDC_CONTROL);
ctl_no = SendMessage(ctls, CB_GETCURSEL, 0, 0);
if (ctl_no >= state->nctls) {
return false;
}
if (ctl_no_out != NULL) {
*ctl_no_out = ctl_no;
}
return true;
}
static bool bind_adv_get_range(HWND hwnd, int32_t *out_min, int32_t *out_max)
{
HWND wnd_min;
HWND wnd_max;
wnd_min = GetDlgItem(hwnd, IDC_BINDING_MIN);
wnd_max = GetDlgItem(hwnd, IDC_BINDING_MAX);
return bind_adv_get_int(wnd_min, out_min)
&& bind_adv_get_int(wnd_max, out_max);
}
static bool bind_adv_get_int(HWND control, int32_t *out)
{
wchar_t text[16];
int32_t tmp;
text[0] = L'\0';
GetWindowText(control, text, lengthof(text));
if (swscanf(text, L"%d", &tmp) != 1) {
return false;
}
if (out != NULL) {
*out = tmp;
}
return true;
}
static bool bind_adv_is_valid(HWND hwnd)
{
if (!bind_adv_get_dev_no(hwnd, NULL)) {
return false;
}
if (!bind_adv_get_ctl_no(hwnd, NULL)) {
return false;
}
if (!bind_adv_get_range(hwnd, NULL, NULL)) {
return false;
}
return true;
}
static void bind_adv_validate(HWND hwnd)
{
HWND ok;
ok = GetDlgItem(hwnd, IDOK);
EnableWindow(ok, bind_adv_is_valid(hwnd) ? TRUE : FALSE);
}
static void bind_adv_select_dev(HWND hwnd, size_t dev_no)
{
struct bind_adv_state *state;
struct hid_stub *hid;
char chars[256];
wchar_t wchars[256];
size_t i;
HWND ctls;
HWND devs;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
ctls = GetDlgItem(hwnd, IDC_CONTROL);
devs = GetDlgItem(hwnd, IDC_DEVICE);
if (dev_no >= state->devs.nitems) {
return;
}
SendMessage(devs, CB_SETCURSEL, (WPARAM) dev_no, 0);
SendMessage(ctls, CB_RESETCONTENT, 0, 0);
hid = *array_item(struct hid_stub *, &state->devs, dev_no);
state->cur_hid = hid;
hid_mgr_lock();
if (!hid_stub_get_controls(state->cur_hid, NULL, &state->nctls)) {
goto size_fail;
}
free(state->ctls);
state->ctls = xmalloc(sizeof(*state->ctls) * state->nctls);
if (!hid_stub_get_controls(state->cur_hid, state->ctls, &state->nctls)) {
goto data_fail;
}
for (i = 0 ; i < state->nctls ; i++) {
wchars[0] = L'\0';
usages_get(chars, lengthof(chars), state->ctls[i].usage);
MultiByteToWideChar(CP_UTF8, 0, chars, -1, wchars, lengthof(wchars));
SendMessage(ctls, CB_ADDSTRING, 0, (LPARAM) wchars);
}
hid_mgr_unlock();
bind_adv_validate(hwnd);
return;
data_fail:
free(state->ctls);
state->ctls = NULL;
state->nctls = 0;
size_fail:
hid_mgr_unlock();
bind_adv_validate(hwnd);
}
static void bind_adv_select_ctl(HWND hwnd, size_t ctl_no)
{
wchar_t text[128];
struct bind_adv_state *state;
const struct hid_control *ctl;
HWND ctls;
HWND limit_min;
HWND limit_max;
state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
ctls = GetDlgItem(hwnd, IDC_CONTROL);
limit_min = GetDlgItem(hwnd, IDC_LIMIT_MIN);
limit_max = GetDlgItem(hwnd, IDC_LIMIT_MAX);
if (ctl_no >= state->nctls) {
return;
}
SendMessage(ctls, CB_SETCURSEL, (WPARAM) ctl_no, 0);
ctl = &state->ctls[ctl_no];
wstr_format(text, lengthof(text), L"%d", ctl->value_min);
SetWindowText(limit_min, text);
wstr_format(text, lengthof(text), L"%d", ctl->value_max);
SetWindowText(limit_max, text);
bind_adv_validate(hwnd);
}
static void bind_adv_set_range(HWND hwnd, int32_t range_min, int32_t range_max)
{
wchar_t text[16];
HWND wnd_min;
HWND wnd_max;
wnd_min = GetDlgItem(hwnd, IDC_BINDING_MIN);
wnd_max = GetDlgItem(hwnd, IDC_BINDING_MAX);
wstr_format(text, lengthof(text), L"%d", range_min);
SetWindowText(wnd_min, text);
wstr_format(text, lengthof(text), L"%d", range_max);
SetWindowText(wnd_max, text);
bind_adv_validate(hwnd);
}

Some files were not shown because too many files have changed in this diff Show More