mirror of
https://gitea.tendokyu.moe/TeamTofuShop/taitools.git
synced 2026-09-22 22:28:06 +03:00
468 lines
16 KiB
C
468 lines
16 KiB
C
#include <windows.h>
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <winioctl.h>
|
|
|
|
#include "idmac/idmac.h"
|
|
#include "idmac/jvs.h"
|
|
|
|
#include "hook/table.h"
|
|
#include "hook/iohook.h"
|
|
#include "util/dprintf.h"
|
|
#include "util/str.h"
|
|
|
|
#define IDMAC_IOCTL_DMA_READ CTL_CODE(0xC350, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_DMA_WRITE CTL_CODE(0xC350, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_REGISTER_READ CTL_CODE(0xC350, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_REGISTER_WRITE CTL_CODE(0xC350, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_REGISTER_BUFFER_READ CTL_CODE(0xC350, 0x804, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_REGISTER_BUFFER_WRITE CTL_CODE(0xC350, 0x805, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_READ CTL_CODE(0xC350, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_WRITE CTL_CODE(0xC350, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_BUFFER_READ CTL_CODE(0xC350, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_BUFFER_WRITE CTL_CODE(0xC350, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_READ_EXT CTL_CODE(0xC350, 0x820, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
#define IDMAC_IOCTL_MEMORY_WRITE_EXT CTL_CODE(0xC350, 0x821, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
|
|
static uint8_t dipsw = 0;
|
|
static HANDLE idmac_fd;
|
|
|
|
static HRESULT idmac_handle_irp(struct irp *irp);
|
|
static HRESULT idmac_handle_open(struct irp *irp);
|
|
static HRESULT idmac_handle_close(struct irp *irp);
|
|
static HRESULT idmac_handle_ioctl(struct irp *irp);
|
|
|
|
static DWORD hook_iDmacDrvOpen(int dev_num, DWORD *dev_handle, DWORD *err);
|
|
static DWORD hook_iDmacDrvClose(HANDLE a1, DWORD *lp);
|
|
static int hook_iDmacDrvDmaRead(HANDLE a1, void *lp, int ucb, unsigned int *err);
|
|
static int hook_iDmacDrvDmaWrite(HANDLE a1, void *lp, int ucb, unsigned int *err);
|
|
static int hook_iDmacDrvRegisterRead(HANDLE a1, DWORD register_id, DWORD* lp, unsigned int *err);
|
|
static int hook_iDmacDrvRegisterWrite(HANDLE a1, DWORD register_id, int a3, DWORD *lp);
|
|
static int hook_iDmacDrvRegisterBufferRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5);
|
|
static int hook_iDmacDrvRegisterBufferWrite(HANDLE a1, DWORD BytesReturned, void *lp, UINT_PTR ucb, DWORD *a5);
|
|
static int hook_iDmacDrvMemoryRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, DWORD *a4);
|
|
static int hook_iDmacDrvMemoryWrite(HANDLE a1, DWORD BytesReturned, int a3, DWORD *lp);
|
|
static int hook_iDmacDrvMemoryBufferRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5);
|
|
static int hook_iDmacDrvMemoryBufferWrite(HANDLE a1, unsigned int a2, void *lp, UINT_PTR ucb, DWORD *a5);
|
|
static int hook_iDmacDrvMemoryReadExt(HANDLE a1, DWORD BytesReturned, unsigned int a3, LPVOID lp, int nOutBufferSize, DWORD *a6);
|
|
static int hook_iDmacDrvMemoryWriteExt(HANDLE a1, int a2, unsigned int a3, void *Source, rsize_t DestinationSize, unsigned int *lp);
|
|
|
|
static DWORD (*next_iDmacDrvOpen)(int dev_num, DWORD *dev_handle, DWORD *err);
|
|
static DWORD (*next_iDmacDrvClose)(HANDLE a1, DWORD *lp);
|
|
static int (*next_iDmacDrvDmaRead)(HANDLE a1, void *lp, int ucb, unsigned int *err);
|
|
static int (*next_iDmacDrvDmaWrite)(HANDLE a1, void *lp, int ucb, unsigned int *err);
|
|
static int (*next_iDmacDrvRegisterRead)(HANDLE a1, DWORD BytesReturned, LPVOID lp, unsigned int *err);
|
|
static int (*next_iDmacDrvRegisterWrite)(HANDLE a1, DWORD BytesReturned, int a3, DWORD *lp);
|
|
static int (*next_iDmacDrvRegisterBufferRead)(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5);
|
|
static int (*next_iDmacDrvRegisterBufferWrite)(HANDLE a1, DWORD BytesReturned, void *lp, UINT_PTR ucb, DWORD *a5);
|
|
static int (*next_iDmacDrvMemoryRead)(HANDLE a1, DWORD BytesReturned, LPVOID lp, DWORD *a4);
|
|
static int (*next_iDmacDrvMemoryWrite)(HANDLE a1, DWORD BytesReturned, int a3, DWORD *lp);
|
|
static int (*next_iDmacDrvMemoryBufferRead)(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5);
|
|
static int (*next_iDmacDrvMemoryBufferWrite)(HANDLE a1, unsigned int a2, void *lp, UINT_PTR ucb, DWORD *a5);
|
|
static int (*next_iDmacDrvMemoryReadExt)(HANDLE a1, DWORD BytesReturned, unsigned int a3, LPVOID lp, int nOutBufferSize, DWORD *a6);
|
|
static int (*next_iDmacDrvMemoryWriteExt)(HANDLE a1, int a2, unsigned int a3, void *Source, rsize_t DestinationSize, unsigned int *lp);
|
|
|
|
static const struct hook_symbol idmac_hooks[] = {
|
|
{
|
|
.name = "iDmacDrvOpen",
|
|
.ordinal = 1,
|
|
.patch = hook_iDmacDrvOpen,
|
|
.link = (void **) &next_iDmacDrvOpen,
|
|
},{
|
|
.name = "iDmacDrvClose",
|
|
.ordinal = 2,
|
|
.patch = hook_iDmacDrvClose,
|
|
.link = (void **) &next_iDmacDrvClose,
|
|
},{
|
|
.name = "iDmacDrvDmaRead",
|
|
.ordinal = 3,
|
|
.patch = hook_iDmacDrvDmaRead,
|
|
.link = (void **) &next_iDmacDrvDmaRead,
|
|
},{
|
|
.name = "iDmacDrvDmaWrite",
|
|
.ordinal = 4,
|
|
.patch = hook_iDmacDrvDmaWrite,
|
|
.link = (void **) &next_iDmacDrvDmaWrite,
|
|
},{
|
|
.name = "iDmacDrvRegisterRead",
|
|
.ordinal = 5,
|
|
.patch = hook_iDmacDrvRegisterRead,
|
|
.link = (void **) &next_iDmacDrvRegisterRead,
|
|
},{
|
|
.name = "iDmacDrvRegisterWrite",
|
|
.ordinal = 6,
|
|
.patch = hook_iDmacDrvRegisterWrite,
|
|
.link = (void **) &next_iDmacDrvRegisterWrite,
|
|
},{
|
|
.name = "iDmacDrvRegisterBufferRead",
|
|
.ordinal = 7,
|
|
.patch = hook_iDmacDrvRegisterBufferRead,
|
|
.link = (void **) &next_iDmacDrvRegisterBufferRead,
|
|
},{
|
|
.name = "iDmacDrvRegisterBufferWrite",
|
|
.ordinal = 8,
|
|
.patch = hook_iDmacDrvRegisterBufferWrite,
|
|
.link = (void **) &next_iDmacDrvRegisterBufferWrite,
|
|
},{
|
|
.name = "iDmacDrvMemoryRead",
|
|
.ordinal = 9,
|
|
.patch = hook_iDmacDrvMemoryRead,
|
|
.link = (void **) &next_iDmacDrvMemoryRead,
|
|
},{
|
|
.name = "iDmacDrvMemoryWrite",
|
|
.ordinal = 10,
|
|
.patch = hook_iDmacDrvMemoryWrite,
|
|
.link = (void **) &next_iDmacDrvMemoryWrite,
|
|
},{
|
|
.name = "iDmacDrvMemoryBufferRead",
|
|
.ordinal = 11,
|
|
.patch = hook_iDmacDrvMemoryBufferRead,
|
|
.link = (void **) &next_iDmacDrvMemoryBufferRead,
|
|
},{
|
|
.name = "iDmacDrvMemoryBufferWrite",
|
|
.ordinal = 12,
|
|
.patch = hook_iDmacDrvMemoryBufferWrite,
|
|
.link = (void **) &next_iDmacDrvMemoryBufferWrite,
|
|
},{
|
|
.name = "iDmacDrvMemoryReadExt",
|
|
.ordinal = 13,
|
|
.patch = hook_iDmacDrvMemoryReadExt,
|
|
.link = (void **) &next_iDmacDrvMemoryReadExt,
|
|
},{
|
|
.name = "iDmacDrvMemoryWriteExt",
|
|
.ordinal = 14,
|
|
.patch = hook_iDmacDrvMemoryWriteExt,
|
|
.link = (void **) &next_iDmacDrvMemoryWriteExt,
|
|
},
|
|
};
|
|
|
|
void idmac_hook_table_apply(HMODULE target)
|
|
{
|
|
hook_table_apply(
|
|
target,
|
|
"iDmacDrv64.dll",
|
|
idmac_hooks,
|
|
_countof(idmac_hooks)
|
|
);
|
|
hook_table_apply(
|
|
target,
|
|
"iDmacDrv32.dll",
|
|
idmac_hooks,
|
|
_countof(idmac_hooks)
|
|
);
|
|
}
|
|
|
|
HRESULT idmac_hook_init(const struct idmac_config *cfg, struct idmac_hardware *hardware)
|
|
{
|
|
HRESULT hr;
|
|
|
|
assert(cfg != NULL);
|
|
|
|
dprintf("iDmac: Init\n");
|
|
|
|
dipsw = hardware->dipsw & 0x0F;
|
|
|
|
if (cfg->dipsw >> 4) {
|
|
dipsw = cfg->dipsw & 0x0F;
|
|
}
|
|
|
|
hr = jvs_hook_init(&cfg->jvs, hardware->jvs);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = fastio_hook_init(hardware->fastio_main, hardware->fastio_sub);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
idmac_hook_table_apply(NULL);
|
|
|
|
hr = iohook_open_nul_fd(&idmac_fd);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = iohook_push_handler(idmac_handle_irp);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT idmac_handle_irp(struct irp *irp)
|
|
{
|
|
assert(irp != NULL);
|
|
|
|
if (irp->op != IRP_OP_OPEN && irp->fd != idmac_fd) {
|
|
return iohook_invoke_next(irp);
|
|
}
|
|
|
|
switch (irp->op) {
|
|
case IRP_OP_OPEN: return idmac_handle_open(irp);
|
|
case IRP_OP_CLOSE: return idmac_handle_close(irp);
|
|
case IRP_OP_IOCTL: return idmac_handle_ioctl(irp);
|
|
default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
|
}
|
|
}
|
|
|
|
static HRESULT idmac_handle_open(struct irp *irp)
|
|
{
|
|
if (!wstr_ieq(irp->open_filename, L"\\\\.\\iDmacDrv0")) {
|
|
return iohook_invoke_next(irp);
|
|
}
|
|
|
|
dprintf("iDmac: Opened handle\n");
|
|
irp->fd = idmac_fd;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT idmac_handle_close(struct irp *irp)
|
|
{
|
|
dprintf("iDmac: Closed handle\n");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT idmac_handle_ioctl(struct irp *irp)
|
|
{
|
|
switch (irp->ioctl) {
|
|
/*case IDMAC_IOCTL_DMA_READ:
|
|
return hook_iDmacDrvDmaRead(irp);
|
|
|
|
case IDMAC_IOCTL_DMA_WRITE:
|
|
return hook_iDmacDrvDmaWrite(irp);
|
|
|
|
case IDMAC_IOCTL_REGISTER_READ:
|
|
return hook_iDmacDrvRegisterRead(irp);
|
|
|
|
case IDMAC_IOCTL_REGISTER_WRITE:
|
|
return hook_iDmacDrvRegisterWrite(irp);
|
|
|
|
case IDMAC_IOCTL_REGISTER_BUFFER_READ:
|
|
return hook_iDmacDrvRegisterBufferRead(irp);
|
|
|
|
case IDMAC_IOCTL_REGISTER_BUFFER_WRITE:
|
|
return hook_iDmacDrvRegisterBufferWrite(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_READ:
|
|
return hook_iDmacDrvMemoryRead(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_WRITE:
|
|
return hook_iDmacDrvMemoryWrite(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_BUFFER_READ:
|
|
return hook_iDmacDrvMemoryBufferRead(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_BUFFER_WRITE:
|
|
return hook_iDmacDrvMemoryBufferWrite(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_READ_EXT:
|
|
return hook_iDmacDrvMemoryReadExt(irp);
|
|
|
|
case IDMAC_IOCTL_MEMORY_WRITE_EXT:
|
|
return hook_iDmacDrvMemoryWriteExt(irp);*/
|
|
|
|
default:
|
|
dprintf("iDmac: Unknown ioctl %#08x, write %i read %i\n",
|
|
irp->ioctl,
|
|
(int) irp->write.nbytes,
|
|
(int) irp->read.nbytes);
|
|
|
|
return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
|
}
|
|
}
|
|
|
|
static DWORD hook_iDmacDrvOpen(int dev_num, DWORD *dev_handle, DWORD *err)
|
|
{
|
|
dprintf("hook_iDmacDrvOpen: Open device #%X\n", dev_num);
|
|
return 0;
|
|
}
|
|
|
|
static DWORD hook_iDmacDrvClose(HANDLE dev_handle, DWORD *lp)
|
|
{
|
|
dprintf("hook_iDmacDrvClose: Close handle %p\n", dev_handle);
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvDmaRead(HANDLE a1, void *lp, int ucb, unsigned int *err)
|
|
{
|
|
dprintf("hook_iDmacDrvDmaRead: Read %d bytes\n", ucb);
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvDmaWrite(HANDLE a1, void *lp, int ucb, unsigned int *err)
|
|
{
|
|
dprintf("hook_iDmacDrvDmaWrite: Write %d bytes\n", ucb);
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvRegisterRead(HANDLE a1, DWORD register_id, DWORD* register_val, unsigned int *err)
|
|
{
|
|
//dprintf("hook_iDmacDrvRegisterRead: This code should not run!\n");
|
|
*register_val = 0;
|
|
*err = 0;
|
|
|
|
switch (register_id) {
|
|
case 0x0400: break; // DMA card ID
|
|
|
|
// Magic values taken from https://github.com/asesidaa/GCLoader-new/blob/main/iDmacDrv32.cpp
|
|
// Both of these were set to -2 prior to work with TFFAC...
|
|
case 0x4000: *register_val = 0x00FF00FF; break; // Main Status
|
|
case 0x4004: *register_val = 0x00FF0000; break; // Sub Status
|
|
|
|
case 0x4120: // Main Buttons
|
|
fastio_digital_read(NULL, 0, (uint32_t *)register_val);
|
|
break;
|
|
|
|
case 0x4124: // Analog 1
|
|
fastio_analog_in(NULL, 0, 1, (uint32_t *)register_val);
|
|
break;
|
|
case 0x4128: // Analog 2
|
|
fastio_analog_in(NULL, 0, 2, (uint32_t *)register_val);
|
|
break;
|
|
case 0x412C: // Analog 3
|
|
fastio_analog_in(NULL, 0, 3, (uint32_t *)register_val);
|
|
break;
|
|
|
|
case 0x4140:
|
|
fastio_coins_in(NULL, 0, 1, (uint32_t *)register_val);
|
|
break; // Coin slot 1
|
|
case 0x4144:
|
|
fastio_coins_in(NULL, 0, 2, (uint32_t *)register_val);
|
|
break; // Coin slot 2
|
|
|
|
// TODO: Let games specify hub connection
|
|
case 0x4150: *register_val = 0x0001823C; break; // FastIO hub port1 status?
|
|
case 0x4154: break; // FastIO hub port2 status?
|
|
case 0x4158: break; // FastIO hub port3 status?
|
|
case 0x415C: break; // FastIO hub port4 status?
|
|
|
|
case 0x41A0: // Sub buttons
|
|
fastio_digital_read(NULL, 1, (uint32_t *)register_val);
|
|
break;
|
|
|
|
case 0x41A4: // sub Analog 1
|
|
fastio_analog_in(NULL, 1, 1, (uint32_t *)register_val);
|
|
break;
|
|
case 0x41A8: // sub Analog 2
|
|
fastio_analog_in(NULL, 1, 1, (uint32_t *)register_val);
|
|
break;
|
|
case 0x41AC: // sub Analog 3
|
|
fastio_analog_in(NULL, 1, 1, (uint32_t *)register_val);
|
|
break;
|
|
|
|
case 0x41C0:
|
|
fastio_coins_in(NULL, 1, 1, (uint32_t *)register_val);
|
|
break; // Sub Coin slot 1
|
|
case 0x41C4:
|
|
fastio_coins_in(NULL, 1, 2, (uint32_t *)register_val);
|
|
break; // Sub Coin slot 2
|
|
|
|
default:
|
|
dprintf("iDmac: Requested read register %04lX\n", register_id);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvRegisterWrite(HANDLE a1, DWORD register_id, int a3, DWORD *lp)
|
|
{
|
|
//dprintf("hook_iDmacDrvRegisterWrite: Write %d to register %04lX\n", a3, register_id);
|
|
switch (register_id) {
|
|
case 0x4000: // Main reset
|
|
fastio_reset(NULL, 0, a3);
|
|
break;
|
|
case 0x4004: // Sub reset
|
|
fastio_reset(NULL, 1, a3);
|
|
break;
|
|
|
|
case 0x4100: // Main GPIO
|
|
case 0x4104:
|
|
case 0x4108:
|
|
case 0x410C:
|
|
fastio_gpio_out(NULL, 0, register_id - 0x4100, a3);
|
|
break;
|
|
|
|
case 0x4140: // Main coin slot 1
|
|
fastio_coins_out(NULL, 0, 1, a3);
|
|
break;
|
|
case 0x4144: // Main coin slot 2
|
|
fastio_coins_out(NULL, 0, 2, a3);
|
|
break;
|
|
|
|
case 0x4180: // Sub GPIO
|
|
case 0x4184:
|
|
case 0x4188:
|
|
case 0x418C:
|
|
fastio_gpio_out(NULL, 1, register_id - 0x4180, a3);
|
|
break;
|
|
|
|
case 0x41C0: // Sub coin slot 1
|
|
fastio_coins_out(NULL, 1, 1, a3);
|
|
break;
|
|
case 0x41C4: // Sub coin slot 2
|
|
fastio_coins_out(NULL, 1, 2, a3);
|
|
break;
|
|
|
|
default:
|
|
dprintf("iDmac: Requested write register %04lX\n", register_id);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvRegisterBufferRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5)
|
|
{
|
|
//dprintf("hook_iDmacDrvRegisterBufferRead: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvRegisterBufferWrite(HANDLE a1, DWORD BytesReturned, void *lp, UINT_PTR ucb, DWORD *a5)
|
|
{
|
|
//dprintf("hook_iDmacDrvRegisterBufferWrite: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, DWORD *a4)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryRead: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryWrite(HANDLE a1, DWORD BytesReturned, int a3, DWORD *lp)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryWrite: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryBufferRead(HANDLE a1, DWORD BytesReturned, LPVOID lp, UINT_PTR ucb, DWORD *a5)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryBufferRead: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryBufferWrite(HANDLE a1, unsigned int a2, void *lp, UINT_PTR ucb, DWORD *a5)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryBufferWrite: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryReadExt(HANDLE a1, DWORD BytesReturned, unsigned int a3, LPVOID lp, int nOutBufferSize, DWORD *a6)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryReadExt: This code should not run!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hook_iDmacDrvMemoryWriteExt(HANDLE a1, int a2, unsigned int a3, void *Source, rsize_t DestinationSize, unsigned int *lp)
|
|
{
|
|
//dprintf("hook_iDmacDrvMemoryWriteExt: This code should not run!\n");
|
|
return 0;
|
|
}
|