jubeat: support p3io games
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
libs += jbhook-util
|
||||
|
||||
libs_jbhook-util := \
|
||||
acioemu \
|
||||
|
||||
src_jbhook-util := \
|
||||
acio.c \
|
||||
eamuse.c \
|
||||
gfx.c \
|
||||
mixer.c \
|
||||
locale.c \
|
||||
p3io.c \
|
||||
p4io.c \
|
||||
@@ -0,0 +1,103 @@
|
||||
// clang-format off
|
||||
// Don't format because the order is important here
|
||||
#include <windows.h>
|
||||
#include <ntdef.h>
|
||||
#include <devioctl.h>
|
||||
#include <ntddser.h>
|
||||
// clang-format on
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "acioemu/addr.h"
|
||||
#include "acioemu/emu.h"
|
||||
#include "acioemu/h44b.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "jbhook-util/acio.h"
|
||||
|
||||
#include "imports/avs.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/hex.h"
|
||||
#include "util/iobuf.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static struct ac_io_emu ac_io_emu;
|
||||
static struct ac_io_emu_icca ac_io_emu_icca;
|
||||
static struct ac_io_emu_h44b ac_io_emu_h44b;
|
||||
|
||||
void jbhook_util_ac_io_port_init(const wchar_t *filename)
|
||||
{
|
||||
ac_io_emu_init(&ac_io_emu, filename);
|
||||
ac_io_emu_icca_init(&ac_io_emu_icca, &ac_io_emu, 0);
|
||||
ac_io_emu_h44b_init(&ac_io_emu_h44b, &ac_io_emu, 1);
|
||||
}
|
||||
|
||||
void jbhook_util_ac_io_port_fini(void)
|
||||
{
|
||||
ac_io_emu_fini(&ac_io_emu);
|
||||
}
|
||||
|
||||
void jbhook_util_ac_io_set_iccb(void) {
|
||||
ac_io_emu_icca_set_version(&ac_io_emu_icca, v150);
|
||||
ac_io_emu_icca_set_product_code(&ac_io_emu_icca, AC_IO_EMU_PROD_CODE_ICCB);
|
||||
}
|
||||
|
||||
HRESULT
|
||||
jbhook_util_ac_io_port_dispatch_irp(struct irp *irp)
|
||||
{
|
||||
const struct ac_io_message *msg;
|
||||
HRESULT hr;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
if (!ac_io_emu_match_irp(&ac_io_emu, irp)) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp);
|
||||
|
||||
if (hr != S_OK) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
msg = ac_io_emu_request_peek(&ac_io_emu);
|
||||
|
||||
switch (msg->addr) {
|
||||
case 0:
|
||||
ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2);
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
ac_io_emu_icca_dispatch_request(&ac_io_emu_icca, msg);
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
ac_io_emu_h44b_dispatch_request(&ac_io_emu_h44b, msg);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_BROADCAST:
|
||||
log_warning("Broadcast(?) message on jubeat ACIO bus?");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning(
|
||||
"ACIO message on unhandled bus address: %d", msg->addr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ac_io_emu_request_pop(&ac_io_emu);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef JBHOOK1_ACIO_H
|
||||
#define JBHOOK1_ACIO_H
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
/**
|
||||
* Initialize the ACIO backend for jubeat on the specified COM port.
|
||||
*/
|
||||
void jbhook_util_ac_io_port_init(const wchar_t *filename);
|
||||
|
||||
/**
|
||||
* Shutdown the ACIO backend.
|
||||
*/
|
||||
void jbhook_util_ac_io_port_fini(void);
|
||||
|
||||
/**
|
||||
* ACIO backend dispatch irp function. This needs to be hooked up to the iohook
|
||||
* module in order to receive system calls to dispatch for emulation.
|
||||
*/
|
||||
HRESULT jbhook_util_ac_io_port_dispatch_irp(struct irp *irp);
|
||||
|
||||
/**
|
||||
* Enable ICCB emulation instead of the default ICCA emulation - some jubeats
|
||||
* will have IO errors unless explicitly set to ICCB mode.
|
||||
*/
|
||||
void jbhook_util_ac_io_set_iccb(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
#define LOG_MODULE "eamuse-hook"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static const char *jbhook_eamuse_konami_url = "eamuse.konami.";
|
||||
|
||||
static int(STDCALL *real_getaddrinfo)(
|
||||
PCSTR pNodeName,
|
||||
PCSTR pServiceName,
|
||||
const ADDRINFOA *pHints,
|
||||
PADDRINFOA *ppResult);
|
||||
|
||||
static int STDCALL my_getaddrinfo(
|
||||
PCSTR pNodeName,
|
||||
PCSTR pServiceName,
|
||||
const ADDRINFOA *pHints,
|
||||
PADDRINFOA *ppResult);
|
||||
|
||||
static DWORD WINAPI my_GetIpAddrTable(
|
||||
PMIB_IPADDRTABLE pIpAddrTable,
|
||||
PULONG pdwSize,
|
||||
BOOL bOrder
|
||||
);
|
||||
|
||||
static const struct hook_symbol eamuse_hook_syms[] = {
|
||||
{
|
||||
.name = "getaddrinfo",
|
||||
.ordinal = 176,
|
||||
.patch = my_getaddrinfo,
|
||||
.link = (void **) &real_getaddrinfo,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct hook_symbol network_hook_syms[] = {
|
||||
{
|
||||
.name = "GetIpAddrTable",
|
||||
.patch = my_GetIpAddrTable,
|
||||
},
|
||||
};
|
||||
|
||||
static int STDCALL my_getaddrinfo(
|
||||
PCSTR pNodeName,
|
||||
PCSTR pServiceName,
|
||||
const ADDRINFOA *pHints,
|
||||
PADDRINFOA *ppResult)
|
||||
{
|
||||
log_misc("my_getaddrinfo: %s, %s", pNodeName, pServiceName);
|
||||
|
||||
/* resolve eamuse.konami.fun/com to 127.0.0.1 to avoid lag spikes every
|
||||
150 seconds which might happen in-game as well */
|
||||
if (!strncmp(
|
||||
pNodeName,
|
||||
jbhook_eamuse_konami_url,
|
||||
strlen(jbhook_eamuse_konami_url))) {
|
||||
log_info("Resolve konami.eamuse.XXX -> localhost");
|
||||
pNodeName = "localhost";
|
||||
}
|
||||
|
||||
return real_getaddrinfo(pNodeName, pServiceName, pHints, ppResult);
|
||||
}
|
||||
|
||||
static DWORD get_best_ip(void) {
|
||||
PIP_ADAPTER_INFO info = NULL;
|
||||
struct net_addr addr;
|
||||
// fallback to a very obviously wrong value
|
||||
DWORD ret = (5 << 24) | (7 << 16) | (3 << 8) | (0 << 0);
|
||||
ULONG sz = 0;
|
||||
|
||||
// this uses our hooked GetAdaptersInfo from hooklib/adapter.c, thus using
|
||||
// the preferred interface instead of the default order
|
||||
log_assert(GetAdaptersInfo(info, &sz) == ERROR_BUFFER_OVERFLOW);
|
||||
|
||||
info = malloc(sz);
|
||||
|
||||
if(GetAdaptersInfo(info, &sz) != NO_ERROR) {
|
||||
goto CLEANUP;
|
||||
}
|
||||
|
||||
if(!net_str_parse(info->IpAddressList.IpAddress.String, &addr)) {
|
||||
goto CLEANUP;
|
||||
}
|
||||
|
||||
// MS docs say this should be in network byte order, but we need to return
|
||||
// host byte order for the game to not throw the IP error
|
||||
ret = addr.ipv4.addr;
|
||||
|
||||
CLEANUP:
|
||||
free(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* In new games via network.dll:network_addr_is_changed and in old games
|
||||
* directly, this function is called to compare the current IP address vs the IP
|
||||
* address when the game was first loaded. If the two are different, an error is
|
||||
* printed. It can be dismissed but will show up every ~10 seconds, impeding
|
||||
* gameplay. Because GetAdaptersInfo is hooked to return a (sometimes) different
|
||||
* adapter, computers with multiple interfaces will often fail this comparison
|
||||
* check. By also hooking this function, the IP addresses of the two calls will
|
||||
* agree.
|
||||
*/
|
||||
static DWORD WINAPI my_GetIpAddrTable(
|
||||
PMIB_IPADDRTABLE pIpAddrTable,
|
||||
PULONG pdwSize,
|
||||
BOOL bOrder
|
||||
) {
|
||||
ULONG in = *pdwSize;
|
||||
// 1 element return table
|
||||
*pdwSize = sizeof(MIB_IPADDRTABLE) + sizeof(MIB_IPADDRROW);
|
||||
|
||||
if(in < *pdwSize) {
|
||||
return ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
pIpAddrTable->dwNumEntries = 1;
|
||||
pIpAddrTable->table[0].dwAddr = get_best_ip();
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
void jbhook_util_eamuse_hook_init(void)
|
||||
{
|
||||
hook_table_apply(
|
||||
NULL, "ws2_32.dll", eamuse_hook_syms, lengthof(eamuse_hook_syms));
|
||||
hook_table_apply(
|
||||
NULL, "iphlpapi.dll", network_hook_syms, lengthof(network_hook_syms));
|
||||
|
||||
log_info("Inserted eamuse hooks");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef JBHOOK_UTIL_EAMUSE_H
|
||||
#define JBHOOK_UTIL_EAMUSE_H
|
||||
|
||||
void jbhook_util_eamuse_hook_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,180 @@
|
||||
#define LOG_MODULE "jbhook-gfx"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include "hook/com-proxy.h"
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "imports/glhelper.h"
|
||||
|
||||
#include "jbhook-util/gfx.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/time.h"
|
||||
|
||||
static void __stdcall hook_glFlush(void);
|
||||
static void (__stdcall *real_glFlush)(void);
|
||||
|
||||
static void hook_glBindFramebufferEXT(GLenum target, GLuint framebuffer);
|
||||
static void (*real_glBindFramebufferEXT)(GLenum target, GLuint framebuffer);
|
||||
|
||||
static const struct hook_symbol jbhook1_opengl_hook_syms[] = {
|
||||
{.name = "glFlush",
|
||||
.patch = hook_glFlush,
|
||||
.link = (void **) &real_glFlush},
|
||||
};
|
||||
|
||||
static const struct hook_symbol jbhook1_glhelper_hook_syms[] = {
|
||||
{.name = "glBindFramebufferEXT",
|
||||
.patch = hook_glBindFramebufferEXT,
|
||||
.link = (void **) &real_glBindFramebufferEXT},
|
||||
};
|
||||
|
||||
static DWORD STDCALL my_GetGlyphOutline(
|
||||
HDC hdc,
|
||||
UINT uChar,
|
||||
UINT uFormat,
|
||||
LPGLYPHMETRICS lpgm,
|
||||
DWORD cbBuffer,
|
||||
LPVOID lpvBuffer,
|
||||
const MAT2 *lpmat2);
|
||||
|
||||
static DWORD(STDCALL *real_GetGlyphOutline)(
|
||||
HDC hdc,
|
||||
UINT uChar,
|
||||
UINT uFormat,
|
||||
LPGLYPHMETRICS lpgm,
|
||||
DWORD cbBuffer,
|
||||
LPVOID lpvBuffer,
|
||||
const MAT2 *lpmat2);
|
||||
|
||||
static const struct hook_symbol gfx_hook_syms[] = {
|
||||
{.name = "GetGlyphOutlineA",
|
||||
.patch = my_GetGlyphOutline,
|
||||
.link = (void **) &real_GetGlyphOutline},
|
||||
{
|
||||
.name = "IsDBCSLeadByteEx",
|
||||
.patch = my_GetGlyphOutline, // !??
|
||||
.link = (void **) &real_GetGlyphOutline // !??
|
||||
},
|
||||
};
|
||||
|
||||
static DWORD STDCALL my_GetGlyphOutline(
|
||||
HDC hdc,
|
||||
UINT uChar,
|
||||
UINT uFormat,
|
||||
LPGLYPHMETRICS lpgm,
|
||||
DWORD cbBuffer,
|
||||
LPVOID lpvBuffer,
|
||||
const MAT2 *lpmat2)
|
||||
{
|
||||
if (IsDBCSLeadByteEx(CP_ACP, uChar & 0xFF)) {
|
||||
log_warning("!!!!!!!!");
|
||||
|
||||
char tmp[2];
|
||||
|
||||
tmp[0] = uChar & 0xFF;
|
||||
tmp[1] = (uChar >> 8) & 0xFF;
|
||||
|
||||
MultiByteToWideChar(
|
||||
CP_ACP, MB_USEGLYPHCHARS, tmp, 2, lpvBuffer, cbBuffer);
|
||||
}
|
||||
|
||||
return real_GetGlyphOutline(
|
||||
hdc, uChar, uFormat, lpgm, cbBuffer, lpvBuffer, lpmat2);
|
||||
}
|
||||
|
||||
void jbhook_util_gfx_hook_init(void)
|
||||
{
|
||||
hook_table_apply(NULL, "gdi32.dll", gfx_hook_syms, lengthof(gfx_hook_syms));
|
||||
|
||||
log_info("Inserted gfx hooks");
|
||||
}
|
||||
|
||||
void jbhook_util_gfx_set_windowed(bool framed)
|
||||
{
|
||||
}
|
||||
|
||||
void jbhook_util_gfx_install_vertical_hooks(void) {
|
||||
hook_table_apply(
|
||||
NULL,
|
||||
"opengl32.dll",
|
||||
jbhook1_opengl_hook_syms,
|
||||
lengthof(jbhook1_opengl_hook_syms));
|
||||
|
||||
hook_table_apply(
|
||||
NULL,
|
||||
"glhelper.dll",
|
||||
jbhook1_glhelper_hook_syms,
|
||||
lengthof(jbhook1_glhelper_hook_syms));
|
||||
|
||||
log_info("Inserted vertical display hooks");
|
||||
}
|
||||
|
||||
// only jubeat uses openGL, let alone needs rotation at all, so hardcode for now
|
||||
#define W 768
|
||||
#define H 1360
|
||||
|
||||
static GLuint fb;
|
||||
static GLuint color;
|
||||
|
||||
static void fb_init(void) {
|
||||
static bool init_done = false;
|
||||
|
||||
if(init_done) {
|
||||
return;
|
||||
}
|
||||
|
||||
init_done = true;
|
||||
|
||||
glGenFramebuffersEXT(1, &fb);
|
||||
glGenTextures(1, &color);
|
||||
}
|
||||
|
||||
static uint8_t pixels_raw[W*H*3];
|
||||
static uint8_t pixels_rot[W*H*3];
|
||||
|
||||
static void __stdcall hook_glFlush(void) {
|
||||
glReadPixels(0, 0, H, W, GL_RGB, GL_UNSIGNED_BYTE, pixels_raw);
|
||||
|
||||
for(size_t x = 0; x < W; x++) {
|
||||
for(size_t y = 0; y < H; y++) {
|
||||
memcpy(&pixels_rot[3*(y*W + x)], &pixels_raw[3*((W-x)*H + y)], 3);
|
||||
}
|
||||
}
|
||||
|
||||
real_glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDrawPixels(W, H, GL_RGB, GL_UNSIGNED_BYTE, pixels_rot);
|
||||
|
||||
real_glFlush();
|
||||
|
||||
fb_init();
|
||||
real_glBindFramebufferEXT(GL_FRAMEBUFFER, fb);
|
||||
glBindTexture(GL_TEXTURE_2D, color);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, H, W, 0, GL_RGBA,GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
|
||||
}
|
||||
|
||||
static void hook_glBindFramebufferEXT(GLenum target, GLuint framebuffer) {
|
||||
fb_init();
|
||||
|
||||
if(target == GL_FRAMEBUFFER && framebuffer == 0) {
|
||||
framebuffer = fb;
|
||||
}
|
||||
|
||||
return real_glBindFramebufferEXT(target, framebuffer);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef JBHOOK_D3D8_H
|
||||
#define JBHOOK_D3D8_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Hook some gfx related functions to patch gfx related stuff
|
||||
* like enabling window mode.
|
||||
*/
|
||||
void jbhook_util_gfx_hook_init(void);
|
||||
|
||||
/**
|
||||
* Set the game to window mode.
|
||||
*
|
||||
* @param framed True to add a window frame and make the window
|
||||
* movable, resizable, minizable. False for no frame.
|
||||
*/
|
||||
void jbhook_util_gfx_set_windowed(bool framed);
|
||||
|
||||
/**
|
||||
* Rotate a horizontal-by-default jubeat window to vertical
|
||||
*/
|
||||
void jbhook_util_gfx_install_vertical_hooks(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#define LOG_MODULE "jbhook-locale"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// ANSI/OEM Japanese; Japanese (Shift-JIS)
|
||||
#define CODEPAGE_SHIFT_JIS 932
|
||||
|
||||
static UINT WINAPI hook_GetACP();
|
||||
static UINT WINAPI hook_GetOEMCP();
|
||||
|
||||
static const struct hook_symbol locale_hook_syms[] = {
|
||||
{.name = "GetOEMCP",
|
||||
.patch = hook_GetACP},
|
||||
{.name = "GetOEMCP",
|
||||
.patch = hook_GetOEMCP},
|
||||
};
|
||||
|
||||
static UINT WINAPI hook_GetACP() {
|
||||
return CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
|
||||
static UINT WINAPI hook_GetOEMCP() {
|
||||
return CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
|
||||
|
||||
void jbhook_util_locale_hook_init(void)
|
||||
{
|
||||
hook_table_apply(NULL, "kernel32.dll", locale_hook_syms, lengthof(locale_hook_syms));
|
||||
|
||||
log_info("Inserted locale hooks");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef JBHOOK_LOCALE_H
|
||||
#define JBHOOK_LOCALE_H
|
||||
|
||||
void jbhook_util_locale_hook_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
#define LOG_MODULE "jbhook-mixer"
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/log.h"
|
||||
|
||||
MMRESULT STDCALL hook_mixerGetLineControlsA(HMIXEROBJ hmxobj, LPMIXERLINECONTROLSA pmxlc, DWORD fdwControls);
|
||||
MMRESULT STDCALL hook_mixerGetDevCapsA(UINT_PTR uMxId, LPMIXERCAPSA pmxcaps, UINT cbmxcaps);
|
||||
MMRESULT STDCALL hook_mixerOpen(LPHMIXER phmx, UINT uMxId, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen);
|
||||
MMRESULT STDCALL hook_mixerSetControlDetails(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails);
|
||||
MMRESULT STDCALL hook_mixerGetControlDetailsA(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails);
|
||||
MMRESULT STDCALL hook_mixerClose(HMIXER hmx);
|
||||
MMRESULT STDCALL hook_mixerGetLineInfoA(HMIXEROBJ hmxobj, LPMIXERLINEA pmxl, DWORD fdwInfo);
|
||||
|
||||
static const struct hook_symbol mixer_hook_syms[] = {
|
||||
{.name = "mixerGetLineControlsA",
|
||||
.patch = hook_mixerGetLineControlsA},
|
||||
{.name = "mixerGetDevCapsA",
|
||||
.patch = hook_mixerGetDevCapsA},
|
||||
{.name = "mixerOpen",
|
||||
.patch = hook_mixerOpen},
|
||||
{.name = "mixerSetControlDetails",
|
||||
.patch = hook_mixerSetControlDetails},
|
||||
{.name = "mixerGetControlDetailsA",
|
||||
.patch = hook_mixerGetControlDetailsA},
|
||||
{.name = "mixerClose",
|
||||
.patch = hook_mixerClose},
|
||||
{.name = "mixerGetLineInfoA",
|
||||
.patch = hook_mixerGetLineInfoA},
|
||||
};
|
||||
|
||||
/*
|
||||
Some of these functions return values which are used as pointer offsets into
|
||||
memory, but that doesn't matter because the game never *modifies* the
|
||||
pointed-to value, only passes it to the other mixer functions. So it's safe
|
||||
to not set any of the returning data.
|
||||
*/
|
||||
|
||||
MMRESULT STDCALL hook_mixerOpen(LPHMIXER phmx, UINT uMxId, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen) {
|
||||
*(DWORD*)phmx = 1234; // any non-zero value is fine
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerClose(HMIXER hmx) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerGetLineControlsA(HMIXEROBJ hmxobj, LPMIXERLINECONTROLSA pmxlc, DWORD fdwControls) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerGetDevCapsA(UINT_PTR uMxId, LPMIXERCAPSA pmxcaps, UINT cbmxcaps) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerGetControlDetailsA(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerGetLineInfoA(HMIXEROBJ hmxobj, LPMIXERLINEA pmxl, DWORD fdwInfo) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT STDCALL hook_mixerSetControlDetails(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
void jbhook_util_mixer_hook_init(void)
|
||||
{
|
||||
hook_table_apply(NULL, "winmm.dll", mixer_hook_syms, lengthof(mixer_hook_syms));
|
||||
|
||||
log_info("Inserted mixer hooks");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef JBHOOK_MIXER_H
|
||||
#define JBHOOK_MIXER_H
|
||||
|
||||
/**
|
||||
* Hook some mixer related functions and stub out attempts to change the volume.
|
||||
* In addition to being annoying, this can fail on Vista+ on some outputs,
|
||||
* despite not actually preventing sound from playing.
|
||||
*/
|
||||
void jbhook_util_mixer_hook_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bemanitools/jbio.h"
|
||||
|
||||
#include "jbhook-util/p3io.h"
|
||||
|
||||
#include "p3ioemu/emu.h"
|
||||
#include "p3ioemu/uart.h"
|
||||
|
||||
#include "security/rp-sign-key.h"
|
||||
#include "security/rp3.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static HRESULT jbhook_p3io_read_jamma(void *ctx, uint32_t *state);
|
||||
static HRESULT jbhook_p3io_get_roundplug(
|
||||
void *ctx, uint8_t plug_id, uint8_t *rom, uint8_t *eeprom);
|
||||
static HRESULT jbhook_p3io_set_outputs(void *ctx, uint32_t state);
|
||||
|
||||
/*
|
||||
0:0 b13
|
||||
0:1 -
|
||||
0:2 b15
|
||||
0:3 -
|
||||
0:4 test
|
||||
0:5 coin
|
||||
0:6 service
|
||||
0:7 -
|
||||
|
||||
1:0 -
|
||||
1:1 b4
|
||||
1:2 b8
|
||||
1:3 b12
|
||||
1:4 b16
|
||||
1:5 b3
|
||||
1:6 b7
|
||||
1:7 b11
|
||||
|
||||
2:0 -
|
||||
2:1 b2
|
||||
2:2 b6
|
||||
2:3 b10
|
||||
2:4 b14
|
||||
2:5 b1
|
||||
2:6 b5
|
||||
2:7 b9
|
||||
|
||||
3:0 -
|
||||
3:1 -
|
||||
3:2 -
|
||||
3:3 -
|
||||
3:4 -
|
||||
3:5 -
|
||||
3:6 -
|
||||
3:7 -
|
||||
*/
|
||||
static const uint32_t jbhook_p3io_panel_mappings[] = {
|
||||
(1 << 21),
|
||||
(1 << 17),
|
||||
(1 << 13),
|
||||
(1 << 9),
|
||||
(1 << 22),
|
||||
(1 << 18),
|
||||
(1 << 14),
|
||||
(1 << 10),
|
||||
(1 << 23),
|
||||
(1 << 19),
|
||||
(1 << 15),
|
||||
(1 << 11),
|
||||
(1 << 0),
|
||||
(1 << 20),
|
||||
(1 << 2),
|
||||
(1 << 12),
|
||||
};
|
||||
|
||||
static const uint32_t jbhook_p3io_sys_button_mappings[] = {
|
||||
(1 << 4),
|
||||
(1 << 6),
|
||||
(1 << 5),
|
||||
};
|
||||
|
||||
static struct security_mcode jbhook_p3io_mcode;
|
||||
static struct security_id jbhook_p3io_pcbid;
|
||||
static struct security_id jbhook_p3io_eamid;
|
||||
|
||||
static const struct p3io_ops p3io_ddr_ops = {
|
||||
.read_jamma = jbhook_p3io_read_jamma,
|
||||
.get_roundplug = jbhook_p3io_get_roundplug,
|
||||
.set_outputs = jbhook_p3io_set_outputs,
|
||||
};
|
||||
|
||||
void jbhook_util_p3io_init(
|
||||
const struct security_mcode *mcode,
|
||||
const struct security_id *pcbid,
|
||||
const struct security_id *eamid)
|
||||
{
|
||||
memcpy(&jbhook_p3io_mcode, mcode, sizeof(struct security_mcode));
|
||||
memcpy(&jbhook_p3io_pcbid, pcbid, sizeof(struct security_id));
|
||||
memcpy(&jbhook_p3io_eamid, eamid, sizeof(struct security_id));
|
||||
|
||||
p3io_emu_init(&p3io_ddr_ops, NULL);
|
||||
}
|
||||
|
||||
void jbhook_util_p3io_fini(void)
|
||||
{
|
||||
p3io_emu_fini();
|
||||
}
|
||||
|
||||
static HRESULT jbhook_p3io_read_jamma(void *ctx, uint32_t *state)
|
||||
{
|
||||
uint16_t panels;
|
||||
uint8_t buttons;
|
||||
|
||||
log_assert(state != NULL);
|
||||
|
||||
*state = 0;
|
||||
|
||||
if (!jb_io_read_inputs()) {
|
||||
log_warning("Reading inputs from jbio failed");
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
panels = jb_io_get_panel_inputs();
|
||||
buttons = jb_io_get_sys_inputs();
|
||||
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
// panels are active-low
|
||||
if ((panels & (1 << i)) == 0) {
|
||||
*state |= jbhook_p3io_panel_mappings[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
// sys buttons are active-high
|
||||
if (buttons & (1 << i)) {
|
||||
*state |= jbhook_p3io_sys_button_mappings[i];
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT jbhook_p3io_get_roundplug(
|
||||
void *ctx, uint8_t plug_id, uint8_t *rom, uint8_t *eeprom)
|
||||
{
|
||||
struct security_rp3_eeprom eeprom_out;
|
||||
|
||||
if (plug_id == 0) {
|
||||
/* black */
|
||||
memcpy(rom, jbhook_p3io_pcbid.id, sizeof(jbhook_p3io_pcbid.id));
|
||||
security_rp3_generate_signed_eeprom_data(
|
||||
SECURITY_RP_UTIL_RP_TYPE_BLACK,
|
||||
&security_rp_sign_key_black_gfdmv4,
|
||||
&jbhook_p3io_mcode,
|
||||
&jbhook_p3io_pcbid,
|
||||
&eeprom_out);
|
||||
} else {
|
||||
/* white */
|
||||
memcpy(rom, jbhook_p3io_eamid.id, sizeof(jbhook_p3io_eamid.id));
|
||||
security_rp3_generate_signed_eeprom_data(
|
||||
SECURITY_RP_UTIL_RP_TYPE_WHITE,
|
||||
&security_rp_sign_key_white_eamuse,
|
||||
&security_mcode_eamuse,
|
||||
&jbhook_p3io_eamid,
|
||||
&eeprom_out);
|
||||
}
|
||||
|
||||
memcpy(eeprom, &eeprom_out, sizeof(struct security_rp3_eeprom));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT jbhook_p3io_set_outputs(void *ctx, uint32_t state) {
|
||||
uint8_t p3io_panel = (state & 0x00FF00) >> 8;
|
||||
uint8_t p3io_coinblocker = (state & 0xFF0000) >> 16;
|
||||
|
||||
enum jb_io_panel_mode panel_mode;
|
||||
|
||||
switch(p3io_panel) {
|
||||
case 5:
|
||||
panel_mode = JB_IO_PANEL_MODE_TOP_LEFT;
|
||||
break;
|
||||
case 4:
|
||||
panel_mode = JB_IO_PANEL_MODE_TOP_RIGHT;
|
||||
break;
|
||||
case 6:
|
||||
panel_mode = JB_IO_PANEL_MODE_BOTTOM_RIGHT;
|
||||
break;
|
||||
case 7:
|
||||
panel_mode = JB_IO_PANEL_MODE_BOTTOM_LEFT;
|
||||
break;
|
||||
default:
|
||||
panel_mode = JB_IO_PANEL_MODE_ALL;
|
||||
break;
|
||||
}
|
||||
|
||||
jb_io_set_panel_mode(panel_mode);
|
||||
|
||||
// 0x40 has been seen to unblock, no other values observed
|
||||
jb_io_set_coin_blocker(p3io_coinblocker == 0x00);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef JBHOOK1_P3IO_H
|
||||
#define JBHOOK1_P3IO_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "security/id.h"
|
||||
#include "security/mcode.h"
|
||||
|
||||
/**
|
||||
* Initialize the jb01-04 specific p3io emulation backend.
|
||||
*
|
||||
* @param mcode Mcode of the target game to run. Required for dongle emulation.
|
||||
* @param pcbid PCBDID
|
||||
* @param eamid EAMID
|
||||
*/
|
||||
void jbhook_util_p3io_init(
|
||||
const struct security_mcode *mcode,
|
||||
const struct security_id *pcbid,
|
||||
const struct security_id *eamid);
|
||||
|
||||
/**
|
||||
* Shutdown the p3io emulation backend.
|
||||
*/
|
||||
void jbhook_util_p3io_fini(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
#define LOG_MODULE "jbhook-io"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "bemanitools/jbio.h"
|
||||
|
||||
#include "imports/avs.h"
|
||||
|
||||
#include "jbhook-util/p4io.h"
|
||||
|
||||
#include "p4io/cmd.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static void jbhook_io_jamma2_read(void *resp, uint32_t nbytes);
|
||||
static uint32_t jbhook_command_handle(
|
||||
uint8_t cmd,
|
||||
const void *payload,
|
||||
uint32_t payload_len,
|
||||
void *resp,
|
||||
uint32_t resp_max_len);
|
||||
|
||||
static const struct p4ioemu_device_msg_hook jbhook_p4io_msg = {
|
||||
.jamma2_read = jbhook_io_jamma2_read,
|
||||
.command_handle = jbhook_command_handle,
|
||||
.roundplug_read_id = NULL,
|
||||
.roundplug_read_mem = NULL};
|
||||
|
||||
/*
|
||||
0:0 ???
|
||||
0:1 b2
|
||||
0:2 b6
|
||||
0:3 b10
|
||||
0:4 b14
|
||||
0:5 b1
|
||||
0:6 b5
|
||||
0:7 b9
|
||||
|
||||
1:0 ???
|
||||
1:1 b4
|
||||
1:2 b8
|
||||
1:3 b12
|
||||
1:4 b16
|
||||
1:5 b3
|
||||
1:6 b7
|
||||
1:7 b11
|
||||
|
||||
2:0 b13
|
||||
2:1 ???
|
||||
2:2 ???
|
||||
2:3 ???
|
||||
2:4 b15
|
||||
2:5 ???
|
||||
2:6 ???
|
||||
2:7 ???
|
||||
|
||||
3:0 coin
|
||||
3:1 service
|
||||
3:2 ???
|
||||
3:3 ???
|
||||
3:4 test
|
||||
3:5 ???
|
||||
3:6 ???
|
||||
3:7 ???
|
||||
*/
|
||||
static const uint32_t jbhook_io_panel_mappings[] = {
|
||||
(1 << 5),
|
||||
(1 << 1),
|
||||
(1 << 13),
|
||||
(1 << 9),
|
||||
(1 << 6),
|
||||
(1 << 2),
|
||||
(1 << 14),
|
||||
(1 << 10),
|
||||
(1 << 7),
|
||||
(1 << 3),
|
||||
(1 << 15),
|
||||
(1 << 11),
|
||||
(1 << 16),
|
||||
(1 << 4),
|
||||
(1 << 20),
|
||||
(1 << 12),
|
||||
};
|
||||
|
||||
static const uint32_t jbhook_io_sys_button_mappings[] = {
|
||||
(1 << 28),
|
||||
(1 << 25),
|
||||
(1 << 24),
|
||||
};
|
||||
|
||||
static void jbhook_io_jamma2_read(void *resp, uint32_t nbytes)
|
||||
{
|
||||
uint32_t *inputs = (uint32_t *) resp;
|
||||
uint16_t panels;
|
||||
uint8_t buttons;
|
||||
|
||||
/* lower three bytes low active, highest byte high active */
|
||||
*inputs = 0x00FFFFFF;
|
||||
|
||||
if (!jb_io_read_inputs()) {
|
||||
log_warning("Reading inputs from jbio failed");
|
||||
return;
|
||||
}
|
||||
|
||||
panels = jb_io_get_panel_inputs();
|
||||
buttons = jb_io_get_sys_inputs();
|
||||
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
if (panels & (1 << i)) {
|
||||
*inputs &= ~jbhook_io_panel_mappings[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
if (buttons & (1 << i)) {
|
||||
*inputs |= jbhook_io_sys_button_mappings[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t jbhook_command_handle(
|
||||
uint8_t cmd,
|
||||
const void *payload,
|
||||
uint32_t payload_len,
|
||||
void *resp,
|
||||
uint32_t resp_max_len)
|
||||
{
|
||||
switch (cmd) {
|
||||
case P4IO_CMD_COINSTOCK: {
|
||||
// on is 0x00, off is either 0x10 or 0x20 depending on whether it's
|
||||
// during gameplay (0x10) or test menu (0x20). Both seem to have the
|
||||
// same effect
|
||||
jb_io_set_coin_blocker(*(uint8_t*)payload == 0x00);
|
||||
|
||||
// this actually returns the coinstock, don't care for it
|
||||
memset(resp, 0, 4);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
case P4IO_CMD_SET_PORTOUT: {
|
||||
const struct p4io_req_panel_mode *req =
|
||||
(const struct p4io_req_panel_mode *) payload;
|
||||
|
||||
// always fallback to ALL if input is unknown
|
||||
enum jb_io_panel_mode mode = JB_IO_PANEL_MODE_ALL;
|
||||
|
||||
if(req->is_single) {
|
||||
switch(req->mode) {
|
||||
case 0x0001:
|
||||
mode = JB_IO_PANEL_MODE_TOP_LEFT;
|
||||
break;
|
||||
|
||||
case 0x0000:
|
||||
mode = JB_IO_PANEL_MODE_TOP_RIGHT;
|
||||
break;
|
||||
|
||||
case 0x0101:
|
||||
mode = JB_IO_PANEL_MODE_BOTTOM_LEFT;
|
||||
break;
|
||||
|
||||
case 0x0100:
|
||||
mode = JB_IO_PANEL_MODE_BOTTOM_RIGHT;
|
||||
break;
|
||||
|
||||
default:
|
||||
mode = JB_IO_PANEL_MODE_ALL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jb_io_set_panel_mode(mode);
|
||||
|
||||
memset(resp, 0, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
default:
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
const struct p4ioemu_device_msg_hook *jbhook_p4io_init(void)
|
||||
{
|
||||
return &jbhook_p4io_msg;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef JBHOOK_IO_H
|
||||
#define JBHOOK_IO_H
|
||||
|
||||
#include "p4ioemu/device.h"
|
||||
|
||||
const struct p4ioemu_device_msg_hook *jbhook_p4io_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef JBHOOK_UTIL_SECURITY_H
|
||||
#define JBHOOK_UTIL_SECURITY_H
|
||||
|
||||
#include "security/mcode.h"
|
||||
|
||||
static const struct security_mcode jbhook_util_security_default_mcode = {
|
||||
.header = SECURITY_MCODE_HEADER,
|
||||
.unkn = SECURITY_MCODE_UNKN_C,
|
||||
.game = SECURITY_MCODE_GAME_JB_1,
|
||||
.region = SECURITY_MCODE_REGION_JAPAN,
|
||||
.cabinet = SECURITY_MCODE_CABINET_A,
|
||||
.revision = SECURITY_MCODE_REVISION_B,
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user