naomi: hopper support for kick'4'cash, shootout pool and club kart prize
high level emulation of 837-14438 and naomi SWP hopper boards
This commit is contained in:
parent
8e3a48eb6f
commit
8c0f543bc8
|
@ -883,6 +883,8 @@ target_sources(${PROJECT_NAME} PRIVATE
|
|||
core/hw/naomi/printer.cpp
|
||||
core/hw/naomi/systemsp.h
|
||||
core/hw/naomi/systemsp.cpp
|
||||
core/hw/naomi/hopper.h
|
||||
core/hw/naomi/hopper.cpp
|
||||
core/hw/pvr/elan.cpp
|
||||
core/hw/pvr/elan.h
|
||||
core/hw/pvr/elan_struct.h
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
Copyright 2023 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "hopper.h"
|
||||
#include "network/ggpo.h"
|
||||
#include "input/gamepad.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "hw/sh4/modules/modules.h"
|
||||
#include "serialize.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace hopper
|
||||
{
|
||||
|
||||
class BaseHopper : public SerialPipe
|
||||
{
|
||||
public:
|
||||
BaseHopper() {
|
||||
schedId = sh4_sched_register(0, schedCallback, this);
|
||||
sh4_sched_request(schedId, SCHED_CYCLES);
|
||||
}
|
||||
virtual ~BaseHopper() {
|
||||
sh4_sched_unregister(schedId);
|
||||
}
|
||||
|
||||
u8 read() override
|
||||
{
|
||||
if (toSend.empty())
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
u8 v = toSend.front();
|
||||
toSend.pop_front();
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
int available() override {
|
||||
return toSend.size();
|
||||
}
|
||||
|
||||
void write(u8 data) override
|
||||
{
|
||||
if (recvBuffer.empty() && data != 'H') {
|
||||
WARN_LOG(NAOMI, "Ignored data %02x %c", data, data);
|
||||
return;
|
||||
}
|
||||
recvBuffer.push_back(data);
|
||||
if (recvBuffer.size() == 3)
|
||||
expectedBytes = data;
|
||||
else if (recvBuffer.size() == 4)
|
||||
expectedBytes += data << 8;
|
||||
else if (expectedBytes > 0 && recvBuffer.size() == expectedBytes)
|
||||
{
|
||||
handleMessage(recvBuffer[1]);
|
||||
recvBuffer.clear();
|
||||
expectedBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void serialize(Serializer& ser)
|
||||
{
|
||||
ser << (u32)recvBuffer.size();
|
||||
ser.serialize(recvBuffer.data(), recvBuffer.size());
|
||||
ser << credit0;
|
||||
ser << credit1;
|
||||
ser << totalCredit;
|
||||
ser << premium;
|
||||
ser << gameNumber;
|
||||
ser << freePlay;
|
||||
ser << autoPayOut;
|
||||
ser << autoExchange;
|
||||
ser << twoWayMode;
|
||||
ser << coinDisc;
|
||||
ser << currency;
|
||||
ser << medalExchRate;
|
||||
ser << maxHopperFloat;
|
||||
ser << maxPay;
|
||||
ser << maxCredit;
|
||||
ser << hopperSize;
|
||||
ser << maxBet;
|
||||
ser << minBet;
|
||||
ser << addBet;
|
||||
ser << expectedBytes;
|
||||
ser << (u32)toSend.size();
|
||||
for (u8 b : toSend)
|
||||
ser << b;
|
||||
ser << started;
|
||||
sh4_sched_serialize(ser, schedId);
|
||||
}
|
||||
|
||||
virtual void deserialize(Deserializer& deser)
|
||||
{
|
||||
u32 size;
|
||||
deser >> size;
|
||||
recvBuffer.resize(size);
|
||||
deser.deserialize(recvBuffer.data(), size);
|
||||
deser >> credit0;
|
||||
deser >> credit1;
|
||||
deser >> totalCredit;
|
||||
deser >> premium;
|
||||
deser >> gameNumber;
|
||||
deser >> freePlay;
|
||||
deser >> autoPayOut;
|
||||
deser >> autoExchange;
|
||||
deser >> twoWayMode;
|
||||
deser >> coinDisc;
|
||||
deser >> currency;
|
||||
deser >> medalExchRate;
|
||||
deser >> maxHopperFloat;
|
||||
deser >> maxPay;
|
||||
deser >> maxCredit;
|
||||
deser >> hopperSize;
|
||||
deser >> maxBet;
|
||||
deser >> minBet;
|
||||
deser >> addBet;
|
||||
deser >> expectedBytes;
|
||||
deser >> size;
|
||||
toSend.clear();
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
u8 b;
|
||||
deser >> b;
|
||||
toSend.push_back(b);
|
||||
}
|
||||
deser >> started;
|
||||
sh4_sched_deserialize(deser, schedId);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void handleMessage(u8 command) = 0;
|
||||
virtual void sendCoinInMessage() = 0;
|
||||
|
||||
void sendMessage(u8 command, const u8 *payload, size_t len)
|
||||
{
|
||||
DEBUG_LOG(NAOMI, "hopper sending command %x size %x", command, (int)len + 5);
|
||||
// 'H' <u8 command> <u16 length> payload ... <u8 checksum>
|
||||
u8 chksum = 'H' + command;
|
||||
toSend.push_back('H');
|
||||
toSend.push_back(command);
|
||||
len += 5;
|
||||
toSend.push_back(len & 0xff);
|
||||
chksum += len & 0xff;
|
||||
toSend.push_back(len >> 8);
|
||||
chksum += len >> 8;
|
||||
len -= 5;
|
||||
for (size_t i = 0; i < len; i++, payload++) {
|
||||
toSend.push_back(*payload);
|
||||
chksum += *payload;
|
||||
}
|
||||
toSend.push_back(chksum);
|
||||
serial_updateStatusRegister();
|
||||
}
|
||||
|
||||
std::vector<u8> recvBuffer;
|
||||
u32 credit0 = 0;
|
||||
u32 credit1 = 0;
|
||||
u32 totalCredit = 0;
|
||||
u32 premium = 0;
|
||||
u32 gameNumber = 0;
|
||||
bool freePlay = false;
|
||||
bool autoPayOut = false;
|
||||
bool autoExchange = false;
|
||||
bool twoWayMode = true;
|
||||
bool coinDisc = true;
|
||||
u8 currency = 0; // 0:medal, 1:pound, 2:dollar, 3:euro, 4:token
|
||||
u8 medalExchRate = 5;
|
||||
u32 maxHopperFloat = 200;
|
||||
u32 maxPay = 1999900;
|
||||
u32 maxCredit = 1999900;
|
||||
u32 hopperSize = 39900;
|
||||
u32 maxBet = 10000;
|
||||
u32 minBet = 100; // normally 1000
|
||||
u32 addBet = 100;
|
||||
|
||||
int schedId;
|
||||
bool started = false;
|
||||
bool coinKey = false;
|
||||
|
||||
static constexpr u32 SCHED_CYCLES = SH4_MAIN_CLOCK / 60;
|
||||
|
||||
private:
|
||||
static int schedCallback(int tag, int cycles, int jitter, void *p)
|
||||
{
|
||||
BaseHopper *board = (BaseHopper *)p;
|
||||
if (board->started)
|
||||
{
|
||||
// button D is coin
|
||||
if ((mapleInputState[0].kcode & DC_BTN_D) == 0 && !board->coinKey)
|
||||
board->sendCoinInMessage();
|
||||
board->coinKey = (mapleInputState[0].kcode & DC_BTN_D) == 0;
|
||||
}
|
||||
return SCHED_CYCLES;
|
||||
}
|
||||
|
||||
u32 expectedBytes = 0;
|
||||
std::deque<u8> toSend;
|
||||
};
|
||||
|
||||
//
|
||||
// SEGA 837-14438 hopper board
|
||||
//
|
||||
// Used by kick'4'cash
|
||||
// Can be used by club kart prize (ver.b) and shootout pool prize (ver.b) if P1 btn 0 is pressed at boot
|
||||
//
|
||||
class Sega837_14438Hopper : public BaseHopper
|
||||
{
|
||||
protected:
|
||||
void handleMessage(u8 command) override
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case 0x40:
|
||||
{
|
||||
// VERSION
|
||||
INFO_LOG(NAOMI, "hopper received VERSION");
|
||||
std::array<u32, 4> payload{};
|
||||
payload[0] = 0x00010001;
|
||||
payload[1] = 3;
|
||||
sendMessage(0x20, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x41:
|
||||
{
|
||||
// POWER ON
|
||||
INFO_LOG(NAOMI, "hopper received POWER ON");
|
||||
std::array<u32, 0x1ea> payload{};
|
||||
payload[0] = gameNumber; // game number
|
||||
payload[1] = 0; // atp num
|
||||
payload[2] = 0; // atp kind and error code
|
||||
payload[3] = status; // status bitfield. rectangle in background if != 0
|
||||
payload[4] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
||||
payload[5] = autoExchange | (coinDisc << 8);
|
||||
payload[6] = 0; // ? 8c027124
|
||||
payload[7] = maxPay;
|
||||
payload[8] = maxCredit;
|
||||
payload[9] = hopperSize;
|
||||
payload[10] = maxBet;
|
||||
payload[11] = minBet;
|
||||
payload[12] = addBet;
|
||||
payload[13] = currency; // currency (lsb, 0:medal, 1:pound, 2:dollar, 3:euro, 4:token)
|
||||
// ? 8c027141, 8c027142, 8c027143
|
||||
payload[14] = credit0; // credit0
|
||||
payload[15] = credit1; // credit1
|
||||
payload[16] = totalCredit; // totalCredit
|
||||
payload[17] = premium; // premium
|
||||
// FUN_8c00e26e
|
||||
payload[18] = 0; // ? 8c02cd60
|
||||
payload[19] = 0; // ? FUN_8c014806() or FUN_8c00817c()
|
||||
// &payload[20] // copy of mem (8c02715c, 0x58 bytes)
|
||||
// FUN_8c009f8a
|
||||
payload[0x2a] = 0; // ? FUN_8c015b5a()
|
||||
payload[0x2b] = 0; // ? FUN_8c0163d8() + FUN_8c0148d4()
|
||||
payload[0x2c] = 0; // hopperOutLap
|
||||
payload[0x2d] = 0; // wins
|
||||
payload[0x2e] = 0; // ? 8c02717c
|
||||
payload[0x2f] = 0; // ? 8c027180
|
||||
payload[0x30] = 0; // ? 8c027188
|
||||
payload[0x31] = 0; // ? 8c02718c
|
||||
payload[0x32] = 0; // paid? 8c027190
|
||||
|
||||
payload[0x33] = maxHopperFloat;
|
||||
payload[0x34] = 0; // showLastWinAndHopperFloat (b6: show last win b7: show hopperfloat)
|
||||
payload[0x35] = 0; // ? 8c02714c
|
||||
payload[0x36] = 0; // ? 8c027150
|
||||
payload[0x37] = 0x80; // dataportCondition (b6: protocol on, bit7: forever)
|
||||
payload[0x38] = 0; // ? 8c027f48
|
||||
payload[0x39] = 0; // ? 8c027f4c
|
||||
payload[0x3a] = 0; // ? 8c027f50
|
||||
// FUN_8c00ae24
|
||||
payload[0x3b] = 0; // ? 20 bytes
|
||||
// 1c2 0 ?
|
||||
// 1c9 100 or 50 depending on currency
|
||||
// 1ca 10000 or 100 depending...
|
||||
// 1cb (short)same as 1c9
|
||||
// (short)499 or 5 dep...
|
||||
// 1cc (short)500 or 5
|
||||
// 499
|
||||
// 1cd (short)500
|
||||
// (short)49 or 19 dep...
|
||||
// 1ce 50 or 20 dep...
|
||||
// 1cf 200, 70 or 100
|
||||
// 1d0 0 or 100
|
||||
// 1d1 0 or 100
|
||||
// 1d2 0, 2 or 3
|
||||
// 1d3 (short)2000, 70 or 200
|
||||
// (short)200, 70 or 50
|
||||
// 1d4 100, 0 or 10
|
||||
// 1d6 (short)100 0 0 0 0 0 0 0
|
||||
// 1d8 (short)12 shorts?
|
||||
// 100 200 300 400 500 1000 2000 2500 5000 10000 0 ...
|
||||
// Default values
|
||||
payload[0x1c0] = 0x5010000; // medal exch rate (5), 2-way (1), autoPO (0), free play (0)
|
||||
payload[0x1c1] = 0x1640100; // ? (1), ? (100), coin disc (1), auto exch (0)
|
||||
payload[0x1c3] = 1999900; // max pay
|
||||
payload[0x1c4] = 1999900; // max credit
|
||||
payload[0x1c5] = 39900; // hopper size
|
||||
payload[0x1c6] = 10000; // max bet
|
||||
payload[0x1c7] = 1000; // min bet
|
||||
payload[0x1c8] = 100; // add bet
|
||||
payload[0x1c9] = 100;
|
||||
payload[0x1ca] = 10000;
|
||||
payload[0x1cb] = 100 | (499 << 16);
|
||||
payload[0x1cc] = 500 | (499 << 16);
|
||||
payload[0x1cd] = 500 | (49 << 16);
|
||||
payload[0x1ce] = 50;
|
||||
payload[0x1cf] = 200;
|
||||
payload[0x1d3] = 2000 | (200 << 16);
|
||||
payload[0x1d4] = 100;
|
||||
payload[0x1d6] = 100;
|
||||
payload[0x1da] = 100 | (200 << 16);
|
||||
payload[0x1db] = 300 | (400 << 16);
|
||||
payload[0x1dc] = 500 | (1000 << 16);
|
||||
payload[0x1dd] = 2000 | (2500 << 16);
|
||||
payload[0x1de] = 5000 | (10000 << 16);
|
||||
payload[0x1e9] = 1; // hopper ready flag
|
||||
sendMessage(0x21, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
started = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x42:
|
||||
{
|
||||
// GET_STATUS
|
||||
INFO_LOG(NAOMI, "hopper received GET STATUS");
|
||||
// TODO offset 4: 0 or 2
|
||||
std::array<u32, 4> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = 0; // atp kind and error code
|
||||
payload[2] = status;
|
||||
sendMessage(0x22, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x43:
|
||||
{
|
||||
// GAME START
|
||||
INFO_LOG(NAOMI, "hopper received GAME START");
|
||||
// 4: credit used?
|
||||
credit0 -= *(u32 *)&recvBuffer[4];
|
||||
std::array<u32, 0x1f> payload{};
|
||||
payload[0] = ++gameNumber; // game#, ?
|
||||
payload[1] = 0; // atp num
|
||||
payload[2] = 0; // atp kind and error code
|
||||
payload[3] = status;
|
||||
payload[4] = credit0;
|
||||
payload[5] = credit1;
|
||||
payload[6] = totalCredit;
|
||||
payload[7] = premium;
|
||||
// &payload[8] ? 0x58 bytes
|
||||
sendMessage(0x23, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x44:
|
||||
{
|
||||
// GAME END
|
||||
INFO_LOG(NAOMI, "hopper received GAME END");
|
||||
std::array<u32, 0x1e> payload{};
|
||||
payload[0] = gameNumber; // gameNum, upper 16 bits: ? 0, -1 or -2
|
||||
payload[1] = credit0;
|
||||
payload[2] = credit1;
|
||||
payload[3] = totalCredit;
|
||||
payload[4] = premium;
|
||||
// ? (58 bytes)
|
||||
payload[27] = 2; // wins
|
||||
payload[28] = 0; // last paid
|
||||
sendMessage(0x24, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x45:
|
||||
{
|
||||
// TEST
|
||||
INFO_LOG(NAOMI, "hopper received TEST");
|
||||
std::array<u32, 0x3c> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = 0; // atp kind and error code
|
||||
payload[2] = status;
|
||||
// TODO
|
||||
// 3: memcpy 8c027028, 0x60
|
||||
// 0x18: memcpy &errorCode, 0x80 (follows previous in ram)
|
||||
// 0x38: bit0: dataport temp? bit1: ?
|
||||
payload[0x38] = 0;
|
||||
sendMessage(0x25, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x4a:
|
||||
{
|
||||
// SWITCH
|
||||
INFO_LOG(NAOMI, "hopper received SWITCH");
|
||||
// TODO ?
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x4b:
|
||||
{
|
||||
// CONFIG_HOP
|
||||
INFO_LOG(NAOMI, "hopper received CONFIG HOP");
|
||||
if (recvBuffer[2] == 0x2c && recvBuffer[3] == 0)
|
||||
{
|
||||
freePlay = recvBuffer[4] & 1;
|
||||
autoPayOut = recvBuffer[5] & 1;
|
||||
twoWayMode = recvBuffer[6] & 1;
|
||||
medalExchRate = recvBuffer[7];
|
||||
autoExchange = recvBuffer[8] & 1;
|
||||
coinDisc = recvBuffer[9] & 1;
|
||||
// 0xc ??
|
||||
maxPay = *(u32 *)&recvBuffer[0x10];
|
||||
maxCredit = *(u32 *)&recvBuffer[0x14];
|
||||
hopperSize = *(u32 *)&recvBuffer[0x18];
|
||||
maxBet = *(u32 *)&recvBuffer[0x1c];
|
||||
minBet = *(u32 *)&recvBuffer[0x20];
|
||||
addBet = *(u32 *)&recvBuffer[0x24];
|
||||
currency = recvBuffer[0x28]; // ??
|
||||
}
|
||||
std::array<u32, 0xa> payload{};
|
||||
payload[0] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
||||
payload[1] = autoExchange | (coinDisc << 8);
|
||||
payload[2] = 0; // ? 8c027124
|
||||
payload[3] = maxPay;
|
||||
payload[4] = maxCredit;
|
||||
payload[5] = hopperSize;
|
||||
payload[6] = maxBet;
|
||||
payload[7] = minBet;
|
||||
payload[8] = addBet;
|
||||
payload[9] = currency; // currency (lsb, 0:medal, 1:pound, 2:dollar, 3:euro, 4:token)
|
||||
// ? 8c027141, 8c027142, 8c027143
|
||||
sendMessage(0x2b, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WARN_LOG(NAOMI, "Unexpected hopper message: %x", command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
void sendStatusMessage()
|
||||
{
|
||||
std::array<u32, 4> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = 0; // atp kind and error code
|
||||
payload[2] = status; // status bitfield. rectangle in background if != 0
|
||||
sendMessage(0, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
}
|
||||
void sendErrorMessage()
|
||||
{
|
||||
std::array<u32, 4> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = 0; // atp kind and error code
|
||||
payload[2] = status; // status bitfield. rectangle in background if != 0
|
||||
sendMessage(7, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
}
|
||||
*/
|
||||
|
||||
void sendCoinInMessage() override
|
||||
{
|
||||
credit0 += 100;
|
||||
totalCredit += 100;
|
||||
std::array<u32, 8> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = 0; // atp kind and error code
|
||||
payload[2] = status;
|
||||
payload[3] = credit0;
|
||||
payload[4] = credit1;
|
||||
payload[5] = totalCredit;
|
||||
payload[5] = premium;
|
||||
sendMessage(1, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
}
|
||||
|
||||
// ?:1
|
||||
// ?:1
|
||||
// coinInEnabled?:1
|
||||
// yenInEnabled?:1
|
||||
// frontDoorOpen:1
|
||||
// backDoorOpen:1
|
||||
// cashDoorOpen:1
|
||||
// needsRefill?:1
|
||||
// ?:1
|
||||
const u32 status = 0x00000000;
|
||||
};
|
||||
|
||||
// Naomi SWP Hopper Board
|
||||
class NaomiHopper : public BaseHopper
|
||||
{
|
||||
protected:
|
||||
void handleMessage(u8 command) override
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case 0x20:
|
||||
{
|
||||
// POWER ON
|
||||
static const char hopperErrors[][32] = {
|
||||
"UNKNOWN ERROR",
|
||||
"ROM IS BAD",
|
||||
"LOW BATTERY",
|
||||
"ROM HAS CHANGED",
|
||||
"RAM DATA IS BAD",
|
||||
"I/O ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"COIN IN JAM(HOPPER)",
|
||||
"COIN IN JAM(GAME)",
|
||||
"HOPPER OVER PAID",
|
||||
"HOPPER RUNAWAY",
|
||||
"HOPPER EMPTY/JAM",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"COM. TIME OUT",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"I/O BOARD IS BAD",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"UNKNOWN ERROR",
|
||||
"ATP NONE\0\0\0\0\0\0\0\0MAX. PAY",
|
||||
"HOPPER SIZE\0\0\0\0\0MAX. CREDIT",
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0DOOR IS OPEN"
|
||||
};
|
||||
|
||||
INFO_LOG(NAOMI, "hopper received POWER ON");
|
||||
std::array<u32, 0x13a> payload{};
|
||||
payload[0] = gameNumber;
|
||||
payload[1] = 0; // atp num
|
||||
payload[2] = status; // status bitfield. rectangle in background if != 0
|
||||
payload[3] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
||||
payload[4] = autoExchange | (coinDisc << 8);
|
||||
payload[5] = maxPay;
|
||||
payload[6] = maxCredit;
|
||||
payload[7] = hopperSize;
|
||||
payload[8] = maxBet;
|
||||
payload[9] = minBet;
|
||||
payload[10] = addBet;
|
||||
payload[11] = 0; // ? BYTE_8c0c9fa8
|
||||
payload[12] = credit0;
|
||||
payload[13] = credit1;
|
||||
payload[14] = totalCredit;
|
||||
payload[15] = premium;
|
||||
payload[16] = 1; // min count
|
||||
payload[17] = 666; // paid amount
|
||||
|
||||
/* doesn't seem to matter */
|
||||
payload[18] = ~0;
|
||||
payload[19] = ~0;
|
||||
payload[20] = ~0;
|
||||
payload[21] = ~0;
|
||||
payload[22] = ~0;
|
||||
payload[23] = ~0;
|
||||
payload[24] = ~0;
|
||||
|
||||
payload[25] = ~0; // ?
|
||||
payload[26] = ~0; // ?
|
||||
payload[27] = ~0; // ?
|
||||
// don't care?
|
||||
payload[28] = ~0;
|
||||
payload[29] = ~0; // ?
|
||||
payload[30] = ~0; // ?
|
||||
payload[31] = ~0; // ?
|
||||
// don't care?
|
||||
payload[32] = ~0;
|
||||
size_t idx = 0x84;
|
||||
for (size_t i = 0; i < std::size(hopperErrors); i++) {
|
||||
memcpy((u8 *)payload.data() + idx, hopperErrors[i], sizeof(hopperErrors[i]));
|
||||
idx += 32;
|
||||
}
|
||||
// power down in game if lsb != 0
|
||||
payload[0x139] = 1;
|
||||
sendMessage(0x10, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
|
||||
started = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x21: // GET_STATUS
|
||||
{
|
||||
INFO_LOG(NAOMI, "hopper received GET STATUS");
|
||||
std::array<u32, 3> payload{};
|
||||
payload[0] = 0; // atp num?
|
||||
payload[1] = status;
|
||||
sendMessage(0x11, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x22: // GAME START
|
||||
{
|
||||
INFO_LOG(NAOMI, "hopper received GAME START");
|
||||
// 4: credit used?
|
||||
credit0 -= *(u32 *)&recvBuffer[4];
|
||||
std::array<u32, 0x17> payload{};
|
||||
payload[0] = ++gameNumber;
|
||||
payload[1] = 0; // atp num
|
||||
payload[2] = status;
|
||||
payload[3] = credit0;
|
||||
payload[4] = credit1;
|
||||
payload[5] = totalCredit;
|
||||
payload[6] = premium;
|
||||
sendMessage(0x12, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x23: // GAME END
|
||||
{
|
||||
INFO_LOG(NAOMI, "hopper received GAME END");
|
||||
// 8: amount to pay?
|
||||
//credit0 += *(u32 *)&recvBuffer[8];
|
||||
std::array<u32, 0x15> payload{};
|
||||
payload[0] = 1; // gameNum, upper 16 bits: ? 0, -1 or -2
|
||||
payload[1] = credit0;
|
||||
payload[2] = credit1;
|
||||
payload[3] = totalCredit;
|
||||
payload[4] = premium;
|
||||
sendMessage(0x13, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
// TODO send PAY WIN message?
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x29: // SWITCH
|
||||
{
|
||||
INFO_LOG(NAOMI, "hopper received SWITCH");
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x2a: // CONFIG HOP
|
||||
{
|
||||
INFO_LOG(NAOMI, "hopper received CONFIG HOP");
|
||||
if (recvBuffer[2] == 0x28 && recvBuffer[3] == 0)
|
||||
{
|
||||
freePlay = recvBuffer[4] & 1;
|
||||
autoPayOut = recvBuffer[5] & 1;
|
||||
twoWayMode = recvBuffer[6] & 1;
|
||||
medalExchRate = recvBuffer[7];
|
||||
autoExchange = recvBuffer[8] & 1;
|
||||
coinDisc = recvBuffer[9] & 1;
|
||||
maxPay = *(u32 *)&recvBuffer[0xc];
|
||||
maxCredit = *(u32 *)&recvBuffer[0x10];
|
||||
hopperSize = *(u32 *)&recvBuffer[0x14];
|
||||
maxBet = *(u32 *)&recvBuffer[0x18];
|
||||
minBet = *(u32 *)&recvBuffer[0x1c];
|
||||
addBet = *(u32 *)&recvBuffer[0x20];
|
||||
//currency = recvBuffer[0x24]; // ??
|
||||
}
|
||||
std::array<u32, 9> payload{};
|
||||
payload[0] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
||||
payload[1] = autoExchange | (coinDisc << 8);
|
||||
payload[2] = maxPay;
|
||||
payload[3] = maxCredit;
|
||||
payload[4] = hopperSize;
|
||||
payload[5] = maxBet;
|
||||
payload[6] = minBet;
|
||||
payload[7] = addBet;
|
||||
payload[8] = currency; // currency (lsb, 0:medal, 1:pound, 2:dollar, 3:euro, 4:token)
|
||||
// ?, ?, ?
|
||||
sendMessage(0x1a, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WARN_LOG(NAOMI, "Unexpected hopper message: %x", command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
void sendStatusMessage()
|
||||
{
|
||||
std::array<u32, 3> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = status; // status bitfield
|
||||
sendMessage(0, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
}
|
||||
*/
|
||||
|
||||
void sendCoinInMessage() override
|
||||
{
|
||||
credit0 += 100;
|
||||
totalCredit += 100;
|
||||
std::array<u32, 7> payload{};
|
||||
payload[0] = 0; // atp num
|
||||
payload[1] = status;
|
||||
payload[2] = credit0;
|
||||
payload[3] = credit1;
|
||||
payload[4] = totalCredit;
|
||||
payload[5] = premium;
|
||||
sendMessage(1, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
||||
}
|
||||
|
||||
// error:16
|
||||
// atpType:4
|
||||
// doorOpen:1
|
||||
// unk1:1
|
||||
// unk2:1
|
||||
// unk3:1
|
||||
// unk4:1
|
||||
const u32 status = 0x00000000;
|
||||
};
|
||||
|
||||
static BaseHopper *pipe;
|
||||
|
||||
void init()
|
||||
{
|
||||
term();
|
||||
if (settings.content.gameId == "KICK '4' CASH")
|
||||
pipe = new Sega837_14438Hopper();
|
||||
else
|
||||
pipe = new NaomiHopper();
|
||||
serial_setPipe(pipe);
|
||||
}
|
||||
|
||||
void term()
|
||||
{
|
||||
delete pipe;
|
||||
pipe = nullptr;
|
||||
}
|
||||
|
||||
void serialize(Serializer& ser)
|
||||
{
|
||||
if (pipe != nullptr)
|
||||
pipe->serialize(ser);
|
||||
}
|
||||
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
if (pipe != nullptr)
|
||||
pipe->deserialize(deser);
|
||||
}
|
||||
|
||||
} // namespace hopper
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2023 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
namespace hopper
|
||||
{
|
||||
|
||||
void init();
|
||||
void term();
|
||||
|
||||
void serialize(Serializer& ser);
|
||||
void deserialize(Deserializer& deser);
|
||||
|
||||
}
|
|
@ -45,6 +45,7 @@
|
|||
#include "network/alienfnt_modem.h"
|
||||
#include "netdimm.h"
|
||||
#include "systemsp.h"
|
||||
#include "hopper.h"
|
||||
|
||||
Cartridge *CurrentCartridge;
|
||||
bool bios_loaded = false;
|
||||
|
@ -666,6 +667,12 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo
|
|||
{
|
||||
printer::init();
|
||||
}
|
||||
if (!strcmp(CurrentCartridge->game->name, "clubkprz") || !strncmp(CurrentCartridge->game->name, "clubkpz", 7)
|
||||
|| !strncmp(CurrentCartridge->game->name, "shootpl", 7)
|
||||
|| gameId == "KICK '4' CASH")
|
||||
{
|
||||
hopper::init();
|
||||
}
|
||||
|
||||
#ifdef NAOMI_MULTIBOARD
|
||||
// Not a multiboard game but needs the same desktop environment
|
||||
|
@ -726,6 +733,7 @@ void naomi_cart_Close()
|
|||
card_reader::initdTerm();
|
||||
card_reader::barcodeTerm();
|
||||
serialModemTerm();
|
||||
hopper::term();
|
||||
delete CurrentCartridge;
|
||||
CurrentCartridge = nullptr;
|
||||
NaomiGameInputs = nullptr;
|
||||
|
@ -738,6 +746,7 @@ void naomi_cart_serialize(Serializer& ser)
|
|||
CurrentCartridge->Serialize(ser);
|
||||
touchscreen::serialize(ser);
|
||||
printer::serialize(ser);
|
||||
hopper::serialize(ser);
|
||||
}
|
||||
|
||||
void naomi_cart_deserialize(Deserializer& deser)
|
||||
|
@ -746,6 +755,7 @@ void naomi_cart_deserialize(Deserializer& deser)
|
|||
CurrentCartridge->Deserialize(deser);
|
||||
touchscreen::deserialize(deser);
|
||||
printer::deserialize(deser);
|
||||
hopper::deserialize(deser);
|
||||
}
|
||||
|
||||
int naomi_cart_GetPlatform(const char *path)
|
||||
|
|
Loading…
Reference in New Issue