mirror of
https://gitea.tendokyu.moe/Hay1tsme/segatools.git
synced 2026-05-14 07:00:47 -05:00
This adds a setting to the global configuration to allow only input when the game is focused. This prevents issues, such as triggering game buttons or service buttons while being tabbed out. Turned off by default. The implementation is not the best, but in the context how these games are structured, the most acceptable. It will keep scanning if the foreground window title fully matches (some games partially match, like FGO because due to the revision number in the title), and if found, will grab the HWND of that window and compare that from then on, to not tank performance due to constant string operations. Tested with FGO, chusan and ongeki. Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/83 Reviewed-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe> Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
723 lines
15 KiB
C
723 lines
15 KiB
C
/*
|
|
Sega "Type 3" JVS I/O emulator
|
|
|
|
Credits:
|
|
|
|
Protocol docs:
|
|
https://github.com/TheOnlyJoey/openjvs/wiki/ (a/o October 2018)
|
|
|
|
Capability dumps:
|
|
https://wiki.arcadeotaku.com/w/JVS#Sega_837-14572 (a/o October 2018)
|
|
*/
|
|
|
|
#include <windows.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "board/io3.h"
|
|
|
|
#include "jvs/jvs-bus.h"
|
|
#include "jvs/jvs-cmd.h"
|
|
#include "jvs/jvs-util.h"
|
|
|
|
#include "util/dprintf.h"
|
|
#include "util/dump.h"
|
|
#include "util/fg-detect.h"
|
|
|
|
static void io3_transact(
|
|
struct jvs_node *node,
|
|
const void *bytes,
|
|
size_t nbytes,
|
|
struct iobuf *resp);
|
|
|
|
static bool io3_sense(struct jvs_node *node);
|
|
|
|
static HRESULT io3_cmd(
|
|
void *ctx,
|
|
struct const_iobuf *req,
|
|
struct iobuf *resp);
|
|
|
|
static HRESULT io3_cmd_read_id(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_get_cmd_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_get_jvs_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_get_comm_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_get_features(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_read_switches(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_read_coin(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_read_analogs(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_read_rotarys(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_write_gpio(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *buf);
|
|
|
|
static HRESULT io3_cmd_assign_addr(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf);
|
|
|
|
static const uint8_t io3_ident[] =
|
|
"SEGA CORPORATION;I/O BD JVS;837-14572;Ver1.00;2005/10";
|
|
|
|
static uint8_t io3_features[] = {
|
|
/* Feature : 0x01 : Players and switches
|
|
Param1 : 2 : Number of players
|
|
Param2 : 14 : Number of switches per player
|
|
Param3 : 0 : N/A */
|
|
|
|
0x01, 2, 14, 0,
|
|
|
|
/* Feature : 0x02 : Coin slots
|
|
Param1 : 2 : Number of coin slots
|
|
Param2 : 0 : N/A
|
|
Param3 : 0 : N/A */
|
|
|
|
0x02, 2, 0, 0,
|
|
|
|
/* Feature : 0x03 : Analog inputs
|
|
Param1 : 8 : Number of ADC channels
|
|
Param2 : 10 : Effective bits of resolution per ADC
|
|
Param3 : 0 : N/A */
|
|
|
|
0x03, 8, 10, 0,
|
|
|
|
/* Feature : 0x04 : Rotary inputs
|
|
Param1 : 4 : Number of rotary channels
|
|
Param2 : 0 : N/A
|
|
Param3 : 0 : N/A */
|
|
|
|
0x04, 4, 0, 0,
|
|
|
|
/* Feature : 0x12 : GPIO outputs
|
|
Param1 : 3 : Number of ports (8 bits per port)
|
|
Param2 : 0 : N/A
|
|
Param3 : 0 : N/A
|
|
|
|
NOTE: This particular port count is what an IO-4 attached over JVS
|
|
advertises, an IO-3 only advertises 3. Still, this seems to be backwards
|
|
compatible with games that expect an IO-3, and the protocols seem to be
|
|
identical otherwise. */
|
|
|
|
0x12, 20, 0, 0,
|
|
|
|
/* Feature : 0x00 : End of capabilities */
|
|
|
|
0x00,
|
|
};
|
|
|
|
static struct io3_switch_state prev_state = {0};
|
|
|
|
void io3_init(
|
|
struct io3 *io3,
|
|
struct jvs_node *next,
|
|
const struct io3_ops *ops,
|
|
void *ops_ctx)
|
|
{
|
|
assert(io3 != NULL);
|
|
assert(ops != NULL);
|
|
|
|
io3->jvs.next = next;
|
|
io3->jvs.transact = io3_transact;
|
|
io3->jvs.sense = io3_sense;
|
|
io3->addr = 0xFF;
|
|
io3->ops = ops;
|
|
io3->ops_ctx = ops_ctx;
|
|
}
|
|
|
|
struct jvs_node *io3_to_jvs_node(struct io3 *io3)
|
|
{
|
|
assert(io3 != NULL);
|
|
|
|
return &io3->jvs;
|
|
}
|
|
|
|
static void io3_transact(
|
|
struct jvs_node *node,
|
|
const void *bytes,
|
|
size_t nbytes,
|
|
struct iobuf *resp)
|
|
{
|
|
struct io3 *io3;
|
|
|
|
assert(node != NULL);
|
|
assert(bytes != NULL);
|
|
assert(resp != NULL);
|
|
|
|
io3 = CONTAINING_RECORD(node, struct io3, jvs);
|
|
|
|
jvs_crack_request(bytes, nbytes, resp, io3->addr, io3_cmd, io3);
|
|
}
|
|
|
|
static bool io3_sense(struct jvs_node *node)
|
|
{
|
|
struct io3 *io3;
|
|
|
|
assert(node != NULL);
|
|
|
|
io3 = CONTAINING_RECORD(node, struct io3, jvs);
|
|
|
|
return io3->addr == 0xFF;
|
|
}
|
|
|
|
static HRESULT io3_cmd(
|
|
void *ctx,
|
|
struct const_iobuf *req,
|
|
struct iobuf *resp)
|
|
{
|
|
struct io3 *io3;
|
|
|
|
io3 = ctx;
|
|
|
|
switch (req->bytes[req->pos]) {
|
|
case JVS_CMD_READ_ID:
|
|
return io3_cmd_read_id(io3, req, resp);
|
|
|
|
case JVS_CMD_GET_CMD_VERSION:
|
|
return io3_cmd_get_cmd_version(io3, req, resp);
|
|
|
|
case JVS_CMD_GET_JVS_VERSION:
|
|
return io3_cmd_get_jvs_version(io3, req, resp);
|
|
|
|
case JVS_CMD_GET_COMM_VERSION:
|
|
return io3_cmd_get_comm_version(io3, req, resp);
|
|
|
|
case JVS_CMD_GET_FEATURES:
|
|
return io3_cmd_get_features(io3, req, resp);
|
|
|
|
case JVS_CMD_READ_SWITCHES:
|
|
return io3_cmd_read_switches(io3, req, resp);
|
|
|
|
case JVS_CMD_READ_COIN:
|
|
return io3_cmd_read_coin(io3, req, resp);
|
|
|
|
case JVS_CMD_READ_ANALOGS:
|
|
return io3_cmd_read_analogs(io3, req, resp);
|
|
|
|
case JVS_CMD_READ_ROTARYS:
|
|
return io3_cmd_read_rotarys(io3, req, resp);
|
|
|
|
case JVS_CMD_WRITE_GPIO:
|
|
return io3_cmd_write_gpio(io3, req, resp);
|
|
|
|
case JVS_CMD_RESET:
|
|
return io3_cmd_reset(io3, req);
|
|
|
|
case JVS_CMD_ASSIGN_ADDR:
|
|
return io3_cmd_assign_addr(io3, req, resp);
|
|
|
|
default:
|
|
dprintf("JVS I/O: Node %02x: Unhandled command byte %02x\n",
|
|
io3->addr,
|
|
req->bytes[req->pos]);
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
}
|
|
|
|
static HRESULT io3_cmd_read_id(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t req;
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read_8(req_buf, &req);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Read ID\n");
|
|
|
|
/* Write report byte */
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
/* Write the identification string. The NUL terminator at the end of this C
|
|
string is also sent, and it naturally terminates the response chunk. */
|
|
|
|
return iobuf_write(resp_buf, io3_ident, sizeof(io3_ident));
|
|
}
|
|
|
|
static HRESULT io3_cmd_get_cmd_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t req;
|
|
uint8_t resp[2];
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read_8(req_buf, &req);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Get command format version\n");
|
|
resp[0] = 0x01; /* Report byte */
|
|
resp[1] = 0x13; /* Command format version BCD */
|
|
|
|
return iobuf_write(resp_buf, resp, sizeof(resp));
|
|
}
|
|
|
|
static HRESULT io3_cmd_get_jvs_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t req;
|
|
uint8_t resp[2];
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read_8(req_buf, &req);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Get JVS version\n");
|
|
resp[0] = 0x01; /* Report byte */
|
|
resp[1] = 0x20; /* JVS version BCD */
|
|
|
|
return iobuf_write(resp_buf, resp, sizeof(resp));
|
|
}
|
|
|
|
static HRESULT io3_cmd_get_comm_version(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t req;
|
|
uint8_t resp[2];
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read_8(req_buf, &req);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Get communication version\n");
|
|
resp[0] = 0x01; /* Report byte */
|
|
resp[1] = 0x10; /* "Communication version" BCD */
|
|
|
|
return iobuf_write(resp_buf, resp, sizeof(resp));
|
|
}
|
|
|
|
static HRESULT io3_cmd_get_features(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t req;
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read_8(req_buf, &req);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Get features\n");
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01); /* Write report byte */
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return iobuf_write(resp_buf, io3_features, sizeof(io3_features));
|
|
}
|
|
|
|
static HRESULT io3_cmd_read_switches(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
struct jvs_req_read_switches req;
|
|
struct io3_switch_state state;
|
|
HRESULT hr;
|
|
|
|
/* Read req */
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
#if defined(LOG_IO3)
|
|
dprintf("JVS I/O: Read switches, np=%i, bpp=%i\n",
|
|
req.num_players,
|
|
req.bytes_per_player);
|
|
#endif
|
|
|
|
if (req.num_players > 2 || req.bytes_per_player != 2) {
|
|
dprintf("JVS I/O: Invalid read size "
|
|
"num_players=%i "
|
|
"bytes_per_player=%i\n",
|
|
req.num_players,
|
|
req.bytes_per_player);
|
|
hr = iobuf_write_8(resp_buf, 0x02);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
/* Build response */
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01); /* Report byte */
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
|
|
if (io3->ops != NULL) {
|
|
fgdet_poll();
|
|
if (fgdet_in_foreground()) { // returns true if fgdet is not enabled
|
|
io3->ops->read_switches(io3->ops_ctx, &state);
|
|
memcpy(&prev_state, &state, sizeof(state));
|
|
} else {
|
|
state = prev_state;
|
|
}
|
|
}
|
|
|
|
hr = iobuf_write_8(resp_buf, state.system); /* Test, Tilt lines */
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (req.num_players > 0) {
|
|
hr = iobuf_write_be16(resp_buf, state.p1);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (req.num_players > 1) {
|
|
hr = iobuf_write_be16(resp_buf, state.p2);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT io3_cmd_read_coin(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
struct jvs_req_read_coin req;
|
|
uint16_t ncoins;
|
|
uint8_t i;
|
|
HRESULT hr;
|
|
|
|
/* Read req */
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
//dprintf("JVS I/O: Read coin, nslots=%i\n", req.nslots);
|
|
|
|
/* Write report byte */
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
/* Write slot detail */
|
|
|
|
for (i = 0 ; i < req.nslots ; i++) {
|
|
ncoins = 0;
|
|
|
|
if (io3->ops->read_coin_counter != NULL) {
|
|
io3->ops->read_coin_counter(io3->ops_ctx, i, &ncoins);
|
|
}
|
|
|
|
hr = iobuf_write_be16(resp_buf, ncoins);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT io3_cmd_read_analogs(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
struct jvs_req_read_analogs req;
|
|
uint16_t analogs[8];
|
|
uint8_t i;
|
|
HRESULT hr;
|
|
|
|
/* Read req */
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (req.nanalogs > _countof(analogs)) {
|
|
dprintf("JVS I/O: Invalid analog count %i\n", req.nanalogs);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
//dprintf("JVS I/O: Read analogs, nanalogs=%i\n", req.nanalogs);
|
|
|
|
/* Write report byte */
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
/* Write analogs */
|
|
|
|
memset(analogs, 0, sizeof(analogs));
|
|
|
|
if (io3->ops->read_analogs != NULL) {
|
|
io3->ops->read_analogs(io3->ops_ctx, analogs, req.nanalogs);
|
|
}
|
|
|
|
for (i = 0 ; i < req.nanalogs ; i++) {
|
|
hr = iobuf_write_be16(resp_buf, analogs[i]);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
static HRESULT io3_cmd_read_rotarys(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
struct jvs_req_read_rotarys req;
|
|
uint16_t rotarys[4];
|
|
uint8_t i;
|
|
HRESULT hr;
|
|
|
|
/* Read req */
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (req.nrotarys > _countof(rotarys)) {
|
|
dprintf("JVS I/O: Invalid analog count %i\n", req.nrotarys);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
//dprintf("JVS I/O: Read rotarys, nrotarys=%i\n", req.nrotarys);
|
|
|
|
/* Write report byte */
|
|
|
|
hr = iobuf_write_8(resp_buf, 0x01);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
/* Write analogs */
|
|
|
|
memset(rotarys, 0, sizeof(rotarys));
|
|
|
|
if (io3->ops->read_rotarys != NULL) {
|
|
io3->ops->read_rotarys(io3->ops_ctx, rotarys, req.nrotarys);
|
|
}
|
|
|
|
for (i = 0 ; i < req.nrotarys ; i++) {
|
|
hr = iobuf_write_be16(resp_buf, rotarys[i]);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
static HRESULT io3_cmd_write_gpio(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
uint8_t cmd;
|
|
uint8_t nbytes;
|
|
uint8_t bytes[3];
|
|
HRESULT hr;
|
|
|
|
/* Read request header */
|
|
|
|
hr = iobuf_read_8(req_buf, &cmd);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = iobuf_read_8(req_buf, &nbytes);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (nbytes > 3) {
|
|
dprintf("JVS I/O: Invalid GPIO write size %i\n", nbytes);
|
|
hr = iobuf_write_8(resp_buf, 0x02);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
/* Read payload */
|
|
|
|
memset(bytes, 0, sizeof(bytes));
|
|
hr = iobuf_read(req_buf, bytes, nbytes);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (io3->ops->write_gpio != NULL) {
|
|
io3->ops->write_gpio(
|
|
io3->ops_ctx,
|
|
bytes[0] | (bytes[1] << 8) | (bytes[2] << 16));
|
|
}
|
|
|
|
/* Write report byte */
|
|
|
|
return iobuf_write_8(resp_buf, 0x01);
|
|
}
|
|
|
|
static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *req_buf)
|
|
{
|
|
struct jvs_req_reset req;
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
dprintf("JVS I/O: Reset (param %02x)\n", req.unknown);
|
|
io3->addr = 0xFF;
|
|
|
|
if (io3->ops->reset != NULL) {
|
|
io3->ops->reset(io3->ops_ctx);
|
|
}
|
|
|
|
/* No ack for this since it really is addressed to everybody */
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT io3_cmd_assign_addr(
|
|
struct io3 *io3,
|
|
struct const_iobuf *req_buf,
|
|
struct iobuf *resp_buf)
|
|
{
|
|
struct jvs_req_assign_addr req;
|
|
bool sense;
|
|
HRESULT hr;
|
|
|
|
hr = iobuf_read(req_buf, &req, sizeof(req));
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
sense = jvs_node_sense(io3->jvs.next);
|
|
dprintf("JVS I/O: Assign addr %02x sense %i\n", req.addr, sense);
|
|
|
|
if (sense) {
|
|
/* That address is for somebody else */
|
|
return S_OK;
|
|
}
|
|
|
|
io3->addr = req.addr;
|
|
|
|
return iobuf_write_8(resp_buf, 0x01);
|
|
}
|