├── .gitignore ├── ACIO.cpp ├── ACIO.h ├── ACIOLauncher.cpp ├── ACIOLauncher.sln ├── ACIOLauncher.vcproj ├── Debug.cpp ├── Debug.h ├── LICENSE ├── Menu.cpp ├── Menu.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.ncb 2 | *.suo 3 | *.user 4 | Debug/ -------------------------------------------------------------------------------- /ACIO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ACIO.h" 4 | #include "Debug.h" 5 | 6 | /* Length of an ACIO packet without payload or checksum */ 7 | #define HEADER_LENGTH 5 8 | 9 | /* Minimum packet length including header and checksum */ 10 | #define MINIMUM_PACKET_LENGTH (HEADER_LENGTH + 1) 11 | 12 | /* Start of Message */ 13 | #define SOM 0xAA 14 | 15 | /* Location of various parts of the protocol */ 16 | #define ID_LOCATION 0 17 | #define COMMAND_HIGH_LOCATION 1 18 | #define COMMAND_LOW_LOCATION 2 19 | #define LENGTH_HIGH_LOCATION 3 20 | #define LENGTH_LOW_LOCATION 4 21 | 22 | /* Number of times to try initializing a reader by sending baud probe */ 23 | #define INITIALIZATION_TRIES 8 24 | 25 | /* Number of return baud probes to receive before we consider ourselves initialized */ 26 | #define MIN_PROBES_RECEIVED 5 27 | 28 | #define WAIT() Sleep( 100 ) 29 | #define LONGWAIT() Sleep( 1000 ) 30 | 31 | ACIO::ACIO() 32 | { 33 | /* Initialize readers first */ 34 | printf( "Initializing readers" ); 35 | InitReaders(); 36 | printf( "\n" ); 37 | 38 | /* Get version of all readers */ 39 | for( unsigned int x = 0; x < readerCount; x++ ) 40 | { 41 | /* Print out reader version in debug mode */ 42 | const char * const version = getReaderVersion( serial, x ); 43 | char * typestring; 44 | reader_type_t type = getReaderType( serial, x ); 45 | 46 | switch( type ) { 47 | case TYPE_UNKNOWN: 48 | typestring = "unknown"; 49 | break; 50 | case TYPE_READER: 51 | typestring = "reader"; 52 | break; 53 | case TYPE_DISPENSER: 54 | typestring = "dispenser"; 55 | break; 56 | case TYPE_LEDBOARD: 57 | typestring = "LED board"; 58 | break; 59 | case TYPE_IOBOARD: 60 | typestring = "IO board"; 61 | break; 62 | } 63 | 64 | DEBUG_PRINTF( "Reader %d is type %s version %s\n", x + 1, typestring, version ); 65 | 66 | /* Walk init routine */ 67 | initReader( serial, x, 0 ); 68 | initReader( serial, x, 1 ); 69 | initReader( serial, x, 2 ); 70 | 71 | /* Set ready for keys only */ 72 | setReaderState( serial, x, STATE_EJECT ); 73 | } 74 | 75 | /* For key input debouncing */ 76 | old_keypresses = (unsigned int *)malloc(sizeof(unsigned int) * readerCount); 77 | } 78 | 79 | ACIO::~ACIO(void) 80 | { 81 | /* Kill memory used for keypresses */ 82 | free(old_keypresses); 83 | 84 | /* Close the reader so we can let the game talk to it */ 85 | for( unsigned int y = 0; y < readerCount; y++ ) 86 | { 87 | setReaderState( serial, y, STATE_EJECT ); 88 | } 89 | CloseHandle( serial ); 90 | } 91 | 92 | bool ACIO::getCardInserted(int reader) 93 | { 94 | unsigned int currentpresses; 95 | getReaderState( serial, reader, &state, ¤tpresses, cardId ); 96 | return (state == STATE_READ || state == STATE_INSERTED); 97 | } 98 | 99 | /** 100 | * TODO: For some reason this returns one card read in the past. So, on first 101 | * read it returns garbage, on second read it returns the first card, etc. Fix 102 | * that at some point. 103 | */ 104 | void ACIO::getCardID(int reader, unsigned char *outCardId) 105 | { 106 | while( !getCardInserted(reader) ) { requestCardId( serial, reader ); } 107 | while( !getCardInserted(reader) ) { requestCardId( serial, reader ); } 108 | memcpy(outCardId, cardId, CARD_LENGTH); 109 | } 110 | 111 | reader_keypress_t ACIO::getPressedKey(int reader) 112 | { 113 | unsigned int currentpresses; 114 | getReaderState( serial, reader, &state, ¤tpresses, cardId ); 115 | unsigned int keypresses = currentpresses & (~old_keypresses[reader]); 116 | old_keypresses[reader] = currentpresses; 117 | 118 | if (keypresses & 0x0002 ) 119 | { 120 | return KEY_1; 121 | } 122 | if (keypresses & 0x0020 ) 123 | { 124 | return KEY_2; 125 | } 126 | if (keypresses & 0x0200 ) 127 | { 128 | return KEY_3; 129 | } 130 | if (keypresses & 0x0004 ) 131 | { 132 | return KEY_4; 133 | } 134 | if (keypresses & 0x0040 ) 135 | { 136 | return KEY_5; 137 | } 138 | if (keypresses & 0x0400 ) 139 | { 140 | return KEY_6; 141 | } 142 | if (keypresses & 0x0008 ) 143 | { 144 | return KEY_7; 145 | } 146 | if (keypresses & 0x0080 ) 147 | { 148 | return KEY_8; 149 | } 150 | if (keypresses & 0x0800 ) 151 | { 152 | return KEY_9; 153 | } 154 | if (keypresses & 0x0001 ) 155 | { 156 | return KEY_0; 157 | } 158 | if (keypresses & 0x0010 ) 159 | { 160 | return KEY_00; 161 | } 162 | if (keypresses & 0x0100 ) 163 | { 164 | return KEY_BLANK; 165 | } 166 | 167 | return KEY_NONE; 168 | } 169 | 170 | void ACIO::requestCardInsert(int reader) 171 | { 172 | setReaderState( serial, reader, STATE_EJECT ); 173 | 174 | /* Walk init routine again. For some reason, it reads the same 175 | * card ID the second time through, so I just reinit. */ 176 | initReader( serial, reader, 0 ); 177 | initReader( serial, reader, 1 ); 178 | initReader( serial, reader, 2 ); 179 | 180 | setReaderState( serial, reader, STATE_NOT_READY ); 181 | setReaderState( serial, reader, STATE_GET_READY ); 182 | setReaderState( serial, reader, STATE_READY ); 183 | } 184 | 185 | void ACIO::requestCardEject(int reader) 186 | { 187 | setReaderState( serial, reader, STATE_EJECT ); 188 | } 189 | 190 | HANDLE ACIO::openSerial( const _TCHAR *arg, int baud ) 191 | { 192 | HANDLE hSerial = CreateFile(arg, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 193 | if (hSerial == INVALID_HANDLE_VALUE) { return hSerial; } 194 | 195 | int rate; 196 | 197 | switch( baud ) 198 | { 199 | case 4800: 200 | rate = CBR_4800; 201 | break; 202 | case 9600: 203 | rate = CBR_9600; 204 | break; 205 | case 19200: 206 | rate = CBR_19200; 207 | break; 208 | case 38400: 209 | rate = CBR_38400; 210 | break; 211 | case 57600: 212 | rate = CBR_57600; 213 | break; 214 | case 115200: 215 | rate = CBR_115200; 216 | break; 217 | default: 218 | rate = CBR_9600; 219 | break; 220 | } 221 | 222 | DCB dcbSerialParams = {0}; 223 | 224 | dcbSerialParams.DCBlength = sizeof(dcbSerialParams); 225 | dcbSerialParams.BaudRate = rate; 226 | dcbSerialParams.ByteSize = 8; 227 | dcbSerialParams.StopBits = ONESTOPBIT; 228 | dcbSerialParams.Parity = NOPARITY; 229 | dcbSerialParams.fOutxCtsFlow = 0; 230 | dcbSerialParams.fOutxDsrFlow = 0; 231 | dcbSerialParams.fDtrControl = DTR_CONTROL_DISABLE; 232 | dcbSerialParams.fDsrSensitivity = 0; 233 | dcbSerialParams.fOutX = 0; 234 | dcbSerialParams.fInX = 0; 235 | dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE; 236 | 237 | SetCommState(hSerial, &dcbSerialParams); 238 | 239 | COMMTIMEOUTS timeouts = { 0 }; 240 | 241 | timeouts.ReadIntervalTimeout = 1; 242 | timeouts.ReadTotalTimeoutConstant = 1; 243 | timeouts.ReadTotalTimeoutMultiplier = 1; 244 | 245 | SetCommTimeouts(hSerial, &timeouts); 246 | return hSerial; 247 | } 248 | 249 | const unsigned char * const ACIO::getPacketData( 250 | unsigned int *readerId, 251 | unsigned int *command, 252 | unsigned int *len, 253 | unsigned int *checksum, 254 | const unsigned char * const packet, 255 | unsigned int length 256 | ) { 257 | /* Exit early if we didn't get a good packet */ 258 | if (length < MINIMUM_PACKET_LENGTH) { return NULL; } 259 | 260 | for( unsigned int i = 0; i <= length - MINIMUM_PACKET_LENGTH; i++ ) 261 | { 262 | const unsigned char * const data = &packet[i]; 263 | 264 | if( data[0] != SOM ) 265 | { 266 | /* Get command */ 267 | *readerId = data[ID_LOCATION]; 268 | *command = (data[COMMAND_HIGH_LOCATION] << 8) | data[COMMAND_LOW_LOCATION]; 269 | *len = (data[LENGTH_HIGH_LOCATION] << 8) | data[LENGTH_LOW_LOCATION]; 270 | *checksum = data[(*len) + HEADER_LENGTH]; 271 | return data + HEADER_LENGTH; 272 | } 273 | } 274 | 275 | return NULL; 276 | } 277 | 278 | unsigned char ACIO::calcPacketChecksum( const unsigned char * const data ) 279 | { 280 | unsigned char *packet = (unsigned char *)data; 281 | 282 | /* Dirty */ 283 | while( 1 ) 284 | { 285 | /* Get to start of packet */ 286 | if( packet[0] == SOM ) 287 | { 288 | packet++; 289 | continue; 290 | } 291 | 292 | unsigned int length = ((packet[LENGTH_HIGH_LOCATION] << 8) | packet[LENGTH_LOW_LOCATION]); 293 | unsigned int sum = 0; 294 | 295 | /* Skip SOM, not part of packet CRC */ 296 | for( unsigned int i = 0; i < length + HEADER_LENGTH; i++ ) 297 | { 298 | sum += packet[i]; 299 | } 300 | 301 | return sum & 0xFF; 302 | } 303 | } 304 | 305 | int ACIO::probeReader( HANDLE hSerial ) 306 | { 307 | const unsigned char packet_probe[] = { 308 | 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 309 | 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 310 | 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 311 | 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 312 | }; 313 | unsigned char data[1024]; 314 | DWORD length; 315 | 316 | WriteFile( hSerial, packet_probe, sizeof( packet_probe ), &length, 0 ); 317 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 318 | 319 | if (length < MIN_PROBES_RECEIVED) 320 | { 321 | return 0; 322 | } 323 | 324 | for (unsigned int i = 0; i < length; i++) 325 | { 326 | if (data[i] != 0xAA) 327 | { 328 | return 0; 329 | } 330 | } 331 | 332 | /* Clear out any additional init the reader sends */ 333 | while (true) 334 | { 335 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 336 | if (length == 0) 337 | { 338 | return 1; 339 | } 340 | } 341 | } 342 | 343 | unsigned int ACIO::getReaderCount( HANDLE hSerial ) 344 | { 345 | const unsigned char count_probe[] = { 0xAA, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, }; 346 | unsigned char data[1024]; 347 | DWORD length; 348 | 349 | WriteFile( hSerial, count_probe, sizeof( count_probe ), &length, 0 ); 350 | WAIT(); 351 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 352 | 353 | unsigned int readerId; 354 | unsigned int command; 355 | unsigned int len; 356 | unsigned int checksum; 357 | const unsigned char * const payload = getPacketData( &readerId, &command, &len, &checksum, data, length ); 358 | 359 | if( payload == NULL ) { return 0; } 360 | if( len != 1 ) { return 0; } 361 | 362 | return payload[0]; 363 | } 364 | 365 | void ACIO::initReader( HANDLE hSerial, unsigned int id, int whichInit ) 366 | { 367 | unsigned int init_length[3] = { 7, 7, 8 }; 368 | unsigned char init_probe[3][8] = { { 0xAA, (id + 1), 0x00, 0x03, 0x00, 0x00, 0xFF, }, 369 | { 0xAA, (id + 1), 0x01, 0x00, 0x00, 0x00, 0xFF, }, 370 | { 0xAA, (id + 1), 0x01, 0x30, 0x00, 0x01, 0x00, 0xFF, } }; 371 | unsigned char data[1024]; 372 | DWORD length; 373 | 374 | /* Fix up checksum since ID is variable */ 375 | init_probe[whichInit][init_length[whichInit] - 1] = calcPacketChecksum( init_probe[whichInit] ); 376 | 377 | WriteFile( hSerial, init_probe[whichInit], init_length[whichInit], &length, 0 ); 378 | WAIT(); 379 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 380 | } 381 | 382 | const char * const ACIO::getReaderVersion( HANDLE hSerial, unsigned int id ) 383 | { 384 | unsigned char version_probe[] = { 0xAA, (id + 1), 0x00, 0x02, 0x00, 0x00, 0xFF, }; 385 | static char version[33] = { 0x00 }; 386 | unsigned char data[1024]; 387 | DWORD length; 388 | 389 | /* Fix up checksum since ID is variable */ 390 | version_probe[6] = calcPacketChecksum( version_probe ); 391 | 392 | WriteFile( hSerial, version_probe, sizeof( version_probe ), &length, 0 ); 393 | WAIT(); 394 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 395 | 396 | unsigned int readerId; 397 | unsigned int command; 398 | unsigned int len; 399 | unsigned int checksum; 400 | const unsigned char * const payload = getPacketData( &readerId, &command, &len, &checksum, data, length ); 401 | 402 | if( payload != NULL && len == 44 ) 403 | { 404 | memcpy( version, &payload[12], 32 ); 405 | version[32] = 0x00; 406 | 407 | /* Spaces, for display */ 408 | for( int i = 0; i < 32; i++ ) 409 | { 410 | if( version[i] == 0x00 ) { version[i] = 0x20; } 411 | } 412 | } 413 | 414 | return (const char * const)version; 415 | } 416 | 417 | reader_type_t ACIO::getReaderType( HANDLE hSerial, unsigned int id ) 418 | { 419 | unsigned char version_probe[] = { 0xAA, (id + 1), 0x00, 0x02, 0x00, 0x00, 0xFF, }; 420 | static char code[5] = { 0x00 }; 421 | unsigned char data[1024]; 422 | DWORD length; 423 | 424 | /* Fix up checksum since ID is variable */ 425 | version_probe[6] = calcPacketChecksum( version_probe ); 426 | 427 | WriteFile( hSerial, version_probe, sizeof( version_probe ), &length, 0 ); 428 | WAIT(); 429 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 430 | 431 | unsigned int readerId; 432 | unsigned int command; 433 | unsigned int len; 434 | unsigned int checksum; 435 | const unsigned char * const payload = getPacketData( &readerId, &command, &len, &checksum, data, length ); 436 | 437 | if( payload != NULL && len == 44 ) 438 | { 439 | memcpy( code, &payload[8], 4 ); 440 | code[4] = 0x00; 441 | 442 | if (strcmp(code, "ICCA") == 0) { 443 | return TYPE_READER; 444 | } 445 | if (strcmp(code, "ICCB") == 0) { 446 | return TYPE_READER; 447 | } 448 | if (strcmp(code, "ICCC") == 0) { 449 | return TYPE_READER; 450 | } 451 | if (strcmp(code, "HBHI") == 0) { 452 | return TYPE_DISPENSER; 453 | } 454 | if (strcmp(code, "LEDB") == 0) { 455 | return TYPE_LEDBOARD; 456 | } 457 | if (strcmp(code, "HDXS") == 0) { 458 | return TYPE_LEDBOARD; 459 | } 460 | if (strcmp(code, "KFCA") == 0) { 461 | return TYPE_IOBOARD; 462 | } 463 | } 464 | 465 | return TYPE_UNKNOWN; 466 | } 467 | 468 | void ACIO::getReaderState( HANDLE hSerial, unsigned int id, card_state_t *state, unsigned int *keypresses, unsigned char *cardId ) 469 | { 470 | unsigned char state_probe[] = { 0xAA, (id + 1), 0x01, 0x34, 0x00, 0x01, 0x10, 0xFF, }; 471 | unsigned char data[1024]; 472 | DWORD length; 473 | 474 | /* Fix up checksum since ID is variable */ 475 | state_probe[7] = calcPacketChecksum( state_probe ); 476 | 477 | /* Sane return */ 478 | *keypresses = 0; 479 | 480 | WriteFile( hSerial, state_probe, sizeof( state_probe ), &length, 0 ); 481 | WAIT(); 482 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 483 | 484 | unsigned int readerId; 485 | unsigned int command; 486 | unsigned int len; 487 | unsigned int checksum; 488 | const unsigned char * const payload = getPacketData( &readerId, &command, &len, &checksum, data, length ); 489 | 490 | if( payload != NULL && len == 16 ) 491 | { 492 | *keypresses = (payload[14] << 8) | payload[15]; 493 | 494 | *state = STATE_UNKNOWN; 495 | switch( payload[1] ) 496 | { 497 | case 0x00: 498 | *state = STATE_REMOVED; 499 | break; 500 | case 0x10: 501 | *state = STATE_FRONT_SENSOR; 502 | break; 503 | case 0x30: 504 | *state = STATE_INSERTED; 505 | break; 506 | default: 507 | if( VERBOSE_DEBUG ) fprintf( stderr, "Unknown card state %02X!\n", payload[1] ); 508 | break; 509 | } 510 | 511 | if( payload[0] != 0x00 && payload[11] == 0x00 && *state == STATE_INSERTED ) 512 | { 513 | memcpy( cardId, &payload[2], CARD_LENGTH ); 514 | *state = STATE_READ; 515 | } 516 | } 517 | } 518 | 519 | void ACIO::setReaderState( HANDLE hSerial, unsigned int id, reader_state_t state ) 520 | { 521 | unsigned char state_request[] = { 0xAA, (id + 1), 0x01, 0x35, 0x00, 0x02, 0x10, 0xFF, 0xFF, }; 522 | unsigned char data[1024]; 523 | DWORD length; 524 | 525 | /* Set state */ 526 | switch( state ) 527 | { 528 | case STATE_NOT_READY: 529 | state_request[7] = 0x00; 530 | break; 531 | case STATE_GET_READY: 532 | state_request[7] = 0x03; 533 | break; 534 | case STATE_READY: 535 | state_request[7] = 0x11; 536 | break; 537 | case STATE_EJECT: 538 | state_request[7] = 0x12; 539 | break; 540 | default: 541 | if( VERBOSE_DEBUG ) fprintf( stderr, "Unknown reader state %02X!\n", state ); 542 | state_request[7] = 0xFF; 543 | break; 544 | } 545 | 546 | /* Fix up checksum since ID is variable */ 547 | state_request[8] = calcPacketChecksum( state_request ); 548 | 549 | WriteFile( hSerial, state_request, sizeof( state_request ), &length, 0 ); 550 | WAIT(); 551 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 552 | } 553 | 554 | void ACIO::requestCardId( HANDLE hSerial, unsigned int id ) 555 | { 556 | unsigned char id_request[] = { 0xAA, (id + 1), 0x01, 0x31, 0x00, 0x01, 0x10, 0xFF, }; 557 | unsigned char data[1024]; 558 | DWORD length; 559 | 560 | /* Fix up checksum since ID is variable */ 561 | id_request[7] = calcPacketChecksum( id_request ); 562 | 563 | WriteFile( hSerial, id_request, sizeof( id_request ), &length, 0 ); 564 | WAIT(); 565 | ReadFile( hSerial, data, sizeof( data ), &length, 0 ); 566 | } 567 | 568 | void ACIO::InitReader(const _TCHAR *comport) 569 | { 570 | HANDLE hSerial = NULL; 571 | const unsigned int baudrate[2] = { 57600, 9600 }; 572 | 573 | /* Loop until some thread claims the handle, and then exit */ 574 | while (serial == NULL) 575 | { 576 | for (unsigned int baud = 0; baud < 2; baud++) 577 | { 578 | DEBUG_PRINTF("Attempting to probe readers on %ls,%d\n", comport, baudrate[baud]); 579 | hSerial = openSerial( comport, baudrate[baud] ); 580 | 581 | if (hSerial == INVALID_HANDLE_VALUE) 582 | { 583 | DEBUG_PRINTF("Couldn't open com port!\n"); 584 | WAIT(); 585 | continue; 586 | } 587 | 588 | /* Ensure we start fresh */ 589 | PurgeComm( hSerial, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR ); 590 | 591 | /* Init */ 592 | for (int i = 0; i < INITIALIZATION_TRIES; i++) 593 | { 594 | if( probeReader( hSerial ) ) 595 | { 596 | /* Get count */ 597 | unsigned int count = getReaderCount( hSerial ); 598 | if (count > 0) 599 | { 600 | /* Make sure at least one is a reader */ 601 | for( unsigned int x = 0; x < count; x++ ) 602 | { 603 | /* Print out reader version in debug mode */ 604 | if (getReaderType( hSerial, x ) == TYPE_READER) 605 | { 606 | DEBUG_PRINTF( "Saw %d readers!\n", count ); 607 | serial = hSerial; 608 | readerCount = count; 609 | return; 610 | } 611 | } 612 | } 613 | } 614 | 615 | if (serial != NULL) 616 | { 617 | /* Exit early if we lost the race */ 618 | break; 619 | } 620 | } 621 | 622 | /* Failed, close */ 623 | CloseHandle( hSerial ); 624 | } 625 | } 626 | } 627 | 628 | void ACIO::InitReaders() 629 | { 630 | /* Walk serial chain, finding readers */ 631 | const _TCHAR *comport[4] = { L"COM1", L"COM2", L"COM3", L"COM4" }; 632 | thread_context_t threadContexts[4]; 633 | HANDLE threadHandles[4]; 634 | const char *pattern[4] = { "\b\b\b ", "\b\b\b. ", "\b\b\b.. ", "\b\b\b..." }; 635 | unsigned int pno = 0; 636 | 637 | /* Init the global serial handle */ 638 | serial = NULL; 639 | 640 | /* Put a space into location where we will be backspacing, for laziness */ 641 | NON_DEBUG_PRINTF( " " ); 642 | DEBUG_PRINTF( "...\n" ); 643 | 644 | /* Kick off threads for finding the right serial port */ 645 | for (unsigned int i = 0; i < 4; i++) 646 | { 647 | threadContexts[i].acio = this; 648 | threadContexts[i].port = comport[i]; 649 | threadHandles[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)initThread, &threadContexts[i], 0, NULL); 650 | } 651 | 652 | /* Try to open the reader indefinitely */ 653 | while( serial == NULL ) 654 | { 655 | /* Wait for threads to find the readers */ 656 | NON_DEBUG_PRINTF( "%s", pattern[((pno++) / 10) % 4] ); 657 | WAIT(); 658 | } 659 | 660 | /* Join on threads */ 661 | for (unsigned int i = 0; i < 4; i++) 662 | { 663 | WaitForSingleObject(threadHandles[i], INFINITE); 664 | } 665 | } 666 | -------------------------------------------------------------------------------- /ACIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /* Length of the card ID in bytes */ 7 | #define CARD_LENGTH 8 8 | 9 | typedef enum 10 | { 11 | STATE_FRONT_SENSOR, 12 | STATE_INSERTED, 13 | STATE_READ, 14 | STATE_REMOVED, 15 | STATE_UNKNOWN, 16 | } card_state_t; 17 | 18 | typedef enum 19 | { 20 | STATE_NOT_READY, 21 | STATE_GET_READY, 22 | STATE_READY, 23 | STATE_EJECT, 24 | } reader_state_t; 25 | 26 | typedef enum 27 | { 28 | KEY_NONE, 29 | KEY_1, 30 | KEY_2, 31 | KEY_3, 32 | KEY_4, 33 | KEY_5, 34 | KEY_6, 35 | KEY_7, 36 | KEY_8, 37 | KEY_9, 38 | KEY_0, 39 | KEY_00, 40 | KEY_BLANK, 41 | } reader_keypress_t; 42 | 43 | typedef enum 44 | { 45 | TYPE_UNKNOWN, 46 | TYPE_READER, 47 | TYPE_DISPENSER, 48 | TYPE_LEDBOARD, 49 | TYPE_IOBOARD, 50 | } reader_type_t; 51 | 52 | typedef struct 53 | { 54 | void *acio; 55 | const _TCHAR *port; 56 | } thread_context_t; 57 | 58 | class ACIO 59 | { 60 | public: 61 | ACIO(void); 62 | ~ACIO(void); 63 | 64 | unsigned int getCount() { return readerCount; } 65 | reader_keypress_t getPressedKey(int reader); 66 | void requestCardInsert(int reader); 67 | void requestCardEject(int reader); 68 | bool getCardInserted(int reader); 69 | void getCardID(int reader, unsigned char *cardId); 70 | 71 | private: 72 | unsigned int readerCount; 73 | HANDLE serial; 74 | unsigned int *old_keypresses; 75 | unsigned char cardId[CARD_LENGTH]; 76 | card_state_t state; 77 | 78 | HANDLE openSerial( const _TCHAR *arg, int baud ); 79 | const unsigned char * const getPacketData( 80 | unsigned int *readerId, 81 | unsigned int *command, 82 | unsigned int *len, 83 | unsigned int *checksum, 84 | const unsigned char * const packet, 85 | unsigned int length 86 | ); 87 | unsigned char calcPacketChecksum( const unsigned char * const data ); 88 | int probeReader( HANDLE serial ); 89 | unsigned int getReaderCount( HANDLE serial ); 90 | void initReader( HANDLE serial, unsigned int id, int whichInit ); 91 | const char * const getReaderVersion( HANDLE serial, unsigned int id ); 92 | reader_type_t getReaderType( HANDLE serial, unsigned int id ); 93 | void getReaderState( HANDLE serial, unsigned int id, card_state_t *state, unsigned int *keypresses, unsigned char *cardId ); 94 | void setReaderState( HANDLE serial, unsigned int id, reader_state_t state ); 95 | void requestCardId( HANDLE serial, unsigned int id ); 96 | 97 | void InitReaders(); 98 | void InitReader(const _TCHAR *comport); 99 | static DWORD initThread(LPVOID* param) 100 | { 101 | thread_context_t *tc = (thread_context_t *)param; 102 | ((ACIO *)tc->acio)->InitReader(tc->port); 103 | return 0; 104 | }; 105 | }; 106 | -------------------------------------------------------------------------------- /ACIOLauncher.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. 2 | #define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include "ACIO.h" 9 | #include "Menu.h" 10 | #include "Debug.h" 11 | 12 | /** 13 | * Define to 1 to enable card reader mode, currently only works with 14 | * slotted readers and will output card IDs with all 0's with wavepass 15 | * hardware. If this is enabled, hitting the blank button will toggle 16 | * card reader mode which allows for card IDs to be read off cards and 17 | * displayed. 18 | */ 19 | #define CARD_READER_MODE 0 20 | 21 | int _tmain(int argc, _TCHAR* argv[]) 22 | { 23 | /* Ensure command is good */ 24 | if( argc < 2 ) 25 | { 26 | fprintf( stderr, "Missing ini file argument!\n" ); 27 | return 1; 28 | } 29 | if( argc > 3 ) 30 | { 31 | fprintf( stderr, "Too many arguments specified!\n" ); 32 | return 1; 33 | } 34 | if( argc == 2 && wcscmp(argv[1], L"--debug") == 0) 35 | { 36 | fprintf( stderr, "Missing ini file argument!\n" ); 37 | return 1; 38 | } 39 | 40 | /* Optional arguments */ 41 | _TCHAR *inifile; 42 | if( argc == 3 ) 43 | { 44 | if (wcscmp(argv[2], L"--debug") == 0) 45 | { 46 | inifile = argv[1]; 47 | debug = true; 48 | } 49 | else if (wcscmp(argv[1], L"--debug") == 0) 50 | { 51 | inifile = argv[2]; 52 | debug = true; 53 | } 54 | else 55 | { 56 | fprintf( stderr, "Too many arguments specified!\n" ); 57 | return 1; 58 | } 59 | } 60 | else 61 | { 62 | inifile = argv[1]; 63 | } 64 | 65 | /* Display if we're debugging */ 66 | DEBUG_PRINTF( "Enabling debug mode!\n" ); 67 | 68 | /* Initialize menu */ 69 | Menu *menu = new Menu(inifile); 70 | 71 | if( menu->NumberOfEntries() < 1 ) 72 | { 73 | fprintf( stderr, "No games configured to launch!\n" ); 74 | return 1; 75 | } 76 | 77 | /* Walk serial chain, finding readers */ 78 | ACIO *readers = new ACIO(); 79 | 80 | /* Whether we're in read mode */ 81 | unsigned int read = 0; 82 | 83 | /* Actual game to load */ 84 | char *path = NULL; 85 | bool selected = false; 86 | 87 | /* It may have taken a long time to init */ 88 | menu->ResetTimeout(); 89 | 90 | /* Display user prompts */ 91 | menu->DisplayPrompt(); 92 | menu->DisplayGames(); 93 | 94 | /* Loop until time's up, then boot */ 95 | while( !selected ) 96 | { 97 | menu->Tick(); 98 | if (menu->ShouldBootDefault()) 99 | { 100 | printf( "No selection made, booting %s.\n", menu->GetSelectedName() ); 101 | path = menu->GetSelectedPath(); 102 | selected = true; 103 | break; 104 | } 105 | 106 | for( unsigned int x = 0; x < readers->getCount(); x++ ) 107 | { 108 | unsigned int game = 0; 109 | bool request_reader_toggle = false; 110 | 111 | switch(readers->getPressedKey(x)) { 112 | case KEY_1: 113 | game = 1; 114 | break; 115 | case KEY_2: 116 | game = 2; 117 | break; 118 | case KEY_3: 119 | game = 3; 120 | break; 121 | case KEY_4: 122 | game = 4; 123 | break; 124 | case KEY_5: 125 | game = 5; 126 | break; 127 | case KEY_6: 128 | game = 6; 129 | break; 130 | case KEY_7: 131 | game = 7; 132 | break; 133 | case KEY_8: 134 | game = 8; 135 | break; 136 | case KEY_9: 137 | game = 9; 138 | break; 139 | case KEY_0: 140 | menu->PageUp(); 141 | break; 142 | case KEY_00: 143 | menu->PageDown(); 144 | break; 145 | case KEY_BLANK: 146 | request_reader_toggle = true; 147 | break; 148 | } 149 | 150 | if (game != 0) 151 | { 152 | /* Chose a game! */ 153 | if (menu->SelectGame(game)) 154 | { 155 | /* Time to boot this game! */ 156 | printf( "Booting %s.\n", menu->GetSelectedName() ); 157 | path = menu->GetSelectedPath(); 158 | selected = true; 159 | break; 160 | } 161 | } 162 | 163 | if( CARD_READER_MODE && request_reader_toggle ) 164 | { 165 | /* Pressed empty button, go into/out of card read mode */ 166 | if (!read) 167 | { 168 | printf( "Entering card read mode, insert card!\n" ); 169 | for( unsigned int y = 0; y < readers->getCount(); y++ ) 170 | { 171 | readers->requestCardInsert(y); 172 | } 173 | read = 1; 174 | } 175 | else 176 | { 177 | printf( "Entering menu mode!\n" ); 178 | for( unsigned int y = 0; y < readers->getCount(); y++ ) 179 | { 180 | readers->requestCardEject(y); 181 | } 182 | read = 0; 183 | } 184 | break; 185 | } 186 | 187 | /* Only read cards if we are in card read mode */ 188 | if (!read) { continue; } 189 | 190 | /* Reset time, so we stay here forever */ 191 | menu->ResetTimeout(); 192 | 193 | if( readers->getCardInserted(x) ) 194 | { 195 | /* Ask for card */ 196 | unsigned char cardId[CARD_LENGTH]; 197 | readers->getCardID(x, cardId); 198 | 199 | /* Display card */ 200 | printf( "Card read: %02X%02X%02X%02X%02X%02X%02X%02X\n", 201 | cardId[0], cardId[1], cardId[2], cardId[3], 202 | cardId[4], cardId[5], cardId[6], cardId[7] ); 203 | 204 | readers->requestCardEject(x); 205 | readers->requestCardInsert(x); 206 | } 207 | } 208 | } 209 | 210 | /* Kill menu and ACIO connection */ 211 | delete readers; 212 | delete menu; 213 | 214 | if (path != NULL) 215 | { 216 | /* Launch actual game */ 217 | system(path); 218 | } 219 | 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /ACIOLauncher.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ACIOLauncher", "ACIOLauncher.vcproj", "{71B9AC26-5FBC-4131-A50B-C9AF69BF6EA6}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {71B9AC26-5FBC-4131-A50B-C9AF69BF6EA6}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {71B9AC26-5FBC-4131-A50B-C9AF69BF6EA6}.Debug|Win32.Build.0 = Debug|Win32 14 | {71B9AC26-5FBC-4131-A50B-C9AF69BF6EA6}.Release|Win32.ActiveCfg = Release|Win32 15 | {71B9AC26-5FBC-4131-A50B-C9AF69BF6EA6}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /ACIOLauncher.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 26 | 29 | 32 | 35 | 38 | 41 | 52 | 55 | 58 | 61 | 68 | 71 | 74 | 77 | 80 | 83 | 86 | 89 | 90 | 98 | 101 | 104 | 107 | 110 | 113 | 124 | 127 | 130 | 133 | 142 | 145 | 148 | 151 | 154 | 157 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | 173 | 176 | 177 | 180 | 181 | 184 | 185 | 188 | 189 | 190 | 195 | 198 | 199 | 202 | 203 | 206 | 207 | 208 | 213 | 214 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /Debug.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Debug.h" 4 | 5 | /* Debug global which can be set via command line flag --debug */ 6 | bool debug = false; 7 | 8 | void printHex(const unsigned char * const data, int length ) 9 | { 10 | printf( "Length: %d bytes\n", length ); 11 | 12 | for( int x = 0; x < length; x++ ) 13 | { 14 | printf( "%02X ", data[x] ); 15 | } 16 | 17 | printf( "\n" ); 18 | } -------------------------------------------------------------------------------- /Debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* Define to 1 to get verbose debugging */ 4 | #define VERBOSE_DEBUG 0 5 | 6 | /* Debug global which can be set via command line flag --debug */ 7 | extern bool debug; 8 | 9 | /* Debug macros that are enabled via the --debug flag */ 10 | #define DEBUG_PRINTF(...) do { if(debug) { printf(__VA_ARGS__); } } while(0) 11 | #define DEBUG_PRINT_HEX(data, length) do { if(debug) { printHex(data, length); } } while(0) 12 | 13 | /* Macros that are enabled when debug is not enabled */ 14 | #define NON_DEBUG_PRINTF(...) do { if(!debug) { printf(__VA_ARGS__); } } while(0) 15 | 16 | void printHex(const unsigned char * const data, int length ); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Menu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Menu.h" 5 | #include "Debug.h" 6 | 7 | Menu::Menu(_TCHAR *inifile) 8 | { 9 | /* Read settings */ 10 | settings = LoadSettings( inifile, &num_programs ); 11 | 12 | /* Set up pagination */ 13 | start = 0; 14 | end = num_programs < GAMES_PER_PAGE ? num_programs : GAMES_PER_PAGE; 15 | selected = 0; 16 | 17 | /* For exiting on defaults */ 18 | ftime(&beginning); 19 | 20 | /* No cursor please! */ 21 | HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 22 | CONSOLE_CURSOR_INFO cursorInfo; 23 | 24 | GetConsoleCursorInfo(out, &cursorInfo); 25 | cursorInfo.bVisible = false; 26 | SetConsoleCursorInfo(out, &cursorInfo); 27 | } 28 | 29 | Menu::~Menu(void) 30 | { 31 | HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 32 | CONSOLE_CURSOR_INFO cursorInfo; 33 | 34 | GetConsoleCursorInfo(out, &cursorInfo); 35 | cursorInfo.bVisible = true; 36 | SetConsoleCursorInfo(out, &cursorInfo); 37 | } 38 | 39 | void Menu::DisplayPrompt() 40 | { 41 | /* Prompt the user */ 42 | if (!debug) { system("cls"); } 43 | printf( "Make a selection on the reader to boot a game.\n" ); 44 | printf( "%s will boot in %d seconds.\n\n", settings[0].name, TIMEOUT_SECONDS ); 45 | } 46 | 47 | void Menu::DisplayGames() 48 | { 49 | for (unsigned int i = start; i < end; i++) 50 | { 51 | printf( "[%d] %s\n", (i - start) + 1, settings[i].name ); 52 | } 53 | 54 | printf( "\n" ); 55 | 56 | if (start > 0) 57 | { 58 | printf( "[0] Previous Page\n" ); 59 | } 60 | 61 | if (end < num_programs) 62 | { 63 | printf( "[00] Next Page\n" ); 64 | } 65 | } 66 | 67 | void Menu::PageDown() 68 | { 69 | if (end < num_programs) 70 | { 71 | /* Move up one page */ 72 | start = start + GAMES_PER_PAGE; 73 | end = start + GAMES_PER_PAGE; 74 | end = end >= num_programs ? num_programs : end; 75 | 76 | /* print games */ 77 | DisplayPrompt(); 78 | DisplayGames(); 79 | } 80 | } 81 | 82 | void Menu::PageUp() 83 | { 84 | if (start > 0) 85 | { 86 | /* Move up one page */ 87 | start = start - GAMES_PER_PAGE; 88 | start = start < 0 ? 0 : start; 89 | end = start + GAMES_PER_PAGE; 90 | end = end >= num_programs ? num_programs : end; 91 | 92 | /* print games */ 93 | DisplayPrompt(); 94 | DisplayGames(); 95 | } 96 | } 97 | 98 | void Menu::ResetTimeout() 99 | { 100 | ftime(&beginning); 101 | } 102 | 103 | void Menu::Tick() 104 | { 105 | ftime(¤t); 106 | } 107 | 108 | bool Menu::ShouldBootDefault() 109 | { 110 | ftime(¤t); 111 | return (current.time - beginning.time) > TIMEOUT_SECONDS; 112 | } 113 | 114 | bool Menu::SelectGame(unsigned int game) 115 | { 116 | if (game < 1 || game > 9) 117 | { 118 | /* Out of bounds */ 119 | return false; 120 | } 121 | 122 | /* Zero-based instead of one-based, and account for pagination */ 123 | game--; 124 | game += start; 125 | 126 | if (game >= num_programs) 127 | { 128 | return false; 129 | } 130 | else 131 | { 132 | selected = game; 133 | return true; 134 | } 135 | } 136 | 137 | /** 138 | * Loads an INI file with the following format: 139 | * 140 | * [Name of game to launch] 141 | * launch= 142 | */ 143 | launcher_program_t *Menu::LoadSettings( _TCHAR *ini_file, unsigned int *final_length ) 144 | { 145 | launcher_program_t *progs = 0; 146 | *final_length = 0; 147 | unsigned int got_name = 0; 148 | launcher_program_t temp; 149 | 150 | // Open the file 151 | HANDLE hFile = CreateFile(ini_file, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 152 | char buffer[16384]; 153 | unsigned int eof = 0; 154 | unsigned int eol = 0; 155 | unsigned int buflen = 0; 156 | 157 | if (hFile == 0) 158 | { 159 | return progs; 160 | } 161 | 162 | memset( &temp, 0, sizeof(temp) ); 163 | memset( buffer, 0, sizeof(buffer) ); 164 | 165 | while( !eof ) 166 | { 167 | DWORD length; 168 | ReadFile( hFile, buffer + buflen, 1, &length, 0 ); 169 | if (length == 0) 170 | { 171 | eof = 1; 172 | eol = 1; 173 | } 174 | else if( *(buffer + buflen) == '\r' ) 175 | { 176 | /* Ignore \r completely */ 177 | *(buffer + buflen) = 0; 178 | } 179 | else if( *(buffer + buflen) == '\n' ) 180 | { 181 | /* End of line */ 182 | *(buffer + buflen) = 0; 183 | eol = 1; 184 | } 185 | else 186 | { 187 | /* Valid thing */ 188 | buflen++; 189 | } 190 | 191 | if ( eol == 1 ) 192 | { 193 | /* Process line */ 194 | if (buffer[0] == '[' && buffer[buflen - 1] == ']' && buflen > 2) 195 | { 196 | buffer[buflen - 1] = 0; 197 | char *game = buffer + 1; 198 | 199 | /* Copy this into temp structure */ 200 | strcpy_s( temp.name, MAX_GAME_NAME_LENGTH, game ); 201 | got_name = 1; 202 | } 203 | else 204 | { 205 | if (strncmp(buffer, "launch", 6) == 0) { 206 | unsigned int loc = 6; 207 | // Find equals sign after space 208 | while (loc < buflen && (buffer[loc] == ' ' || buffer[loc] == '\t')) { loc++; } 209 | if (loc < buflen) 210 | { 211 | if (buffer[loc] == '=') 212 | { 213 | loc++; 214 | while (loc < buflen && (buffer[loc] == ' ' || buffer[loc] == '\t')) { loc++; } 215 | if (loc < buflen) 216 | { 217 | char *launch = buffer + loc; 218 | 219 | if( got_name == 1 ) 220 | { 221 | /* We have a name to associate with this */ 222 | strcpy_s( temp.location, MAX_GAME_LOCATION_LENGTH, launch ); 223 | got_name = 0; 224 | 225 | /* Make a new spot for this, copy in */ 226 | (*final_length)++; 227 | progs = (launcher_program_t *)realloc( progs, sizeof(launcher_program_t) * (*final_length) ); 228 | memcpy( progs + ((*final_length) - 1), &temp, sizeof(launcher_program_t) ); 229 | memset( &temp, 0, sizeof(temp) ); 230 | } 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | /* Reset buffer */ 238 | if (buflen > 0) 239 | { 240 | memset( buffer, 0, sizeof(buffer) ); 241 | buflen = 0; 242 | } 243 | 244 | /* Not end of line anymore */ 245 | eol = 0; 246 | } 247 | } 248 | 249 | return progs; 250 | } 251 | -------------------------------------------------------------------------------- /Menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /* Seconds to wait for a selection before booting the default option */ 7 | #define TIMEOUT_SECONDS 60 8 | 9 | /* Constants for limitations on how long INI file pieces can be */ 10 | #define MAX_GAME_NAME_LENGTH 63 11 | #define MAX_GAME_LOCATION_LENGTH 511 12 | 13 | /* Number of games to display on one screen */ 14 | #define GAMES_PER_PAGE 9 15 | 16 | typedef struct 17 | { 18 | char location[MAX_GAME_LOCATION_LENGTH + 1]; 19 | char name[MAX_GAME_NAME_LENGTH + 1]; 20 | } launcher_program_t; 21 | 22 | class Menu 23 | { 24 | public: 25 | Menu(_TCHAR *inifile); 26 | ~Menu(void); 27 | 28 | unsigned int NumberOfEntries() { return num_programs; } 29 | char *GetSelectedName() { return settings[selected].name; } 30 | char *GetSelectedPath() { return settings[selected].location; } 31 | void DisplayPrompt(); 32 | void DisplayGames(); 33 | bool SelectGame(unsigned int game); 34 | void PageUp(); 35 | void PageDown(); 36 | 37 | void Tick(); 38 | void ResetTimeout(); 39 | bool ShouldBootDefault(); 40 | private: 41 | unsigned int num_programs; 42 | unsigned int start; 43 | unsigned int end; 44 | unsigned int selected; 45 | launcher_program_t *settings; 46 | struct timeb beginning; 47 | struct timeb current; 48 | 49 | launcher_program_t *LoadSettings( _TCHAR *ini_file, unsigned int *final_length ); 50 | }; 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACIOLauncher 2 | 3 | A launcher that takes input from an ACIO reader attached via serial to a cabinet. 4 | 5 | This will initialize either slotted or wavepass reader hardware, plugged into any standard COM port on actual BEMANI hardware. Once initialized, it will present a menu of games to launch, based on a configured ini file. If a game is not selected, it will boot the first option after 60 seconds. 6 | 7 | The project provided is a Visual Studio 2008 project that produces a statically-compiled executable which is ready to run on embedded XP. This is suitable for execution on the operating system that comes with various BEMANI games. 8 | 9 | The ini file format is simple. For each game, there should be a section which is the game name. For each section, there should be a "launch" key which points to the full path of the executable or batch file to execute when selecting this option. An example is below: 10 | 11 | ``` 12 | [Lincle] 13 | launch=D:\LincleData\contents\gamestart.bat 14 | 15 | [Resort Anthem] 16 | launch=D:\RAData\contents\gamestart.bat 17 | 18 | [Sirius] 19 | launch=D:\Sirius\contents\gamestart.bat 20 | ``` 21 | 22 | To correctly execute the built code, run the executable with one parameter specifying the location of the INI file. Optionally, you can add the `--debug` attribute for additional debugging. An example invocation is as follows: 23 | 24 | ``` 25 | ACIOLauncher.exe games.ini 26 | ``` --------------------------------------------------------------------------------