1307 lines
32 KiB
C++
1307 lines
32 KiB
C++
/*
|
|
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 "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 "oslib/oslib.h"
|
|
#include "emulator.h"
|
|
#include "cfg/option.h"
|
|
|
|
#include <array>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <memory>
|
|
|
|
namespace hopper
|
|
{
|
|
|
|
class BaseHopper : public SerialPort::Pipe
|
|
{
|
|
public:
|
|
BaseHopper()
|
|
{
|
|
schedId = sh4_sched_register(0, schedCallback, this);
|
|
sh4_sched_request(schedId, SCHED_CYCLES);
|
|
EventManager::listen(Event::Pause, handleEvent, this);
|
|
|
|
std::string path = getConfigFileName();
|
|
FILE *f = fopen(path.c_str(), "rb");
|
|
if (f == nullptr) {
|
|
INFO_LOG(NAOMI, "Hopper config not found at %s", path.c_str());
|
|
}
|
|
else
|
|
{
|
|
u8 data[4096];
|
|
size_t len = fread(data, 1, sizeof(data), f);
|
|
fclose(f);
|
|
verify(len < sizeof(data));
|
|
if (len <= 0) {
|
|
ERROR_LOG(NAOMI, "Hopper config empty or I/O error: %s", path.c_str());
|
|
}
|
|
else
|
|
{
|
|
Deserializer deser(data, len);
|
|
deserializeConfig(deser);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual ~BaseHopper() {
|
|
EventManager::unlisten(Event::Pause, handleEvent, this);
|
|
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;
|
|
}
|
|
}
|
|
|
|
void serialize(Serializer& ser) const
|
|
{
|
|
ser << (u32)recvBuffer.size();
|
|
ser.serialize(recvBuffer.data(), recvBuffer.size());
|
|
serializeConfig(ser);
|
|
ser << expectedBytes;
|
|
ser << (u32)toSend.size();
|
|
for (u8 b : toSend)
|
|
ser << b;
|
|
ser << started;
|
|
sh4_sched_serialize(ser, schedId);
|
|
}
|
|
|
|
void deserialize(Deserializer& deser)
|
|
{
|
|
u32 size;
|
|
deser >> size;
|
|
recvBuffer.resize(size);
|
|
deser.deserialize(recvBuffer.data(), size);
|
|
deserializeConfig(deser);
|
|
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);
|
|
}
|
|
|
|
void saveConfig() const
|
|
{
|
|
std::string path = getConfigFileName();
|
|
FILE *f = fopen(path.c_str(), "wb");
|
|
if (f == nullptr) {
|
|
ERROR_LOG(NAOMI, "Can't save hopper config to %s", path.c_str());
|
|
return;
|
|
}
|
|
Serializer ser;
|
|
serializeConfig(ser);
|
|
std::unique_ptr<u8[]> data = std::make_unique<u8[]>(ser.size());
|
|
ser = Serializer(data.get(), ser.size());
|
|
serializeConfig(ser);
|
|
|
|
size_t len = fwrite(data.get(), 1, ser.size(), f);
|
|
fclose(f);
|
|
if (len != ser.size())
|
|
ERROR_LOG(NAOMI, "Hopper config I/O error: %s", path.c_str());
|
|
}
|
|
|
|
protected:
|
|
virtual void handleMessage(u8 command) = 0;
|
|
virtual void sendCoinInMessage() = 0;
|
|
virtual void sendCoinOutMessage() = 0;
|
|
virtual void sendPayWinMessage() = 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);
|
|
SCIFSerialPort::Instance().updateStatus();
|
|
}
|
|
|
|
void bet(const u32 *values)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
u32& primary = i == 0 ? credit0 : credit1;
|
|
u32& second = i == 0 ? credit1 : credit0;
|
|
if (primary >= values[i]) {
|
|
primary -= values[i];
|
|
}
|
|
else
|
|
{
|
|
int residual = values[i] - primary;
|
|
primary = 0;
|
|
second = std::max(0, (int)second - residual);
|
|
}
|
|
}
|
|
premium = std::max(0, (int)premium - (int)values[2]);
|
|
}
|
|
|
|
void payOut(u32 value)
|
|
{
|
|
if (value == 0)
|
|
return;
|
|
wonAmount += value;
|
|
if (!autoPayOut)
|
|
credit0 += value;
|
|
else {
|
|
paidAmount += value;
|
|
sendPayWinMessage();
|
|
}
|
|
}
|
|
|
|
void insertCoin(u32 value) {
|
|
credit0 += value;
|
|
}
|
|
|
|
std::vector<u8> recvBuffer;
|
|
u32 credit0 = 0;
|
|
u32 credit1 = 0;
|
|
u32 totalCredit = 100; // min bet
|
|
u32 premium = 0;
|
|
u32 gameNumber = 0;
|
|
bool freePlay = false;
|
|
bool autoPayOut = false;
|
|
bool autoExchange = false;
|
|
bool twoWayMode = true;
|
|
bool coinDiscrimination = true;
|
|
bool betButton = true;
|
|
u8 currency = ~0; // 0:medal, 1:pound, 2:dollar, 3:euro, 4:token, 5: any cash (837-14438 only)
|
|
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;
|
|
u32 paidAmount = 0;
|
|
u32 wonAmount = 0;
|
|
u32 curBase = 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;
|
|
}
|
|
|
|
static void handleEvent(Event event, void *p) {
|
|
((BaseHopper *)p)->saveConfig();
|
|
}
|
|
|
|
std::string getConfigFileName() const
|
|
{
|
|
return hostfs::getArcadeFlashPath() + "-hopper.bin";
|
|
}
|
|
|
|
void serializeConfig(Serializer& ser) const
|
|
{
|
|
ser << credit0;
|
|
ser << credit1;
|
|
ser << totalCredit;
|
|
ser << premium;
|
|
ser << gameNumber;
|
|
ser << freePlay;
|
|
ser << autoPayOut;
|
|
ser << autoExchange;
|
|
ser << twoWayMode;
|
|
ser << coinDiscrimination;
|
|
ser << currency;
|
|
ser << medalExchRate;
|
|
ser << maxHopperFloat;
|
|
ser << maxPay;
|
|
ser << maxCredit;
|
|
ser << hopperSize;
|
|
ser << maxBet;
|
|
ser << minBet;
|
|
ser << addBet;
|
|
ser << paidAmount;
|
|
ser << wonAmount;
|
|
ser << betButton;
|
|
ser << curBase;
|
|
}
|
|
|
|
void deserializeConfig(Deserializer& deser)
|
|
{
|
|
deser >> credit0;
|
|
deser >> credit1;
|
|
deser >> totalCredit;
|
|
deser >> premium;
|
|
deser >> gameNumber;
|
|
deser >> freePlay;
|
|
deser >> autoPayOut;
|
|
deser >> autoExchange;
|
|
deser >> twoWayMode;
|
|
deser >> coinDiscrimination;
|
|
deser >> currency;
|
|
deser >> medalExchRate;
|
|
deser >> maxHopperFloat;
|
|
deser >> maxPay;
|
|
deser >> maxCredit;
|
|
deser >> hopperSize;
|
|
deser >> maxBet;
|
|
deser >> minBet;
|
|
deser >> addBet;
|
|
if (deser.version() >= Deserializer::V39)
|
|
{
|
|
deser >> paidAmount;
|
|
deser >> wonAmount;
|
|
deser >> betButton;
|
|
deser >> curBase;
|
|
}
|
|
else
|
|
{
|
|
paidAmount = 0;
|
|
wonAmount = 0;
|
|
}
|
|
}
|
|
|
|
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");
|
|
// 8: currency
|
|
if (currency != recvBuffer[8]) {
|
|
currency = recvBuffer[8];
|
|
setDefaults();
|
|
}
|
|
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 | (coinDiscrimination << 8) | (curBase << 16) | (betButton << 24);
|
|
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, 5: any cash)
|
|
// ? 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] = wonAmount;
|
|
payload[0x2e] = 0; // ? 8c02717c
|
|
payload[0x2f] = 0; // ? 8c027180
|
|
payload[0x30] = 0; // ? 8c027188
|
|
payload[0x31] = 0; // ? 8c02718c
|
|
payload[0x32] = paidAmount; // 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
|
|
const u32 *def = getDefaultValues();
|
|
payload[0x1c0] = def[0];
|
|
payload[0x1c1] = def[1];
|
|
payload[0x1c3] = def[2];
|
|
payload[0x1c4] = def[3];
|
|
payload[0x1c5] = def[4];
|
|
payload[0x1c6] = def[5];
|
|
payload[0x1c7] = def[6];
|
|
payload[0x1c8] = def[7];
|
|
getRanges(&payload[0x1c9]);
|
|
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");
|
|
u32 *betValues = (u32 *)&recvBuffer[4];
|
|
bet(betValues);
|
|
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;
|
|
coinDiscrimination = recvBuffer[9] & 1;
|
|
curBase = recvBuffer[0xa];
|
|
betButton = recvBuffer[0xb] & 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];
|
|
}
|
|
std::array<u32, 0xa> payload{};
|
|
payload[0] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
|
payload[1] = autoExchange | (coinDiscrimination << 8) | (curBase << 16) | (betButton << 24);
|
|
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, 5:any cash)
|
|
// ? 8c027141, 8c027142, 8c027143
|
|
sendMessage(0x2b, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
break;
|
|
}
|
|
|
|
case 0x61: // COIN IN ACK
|
|
case 0x62: // COIN OUT ACK
|
|
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
|
|
{
|
|
insertCoin(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[6] = premium;
|
|
sendMessage(1, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
}
|
|
|
|
void sendCoinOutMessage() override
|
|
{
|
|
insertCoin(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[6] = premium;
|
|
sendMessage(2, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
}
|
|
|
|
void sendPayWinMessage() override
|
|
{
|
|
std::array<u32, 0xa> 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[6] = premium;
|
|
payload[7] = wonAmount;
|
|
payload[8] = paidAmount;
|
|
sendMessage(3, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
}
|
|
|
|
const u32 *getDefaultValues()
|
|
{
|
|
static const u32 defaults[][8] = {
|
|
// free play... max pay hopper size min bet
|
|
// auto exch... max credit max bet add bet
|
|
{ 0x05010000, 0x01640100, 1999900, 1999900, 39900, 10000, 1000, 100 },
|
|
{ 0x100, 0x50000, 10000, 1999900, 39900, 100, 100, 50 },
|
|
{ 0x100, 0x50000, 10000, 1999900, 39900, 100, 100, 100 },
|
|
{ 0x100, 0x50000, 10000, 1999900, 39900, 100, 100, 100 },
|
|
{ 0, 0x01640000, 1999900, 1999900, 39900, 10000, 100, 100 },
|
|
{ 0x100, 0x50000, 10000, 1999900, 39900, 100, 100, 100 },
|
|
};
|
|
if (currency >= std::size(defaults))
|
|
return defaults[0];
|
|
else
|
|
return defaults[currency];
|
|
}
|
|
|
|
void setDefaults()
|
|
{
|
|
const u32 *def = getDefaultValues();
|
|
freePlay = def[0] & 1;
|
|
autoPayOut = def[0] & 0x100;
|
|
twoWayMode = def[0] & 0x10000;
|
|
medalExchRate = def[0] >> 24;
|
|
autoExchange = def[1] & 1;
|
|
coinDiscrimination = def[1] & 0x100;
|
|
curBase = (def[1] >> 16) & 0xff;
|
|
betButton = def[1] & 0x1000000;
|
|
maxPay = def[2];
|
|
maxCredit = def[3];
|
|
hopperSize = def[4];
|
|
maxBet = def[5];
|
|
minBet = def[6];
|
|
addBet = def[7];
|
|
totalCredit = minBet;
|
|
}
|
|
|
|
void getRanges(u32 *p)
|
|
{
|
|
switch (currency)
|
|
{
|
|
case 0: // medal
|
|
p[0x0] = 100;
|
|
p[0x1] = 10000;
|
|
p[0x2] = 100 | (499 << 16);
|
|
p[0x3] = 500 | (499 << 16);
|
|
p[0x4] = 500 | (49 << 16);
|
|
p[0x5] = 50;
|
|
p[0x6] = 200;
|
|
p[0x7] = 0;
|
|
p[0x8] = 0;
|
|
p[0x9] = 0;
|
|
p[0xa] = 2000 | (200 << 16);
|
|
p[0xb] = 100;
|
|
p[0xc] = 0;
|
|
|
|
p[0xd] = 100;
|
|
p[0xe] = 0;
|
|
p[0xf] = 0;
|
|
p[0x10] = 0;
|
|
|
|
p[0x11] = 100 | (200 << 16);
|
|
p[0x12] = 300 | (400 << 16);
|
|
p[0x13] = 500 | (1000 << 16);
|
|
p[0x14] = 2000 | (2500 << 16);
|
|
p[0x15] = 5000 | (10000 << 16);
|
|
p[0x16] = 0;
|
|
break;
|
|
case 1: // pound
|
|
p[0x0] = 50;
|
|
p[0x1] = 100;
|
|
p[0x2] = 50 | (5 << 16);
|
|
p[0x3] = 5 | (499 << 16);
|
|
p[0x4] = 500 | (19 << 16);
|
|
p[0x5] = 20;
|
|
p[0x6] = 70;
|
|
p[0x7] = 0;
|
|
p[0x8] = 0;
|
|
p[0x9] = 0;
|
|
p[0xa] = 70 | (70 << 16);
|
|
p[0xb] = 0;
|
|
p[0xc] = 0;
|
|
|
|
p[0xd] = 5 | (10 << 16);
|
|
p[0xe] = 20 | (50 << 16);
|
|
p[0xf] = 50 | (100 << 16);
|
|
p[0x10] = 200;
|
|
|
|
p[0x11] = 5 | (10 << 16);
|
|
p[0x12] = 20 | (50 << 16);
|
|
p[0x13] = 100 | (200 << 16);
|
|
p[0x14] = 500 | (1000 << 16);
|
|
p[0x15] = 0;
|
|
p[0x16] = 0;
|
|
break;
|
|
case 2: // dollar
|
|
case 5: // any cash
|
|
default:
|
|
p[0x0] = 50;
|
|
p[0x1] = 100;
|
|
p[0x2] = 50 | (5 << 16);
|
|
p[0x3] = 5 | (499 << 16);
|
|
p[0x4] = 500 | (19 << 16);
|
|
p[0x5] = 20;
|
|
p[0x6] = 200;
|
|
p[0x7] = 0;
|
|
p[0x8] = 0;
|
|
p[0x9] = 0;
|
|
p[0xa] = 200 | (50 << 16);
|
|
p[0xb] = 10;
|
|
p[0xc] = 0;
|
|
|
|
p[0xd] = 5 | (10 << 16);
|
|
p[0xe] = 20 | (25 << 16);
|
|
p[0xf] = 50 | (100 << 16);
|
|
p[0x10] = 200 | (100 << 16);
|
|
|
|
p[0x11] = 5 | (10 << 16);
|
|
p[0x12] = 20 | (25 << 16);
|
|
p[0x13] = 50 | (100 << 16);
|
|
p[0x14] = 200 | (500 << 16);
|
|
p[0x15] = 1000;
|
|
p[0x16] = 0;
|
|
break;
|
|
case 3: // euro
|
|
p[0x0] = 50;
|
|
p[0x1] = 100;
|
|
p[0x2] = 50 | (5 << 16);
|
|
p[0x3] = 5 | (499 << 16);
|
|
p[0x4] = 500 | (19 << 16);
|
|
p[0x5] = 20;
|
|
p[0x6] = 200;
|
|
p[0x7] = 0;
|
|
p[0x8] = 0;
|
|
p[0x9] = 0;
|
|
p[0xa] = 200 | (50 << 16);
|
|
p[0xb] = 10;
|
|
p[0xc] = 0;
|
|
|
|
p[0xd] = 5 | (10 << 16);
|
|
p[0xe] = 20 | (50 << 16);
|
|
p[0xf] = 50 | (100 << 16);
|
|
p[0x10] = 200 | (100 << 16);
|
|
|
|
p[0x11] = 5 | (10 << 16);
|
|
p[0x12] = 20 | (50 << 16);
|
|
p[0x13] = 100 | (200 << 16);
|
|
p[0x14] = 500 | (1000 << 16);
|
|
p[0x15] = 0;
|
|
p[0x16] = 0;
|
|
break;
|
|
case 4: // token
|
|
p[0x0] = 100;
|
|
p[0x1] = 10000;
|
|
p[0x2] = 100 | (499 << 16);
|
|
p[0x3] = 500 | (499 << 16);
|
|
p[0x4] = 500 | (19 << 16);
|
|
p[0x5] = 20;
|
|
p[0x6] = 200;
|
|
p[0x7] = 0;
|
|
p[0x8] = 0;
|
|
p[0x9] = 0;
|
|
p[0xa] = 200 | (50 << 16);
|
|
p[0xb] = 10;
|
|
p[0xc] = 0;
|
|
|
|
p[0xd] = 100;
|
|
p[0xe] = 0;
|
|
p[0xf] = 0;
|
|
p[0x10] = 0;
|
|
|
|
p[0x11] = 100 | (200 << 16);
|
|
p[0x12] = 300 | (400 << 16);
|
|
p[0x13] = 500 | (1000 << 16);
|
|
p[0x14] = 2000 | (2500 << 16);
|
|
p[0x15] = 5000 | (10000 << 16);
|
|
p[0x16] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ?: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"
|
|
};
|
|
|
|
// 8: currency
|
|
if (currency != recvBuffer[8]) {
|
|
currency = recvBuffer[8];
|
|
setDefaults();
|
|
}
|
|
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 | (coinDiscrimination << 8) | (curBase << 16) | (betButton << 24);
|
|
payload[5] = maxPay;
|
|
payload[6] = maxCredit;
|
|
payload[7] = hopperSize;
|
|
payload[8] = maxBet;
|
|
payload[9] = minBet;
|
|
payload[10] = addBet;
|
|
payload[11] = currency; // ? BYTE_8c0c9fa8-b
|
|
payload[12] = credit0;
|
|
payload[13] = credit1;
|
|
payload[14] = totalCredit;
|
|
payload[15] = premium;
|
|
payload[16] = wonAmount;
|
|
payload[17] = paidAmount; // paid amount (if autoPO) else credit won?
|
|
|
|
payload[18] = 0; // coin1 in count
|
|
payload[19] = 0; // coin2 in count
|
|
payload[20] = 0; // coin3 in count
|
|
payload[21] = 0; // coin4 in count
|
|
payload[22] = 0; // coin5 in count
|
|
payload[23] = 0; // coin6 in count
|
|
payload[24] = 0; // coin out count
|
|
|
|
payload[25] = 0; // big int(1/2) 8c0ca674?
|
|
payload[26] = 0; // big int(2/2)?
|
|
payload[27] = 0; // 8c0ca67c?
|
|
|
|
payload[28] = 0; // 8c0ca680?
|
|
payload[29] = 0; // big int(1/2) 8c0ca684?
|
|
payload[30] = 0; // big int(2/2)?
|
|
payload[31] = 0; // 8c0ca68c?
|
|
payload[32] = 0; // 8c0ca690?
|
|
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;
|
|
}
|
|
// init done/hopper ready?
|
|
payload[0x139] = 1;
|
|
sendMessage(0x10, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
|
|
started = true;
|
|
|
|
// Displays the in-game hopper debug overlay
|
|
//u32 payload2 = 0;
|
|
//sendMessage(0xf, (const u8 *)&payload2, 3); // DEBUG
|
|
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: bet value: credit0
|
|
// 8: credit1
|
|
// c: premium
|
|
u32 *betValues = (u32 *)&recvBuffer[4];
|
|
bet(betValues);
|
|
wonAmount = 0;
|
|
paidAmount = 0;
|
|
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;
|
|
// 7-c: coinIn1-6Count
|
|
// d: coinOutCount
|
|
// e-f: bigint 8c0ca674
|
|
// 10: 8c0ca67c
|
|
// 11: 8c0ca680
|
|
// 12-13: bigint 8c0ca684
|
|
// 14: 8c0ca68c
|
|
// 15: 8c0ca690
|
|
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?
|
|
u32 value = *(u32 *)&recvBuffer[8];
|
|
payOut(value);
|
|
std::array<u32, 0x15> payload{};
|
|
payload[0] = gameNumber; // gameNum, upper 16 bits: ? 0, -1 or -2
|
|
payload[1] = credit0;
|
|
payload[2] = credit1;
|
|
payload[3] = totalCredit;
|
|
payload[4] = premium;
|
|
// 5-a: coinIn1-6Count
|
|
// b: coinOutCount
|
|
// c-d: bigint 8c0ca674
|
|
// e: 8c0ca67c
|
|
// f: 8c0ca680
|
|
// 10-11: bigint 8c0ca684
|
|
// 12: 8c0ca68c
|
|
// 13: 8c0ca690
|
|
sendMessage(0x13, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
break;
|
|
}
|
|
|
|
case 0x24: // TEST START
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received TEST START");
|
|
// recv[4] is 1
|
|
std::array<u32, 0x33> payload{};
|
|
payload[0] = 0; // atp num
|
|
payload[1] = status;
|
|
// c0 bytes from 8c0ca1b4
|
|
payload[0x12] = 2023 | (7 << 16) | (19 << 24); // year month day
|
|
payload[0x13] = (55 << 24) | (48 << 16) | (11 << 8) | 3; // day of week, hour, minute, second
|
|
payload[0x14] = gameNumber;
|
|
sendMessage(0x14, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
break;
|
|
}
|
|
|
|
case 0x25: // BACKUP CLEAR
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received BACKUP CLEAR");
|
|
credit0 = 0;
|
|
credit1 = 0;
|
|
premium = 0;
|
|
paidAmount = 0;
|
|
wonAmount = 0;
|
|
std::array<u32, 0x44> payload{};
|
|
payload[0] = credit0;
|
|
payload[1] = credit1;
|
|
payload[2] = totalCredit;
|
|
payload[3] = premium;
|
|
// coin1InCount -> coin6InCount
|
|
// coinOutCount
|
|
// bigint 8c0ca674
|
|
// 8c0ca67c
|
|
// 8c0ca680
|
|
// bigint 8c0ca684
|
|
// 8c0ca68c
|
|
// 8c0ca690
|
|
// copy c0 bytes from 8c0ca1b4
|
|
payload[0x23] = 2023 | (7 << 16) | (19 << 24); // year month day
|
|
payload[0x24] = (55 << 24) | (48 << 16) | (11 << 8) | 3; // day of week, hour, minute, second
|
|
payload[0x25] = gameNumber;
|
|
sendMessage(0x15, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
break;
|
|
}
|
|
|
|
case 0x26: // BET
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received BET");
|
|
u32 v = *(u32 *)&recvBuffer[4];
|
|
std::array<u32, 2> payload{};
|
|
payload[0] = v;
|
|
sendMessage(0x16, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
break;
|
|
}
|
|
|
|
case 0x29: // SWITCH
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received SWITCH");
|
|
// 4: test button
|
|
// 5: service button
|
|
// 6-7: c01 in test menu
|
|
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;
|
|
coinDiscrimination = recvBuffer[9] & 1;
|
|
curBase = recvBuffer[0xa];
|
|
betButton = recvBuffer[0xb] & 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];
|
|
}
|
|
std::array<u32, 9> payload{};
|
|
payload[0] = freePlay | (autoPayOut << 8) | (twoWayMode << 16) | (medalExchRate << 24);
|
|
payload[1] = autoExchange | (coinDiscrimination << 8) | (curBase << 16) | (betButton << 24);
|
|
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;
|
|
}
|
|
|
|
case 0x2f: // TEST DATA MON
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received cmd TEST DATA MON");
|
|
// recv[4] == 2 (3 in coin test, 4 in error log)
|
|
// recv[8] b0 payout lamp
|
|
// b1 coin in lamp
|
|
// b2 divider cash box side
|
|
// b3 divider hopper side
|
|
// b5 coin inhibit
|
|
// b6 cash inhibit
|
|
// recv[c] hopper run
|
|
// no reply
|
|
break;
|
|
}
|
|
|
|
case 0x31: // COIN IN ACK?
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received cmd 31");
|
|
// no reply
|
|
break;
|
|
}
|
|
|
|
case 0x32: // COIN OUT ACK?
|
|
{
|
|
INFO_LOG(NAOMI, "hopper received cmd 32");
|
|
// no reply
|
|
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
|
|
{
|
|
insertCoin(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);
|
|
}
|
|
|
|
void sendCoinOutMessage() override
|
|
{
|
|
insertCoin(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(2, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
}
|
|
|
|
void sendPayWinMessage() override
|
|
{
|
|
std::array<u32, 9> payload{};
|
|
payload[0] = 0; // atp num
|
|
payload[1] = status;
|
|
payload[2] = credit0;
|
|
payload[3] = credit1;
|
|
payload[4] = totalCredit;
|
|
payload[5] = premium;
|
|
payload[6] = wonAmount;
|
|
payload[7] = paidAmount;
|
|
sendMessage(3, (const u8 *)payload.data(), payload.size() * sizeof(u32) - 1);
|
|
}
|
|
|
|
void setDefaults()
|
|
{
|
|
switch (currency)
|
|
{
|
|
case 0: // Medal
|
|
medalExchRate = 5;
|
|
twoWayMode = true;
|
|
autoPayOut = false;
|
|
coinDiscrimination = true;
|
|
curBase = 100;
|
|
betButton = true;
|
|
maxPay = 1999900;
|
|
maxCredit = 1999900;
|
|
hopperSize = 39900;
|
|
maxBet = 10000;
|
|
minBet = 1000;
|
|
addBet = 100;
|
|
break;
|
|
case 1: // Pound
|
|
medalExchRate = 0;
|
|
twoWayMode = false;
|
|
autoPayOut = true;
|
|
coinDiscrimination = false;
|
|
curBase = 5;
|
|
betButton = false;
|
|
maxPay = 4000;
|
|
maxCredit = 1999900;
|
|
hopperSize = 1999900;
|
|
maxBet = 100;
|
|
minBet = 100;
|
|
addBet = 50;
|
|
break;
|
|
case 2: // Dollar
|
|
case 3: // Euro
|
|
medalExchRate = 0;
|
|
twoWayMode = false;
|
|
autoPayOut = false;
|
|
coinDiscrimination = false;
|
|
curBase = 5;
|
|
betButton = true;
|
|
maxPay = 1999900;
|
|
maxCredit = 1999900;
|
|
hopperSize = 39900;
|
|
maxBet = 10000;
|
|
minBet = 100;
|
|
addBet = 100;
|
|
break;
|
|
case 4: // Token
|
|
medalExchRate = 0;
|
|
twoWayMode = false;
|
|
autoPayOut = false;
|
|
coinDiscrimination = false;
|
|
curBase = 100;
|
|
betButton = true;
|
|
maxPay = 1999900;
|
|
maxCredit = 1999900;
|
|
hopperSize = 39900;
|
|
maxBet = 10000;
|
|
minBet = 100;
|
|
addBet = 100;
|
|
break;
|
|
default:
|
|
WARN_LOG(NAOMI, "Unsupported currency %d", currency);
|
|
break;
|
|
}
|
|
totalCredit = minBet;
|
|
}
|
|
|
|
// error:16
|
|
// atpType:4
|
|
// doorOpen:1
|
|
// unk1:1
|
|
// unk2:1
|
|
// unk3:1
|
|
// unk4:1
|
|
const u32 status = 0x00000000;
|
|
};
|
|
|
|
static BaseHopper *hopper;
|
|
|
|
void init()
|
|
{
|
|
term();
|
|
if (settings.content.gameId == "KICK '4' CASH")
|
|
hopper = new Sega837_14438Hopper();
|
|
else
|
|
hopper = new NaomiHopper();
|
|
SCIFSerialPort::Instance().setPipe(hopper);
|
|
config::ForceFreePlay.override(false);
|
|
}
|
|
|
|
void term()
|
|
{
|
|
SCIFSerialPort::Instance().setPipe(nullptr);
|
|
delete hopper;
|
|
hopper = nullptr;
|
|
}
|
|
|
|
void serialize(Serializer& ser)
|
|
{
|
|
if (hopper != nullptr)
|
|
hopper->serialize(ser);
|
|
}
|
|
|
|
void deserialize(Deserializer& deser)
|
|
{
|
|
if (hopper != nullptr)
|
|
hopper->deserialize(deser);
|
|
}
|
|
|
|
} // namespace hopper
|