mirror of https://github.com/PCSX2/pcsx2.git
678 lines
13 KiB
C++
678 lines
13 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2014 David Quintana [gigaherz]
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 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 PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#define WINVER 0x0600
|
|
#define _WIN32_WINNT 0x0600
|
|
|
|
#include <winsock2.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <Winioctl.h>
|
|
#include <fcntl.h>
|
|
#include <windows.h>
|
|
#include <stdarg.h>
|
|
#define EXTERN
|
|
#include "DEV9.h"
|
|
#undef EXTERN
|
|
#include "Config.h"
|
|
#include "smap.h"
|
|
#include "ata.h"
|
|
|
|
#ifdef __WIN32__
|
|
#pragma warning(disable:4244)
|
|
|
|
HINSTANCE hInst=NULL;
|
|
#endif
|
|
|
|
//#define HDD_48BIT
|
|
|
|
u8 eeprom[] = {
|
|
//0x6D, 0x76, 0x63, 0x61, 0x31, 0x30, 0x08, 0x01,
|
|
0x76, 0x6D, 0x61, 0x63, 0x30, 0x31, 0x07, 0x02,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
u32 *iopPC;
|
|
|
|
const unsigned char version = PS2E_DEV9_VERSION;
|
|
const unsigned char revision = 0;
|
|
const unsigned char build = 4; // increase that with each version
|
|
|
|
|
|
static char *libraryName = "GiGaHeRz's DEV9 Driver"
|
|
#ifdef _DEBUG
|
|
"(debug)"
|
|
#endif
|
|
;
|
|
|
|
HANDLE hEeprom;
|
|
HANDLE mapping;
|
|
|
|
u32 CALLBACK PS2EgetLibType() {
|
|
return PS2E_LT_DEV9;
|
|
}
|
|
|
|
char* CALLBACK PS2EgetLibName() {
|
|
return libraryName;
|
|
}
|
|
|
|
u32 CALLBACK PS2EgetLibVersion2(u32 type) {
|
|
return (version<<16) | (revision<<8) | build;
|
|
}
|
|
|
|
|
|
std::string s_strIniPath = "inis";
|
|
std::string s_strLogPath = "logs";
|
|
// Warning: The below log function is SLOW. Better fix it before attempting to use it.
|
|
#ifdef _DEBUG
|
|
int Log = 1;
|
|
#else
|
|
int Log = 0;
|
|
#endif
|
|
|
|
void __Log(char *fmt, ...) {
|
|
if (!Log) return;
|
|
va_list list;
|
|
|
|
static int ticks=-1;
|
|
int nticks=GetTickCount();
|
|
|
|
if(ticks==-1) ticks=nticks;
|
|
|
|
if(iopPC!=NULL)
|
|
{
|
|
DEV9Log.Write("[%10d + %4d, IOP PC = %08x] ", nticks, nticks - ticks, *iopPC);
|
|
}
|
|
else
|
|
{
|
|
DEV9Log.Write( "[%10d + %4d] ", nticks, nticks - ticks);
|
|
}
|
|
ticks=nticks;
|
|
|
|
va_start(list, fmt);
|
|
DEV9Log.Write(fmt, list);
|
|
va_end(list);
|
|
}
|
|
|
|
void LogInit()
|
|
{
|
|
const std::string LogFile(s_strLogPath + "/dev9Log.txt");
|
|
DEV9Log.WriteToFile = true;
|
|
DEV9Log.Open(LogFile);
|
|
}
|
|
|
|
s32 CALLBACK DEV9init()
|
|
{
|
|
|
|
#ifdef DEV9_LOG_ENABLE
|
|
LogInit();
|
|
DEV9_LOG("DEV9init\n");
|
|
#endif
|
|
memset(&dev9, 0, sizeof(dev9));
|
|
DEV9_LOG("DEV9init2\n");
|
|
|
|
DEV9_LOG("DEV9init3\n");
|
|
|
|
FLASHinit();
|
|
|
|
hEeprom = CreateFile(
|
|
"eeprom.dat",
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_WRITE_THROUGH,
|
|
NULL
|
|
);
|
|
|
|
if(hEeprom==INVALID_HANDLE_VALUE)
|
|
{
|
|
dev9.eeprom=(u16*)eeprom;
|
|
}
|
|
else
|
|
{
|
|
mapping=CreateFileMapping(hEeprom,NULL,PAGE_READWRITE,0,0,NULL);
|
|
if(mapping==INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(hEeprom);
|
|
dev9.eeprom=(u16*)eeprom;
|
|
}
|
|
else
|
|
{
|
|
dev9.eeprom = (u16*)MapViewOfFile(mapping,FILE_MAP_WRITE,0,0,0);
|
|
if(dev9.eeprom==NULL)
|
|
{
|
|
CloseHandle(mapping);
|
|
CloseHandle(hEeprom);
|
|
dev9.eeprom=(u16*)eeprom;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
int rxbi;
|
|
|
|
for(rxbi=0;rxbi<(SMAP_BD_SIZE/8);rxbi++)
|
|
{
|
|
smap_bd_t *pbd = (smap_bd_t *)&dev9.dev9R[SMAP_BD_RX_BASE & 0xffff];
|
|
pbd = &pbd[rxbi];
|
|
|
|
pbd->ctrl_stat = SMAP_BD_RX_EMPTY;
|
|
pbd->length = 0;
|
|
}
|
|
}
|
|
|
|
DEV9_LOG("DEV9init ok\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CALLBACK DEV9shutdown() {
|
|
DEV9_LOG("DEV9shutdown\n");
|
|
#ifdef DEV9_LOG_ENABLE
|
|
DEV9Log.Close();
|
|
#endif
|
|
}
|
|
|
|
s32 CALLBACK DEV9open(void *pDsp)
|
|
{
|
|
DEV9_LOG("DEV9open\n");
|
|
LoadConf();
|
|
DEV9_LOG("open r+: %s\n", config.Hdd);
|
|
config.HddSize = 8*1024;
|
|
|
|
iopPC = (u32*)pDsp;
|
|
|
|
#ifdef ENABLE_ATA
|
|
ata_init();
|
|
#endif
|
|
return _DEV9open();
|
|
}
|
|
|
|
void CALLBACK DEV9close()
|
|
{
|
|
DEV9_LOG("DEV9close\n");
|
|
#ifdef ENABLE_ATA
|
|
ata_term();
|
|
#endif
|
|
_DEV9close();
|
|
}
|
|
|
|
int CALLBACK _DEV9irqHandler(void)
|
|
{
|
|
//dev9Ru16(SPD_R_INTR_STAT)|= dev9.irqcause;
|
|
DEV9_LOG("_DEV9irqHandler %x, %x\n", dev9.irqcause, dev9Ru16(SPD_R_INTR_MASK));
|
|
if (dev9.irqcause & dev9Ru16(SPD_R_INTR_MASK))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
DEV9handler CALLBACK DEV9irqHandler(void) {
|
|
return (DEV9handler)_DEV9irqHandler;
|
|
}
|
|
|
|
void _DEV9irq(int cause, int cycles)
|
|
{
|
|
DEV9_LOG("_DEV9irq %x, %x\n", cause, dev9Ru16(SPD_R_INTR_MASK));
|
|
|
|
dev9.irqcause|= cause;
|
|
|
|
if(cycles<1)
|
|
DEV9irq(1);
|
|
else
|
|
DEV9irq(cycles);
|
|
}
|
|
|
|
|
|
u8 CALLBACK DEV9read8(u32 addr) {
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return 0;
|
|
|
|
u8 hard;
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
return ata_read<1>(addr);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
return smap_read8(addr);
|
|
}
|
|
|
|
switch (addr)
|
|
{
|
|
case SPD_R_PIO_DATA:
|
|
|
|
/*if(dev9.eeprom_dir!=1)
|
|
{
|
|
hard=0;
|
|
break;
|
|
}*/
|
|
|
|
if(dev9.eeprom_state==EEPROM_TDATA)
|
|
{
|
|
if(dev9.eeprom_command==2) //read
|
|
{
|
|
if(dev9.eeprom_bit==0xFF)
|
|
hard=0;
|
|
else
|
|
hard=((dev9.eeprom[dev9.eeprom_address]<<dev9.eeprom_bit)&0x8000)>>11;
|
|
dev9.eeprom_bit++;
|
|
if(dev9.eeprom_bit==16)
|
|
{
|
|
dev9.eeprom_address++;
|
|
dev9.eeprom_bit=0;
|
|
}
|
|
}
|
|
else hard=0;
|
|
}
|
|
else hard=0;
|
|
return hard;
|
|
|
|
case DEV9_R_REV:
|
|
hard = 0x32; // expansion bay
|
|
break;
|
|
|
|
default:
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
return (u8)FLASHread32(addr, 1);
|
|
}
|
|
|
|
hard = dev9Ru8(addr);
|
|
DEV9_LOG("*Unknown 8bit read at address %lx value %x\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
DEV9_LOG("*Known 8bit read at address %lx value %x\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
u16 CALLBACK DEV9read16(u32 addr)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return 0;
|
|
|
|
u16 hard;
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
return ata_read<2>(addr);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
return smap_read16(addr);
|
|
}
|
|
|
|
switch (addr)
|
|
{
|
|
case SPD_R_INTR_STAT:
|
|
return dev9.irqcause;
|
|
|
|
case DEV9_R_REV:
|
|
hard = 0x0030; // expansion bay
|
|
break;
|
|
|
|
case SPD_R_REV_1:
|
|
hard = 0x0011;
|
|
DEV9_LOG("STD_R_REV_1 16bit read %x\n", hard);
|
|
return hard;
|
|
|
|
case SPD_R_REV_3:
|
|
// bit 0: smap
|
|
// bit 1: hdd
|
|
// bit 5: flash
|
|
hard = 0;
|
|
/*if (config.hddEnable) {
|
|
hard|= 0x2;
|
|
}*/
|
|
if (config.ethEnable) {
|
|
hard|= 0x1;
|
|
}
|
|
hard|= 0x20;//flash
|
|
break;
|
|
|
|
case SPD_R_0e:
|
|
hard = 0x0002;
|
|
break;
|
|
default:
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
return (u16)FLASHread32(addr, 2);
|
|
}
|
|
|
|
hard = dev9Ru16(addr);
|
|
DEV9_LOG("*Unknown 16bit read at address %lx value %x\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
DEV9_LOG("*Known 16bit read at address %lx value %x\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
u32 CALLBACK DEV9read32(u32 addr)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return 0;
|
|
|
|
u32 hard;
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
return ata_read<4>(addr);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
return smap_read32(addr);
|
|
}
|
|
switch (addr) {
|
|
|
|
default:
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
return (u32)FLASHread32(addr, 4);
|
|
}
|
|
|
|
hard = dev9Ru32(addr);
|
|
DEV9_LOG("*Unknown 32bit read at address %lx value %x\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
DEV9_LOG("*Known 32bit read at address %lx: %lx\n", addr, hard);
|
|
return hard;
|
|
}
|
|
|
|
void CALLBACK DEV9write8(u32 addr, u8 value)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return;
|
|
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
ata_write<1>(addr,value);
|
|
#endif
|
|
return;
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
smap_write8(addr,value);
|
|
return;
|
|
}
|
|
switch (addr) {
|
|
case 0x10000020:
|
|
dev9.irqcause = 0xff;
|
|
break;
|
|
case SPD_R_INTR_STAT:
|
|
emu_printf("SPD_R_INTR_STAT , WTFH ?\n");
|
|
dev9.irqcause=value;
|
|
return;
|
|
case SPD_R_INTR_MASK:
|
|
emu_printf("SPD_R_INTR_MASK8 , WTFH ?\n");
|
|
break;
|
|
|
|
case SPD_R_PIO_DIR:
|
|
//DEV9_LOG("SPD_R_PIO_DIR 8bit write %x\n", value);
|
|
|
|
if((value&0xc0)!=0xc0)
|
|
return;
|
|
|
|
if((value&0x30)==0x20)
|
|
{
|
|
dev9.eeprom_state=0;
|
|
}
|
|
dev9.eeprom_dir=(value>>4)&3;
|
|
|
|
return;
|
|
|
|
case SPD_R_PIO_DATA:
|
|
//DEV9_LOG("SPD_R_PIO_DATA 8bit write %x\n", value);
|
|
|
|
if((value&0xc0)!=0xc0)
|
|
return;
|
|
|
|
switch(dev9.eeprom_state)
|
|
{
|
|
case EEPROM_READY:
|
|
dev9.eeprom_command=0;
|
|
dev9.eeprom_state++;
|
|
break;
|
|
case EEPROM_OPCD0:
|
|
dev9.eeprom_command = (value>>4)&2;
|
|
dev9.eeprom_state++;
|
|
dev9.eeprom_bit=0xFF;
|
|
break;
|
|
case EEPROM_OPCD1:
|
|
dev9.eeprom_command |= (value>>5)&1;
|
|
dev9.eeprom_state++;
|
|
break;
|
|
case EEPROM_ADDR0:
|
|
case EEPROM_ADDR1:
|
|
case EEPROM_ADDR2:
|
|
case EEPROM_ADDR3:
|
|
case EEPROM_ADDR4:
|
|
case EEPROM_ADDR5:
|
|
dev9.eeprom_address =
|
|
(dev9.eeprom_address&(63^(1<<(dev9.eeprom_state-EEPROM_ADDR0))))|
|
|
((value>>(dev9.eeprom_state-EEPROM_ADDR0))&(0x20>>(dev9.eeprom_state-EEPROM_ADDR0)));
|
|
dev9.eeprom_state++;
|
|
break;
|
|
case EEPROM_TDATA:
|
|
{
|
|
if(dev9.eeprom_command==1) //write
|
|
{
|
|
dev9.eeprom[dev9.eeprom_address] =
|
|
(dev9.eeprom[dev9.eeprom_address]&(63^(1<<dev9.eeprom_bit)))|
|
|
((value>>dev9.eeprom_bit)&(0x8000>>dev9.eeprom_bit));
|
|
dev9.eeprom_bit++;
|
|
if(dev9.eeprom_bit==16)
|
|
{
|
|
dev9.eeprom_address++;
|
|
dev9.eeprom_bit=0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return;
|
|
|
|
default:
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
FLASHwrite32(addr, (u32)value, 1);
|
|
return;
|
|
}
|
|
|
|
dev9Ru8(addr) = value;
|
|
DEV9_LOG("*Unknown 8bit write at address %lx value %x\n", addr, value);
|
|
return;
|
|
}
|
|
dev9Ru8(addr) = value;
|
|
DEV9_LOG("*Known 8bit write at address %lx value %x\n", addr, value);
|
|
}
|
|
|
|
void CALLBACK DEV9write16(u32 addr, u16 value)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return;
|
|
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
ata_write<2>(addr,value);
|
|
#endif
|
|
return;
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
smap_write16(addr,value);
|
|
return;
|
|
}
|
|
switch (addr)
|
|
{
|
|
case SPD_R_INTR_MASK:
|
|
if ((dev9Ru16(SPD_R_INTR_MASK)!=value) && ((dev9Ru16(SPD_R_INTR_MASK)|value) & dev9.irqcause))
|
|
{
|
|
DEV9_LOG("SPD_R_INTR_MASK16=0x%X , checking for masked/unmasked interrupts\n",value);
|
|
DEV9irq(1);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
FLASHwrite32(addr, (u32)value, 2);
|
|
return;
|
|
}
|
|
|
|
dev9Ru16(addr) = value;
|
|
DEV9_LOG("*Unknown 16bit write at address %lx value %x\n", addr, value);
|
|
return;
|
|
}
|
|
dev9Ru16(addr) = value;
|
|
DEV9_LOG("*Known 16bit write at address %lx value %x\n", addr, value);
|
|
}
|
|
|
|
void CALLBACK DEV9write32(u32 addr, u32 value)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return;
|
|
|
|
if (addr>=ATA_DEV9_HDD_BASE && addr<ATA_DEV9_HDD_END)
|
|
{
|
|
#ifdef ENABLE_ATA
|
|
ata_write<4>(addr,value);
|
|
#endif
|
|
return;
|
|
}
|
|
if (addr>=SMAP_REGBASE && addr<FLASH_REGBASE)
|
|
{
|
|
//smap
|
|
smap_write32(addr,value);
|
|
return;
|
|
}
|
|
switch (addr)
|
|
{
|
|
case SPD_R_INTR_MASK:
|
|
emu_printf("SPD_R_INTR_MASK , WTFH ?\n");
|
|
break;
|
|
default:
|
|
if ((addr >= FLASH_REGBASE) && (addr < (FLASH_REGBASE + FLASH_REGSIZE))) {
|
|
FLASHwrite32(addr, (u32)value, 4);
|
|
return;
|
|
}
|
|
|
|
dev9Ru32(addr) = value;
|
|
DEV9_LOG("*Unknown 32bit write at address %lx write %x\n", addr, value);
|
|
return;
|
|
}
|
|
dev9Ru32(addr) = value;
|
|
DEV9_LOG("*Known 32bit write at address %lx value %lx\n", addr, value);
|
|
}
|
|
|
|
void CALLBACK DEV9readDMA8Mem(u32 *pMem, int size)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return;
|
|
|
|
DEV9_LOG("*DEV9readDMA8Mem: size %x\n", size);
|
|
emu_printf("rDMA\n");
|
|
|
|
smap_readDMA8Mem(pMem,size);
|
|
#ifdef ENABLE_ATA
|
|
ata_readDMA8Mem(pMem,size);
|
|
#endif
|
|
}
|
|
|
|
void CALLBACK DEV9writeDMA8Mem(u32* pMem, int size)
|
|
{
|
|
if (!config.ethEnable & !config.hddEnable)
|
|
return;
|
|
|
|
DEV9_LOG("*DEV9writeDMA8Mem: size %x\n", size);
|
|
emu_printf("wDMA\n");
|
|
|
|
smap_writeDMA8Mem(pMem,size);
|
|
#ifdef ENABLE_ATA
|
|
ata_writeDMA8Mem(pMem,size);
|
|
#endif
|
|
}
|
|
|
|
|
|
//plugin interface
|
|
void CALLBACK DEV9irqCallback(void (*callback)(int cycles)) {
|
|
DEV9irq = callback;
|
|
}
|
|
|
|
void CALLBACK DEV9async(u32 cycles)
|
|
{
|
|
smap_async(cycles);
|
|
}
|
|
|
|
// extended funcs
|
|
|
|
s32 CALLBACK DEV9test() {
|
|
return 0;
|
|
}
|
|
|
|
void CALLBACK DEV9setSettingsDir(const char* dir)
|
|
{
|
|
// Grab the ini directory.
|
|
// TODO: Use
|
|
s_strIniPath = (dir == NULL) ? "inis" : dir;
|
|
}
|
|
|
|
void CALLBACK DEV9setLogDir(const char* dir)
|
|
{
|
|
// Get the path to the log directory.
|
|
s_strLogPath = (dir == NULL) ? "logs" : dir;
|
|
|
|
// Reload the log file after updated the path
|
|
// Currently dosn't change winPcap log directories post DEV9open()
|
|
DEV9Log.Close();
|
|
LogInit();
|
|
}
|
|
|
|
int emu_printf(const char *fmt, ...)
|
|
{
|
|
va_list vl;
|
|
int ret;
|
|
va_start(vl,fmt);
|
|
ret = vfprintf(stderr,fmt,vl);
|
|
va_end(vl);
|
|
fflush(stderr);
|
|
return ret;
|
|
}
|