Initial import of new Supermodel code.

This commit is contained in:
Bart Trzynadlowski 2011-04-24 01:14:00 +00:00
parent 1848a427ec
commit 77cb1b8a76
92 changed files with 80050 additions and 0 deletions

258
BlockFile.cpp Normal file
View file

@ -0,0 +1,258 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* BlockFile.cpp
*
* Block format container file management. Implementation of the CBlockFile
* class.
*/
#include <stdio.h>
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Output Functions
******************************************************************************/
void CBlockFile::ReadString(char *str, unsigned strLen, unsigned maxLen)
{
if (NULL == fp)
return;
if (strLen>maxLen)
strLen = maxLen;
fread(str, sizeof(char), strLen, fp);
str[strLen] = '\0';
}
unsigned CBlockFile::ReadBytes(void *data, unsigned numBytes)
{
if (NULL == fp)
return 0;
return fread(data, sizeof(UINT8), numBytes, fp);
}
unsigned CBlockFile::ReadDWord(UINT32 *data)
{
if (NULL == fp)
return 0;
fread(data, sizeof(UINT32), 1, fp);
return 4;
}
void CBlockFile::UpdateBlockSize(void)
{
long int curPos;
unsigned newBlockSize;
if (NULL == fp)
return;
curPos = ftell(fp); // save current file position
fseek(fp, blockStartPos, SEEK_SET);
newBlockSize = curPos - blockStartPos;
fwrite(&newBlockSize, sizeof(UINT32), 1, fp);
fseek(fp, curPos, SEEK_SET); // go back
}
void CBlockFile::WriteByte(UINT8 data)
{
if (NULL == fp)
return;
fwrite(&data, sizeof(UINT8), 1, fp);
UpdateBlockSize();
}
void CBlockFile::WriteDWord(UINT32 data)
{
if (NULL == fp)
return;
fwrite(&data, sizeof(UINT32), 1, fp);
UpdateBlockSize();
}
void CBlockFile::WriteBytes(const void *data, unsigned numBytes)
{
if (NULL == fp)
return;
fwrite(data, sizeof(UINT8), numBytes, fp);
UpdateBlockSize();
}
void CBlockFile::WriteBlockHeader(const char *name, const char *comment)
{
unsigned nameLen, commentLen;
const char nullComment[1] = {'\0'};
if (NULL == fp)
return;
if (comment == NULL)
comment = nullComment;
nameLen = strlen(name);
commentLen = strlen(comment);
if (nameLen > 1024)
nameLen = 1024;
if (commentLen > 1024)
commentLen = 1024;
// Record current block starting position
blockStartPos = ftell(fp);
// Write the total block length field
WriteDWord(0); // will be automatically updated as we write the file
// Write name and comment lengths
WriteDWord(nameLen+1);
WriteDWord(commentLen+1);
WriteBytes(name, nameLen);
WriteByte(0);
WriteBytes(comment, commentLen);
WriteByte(0);
// Record the start of the current data section
dataStartPos = ftell(fp);
}
/******************************************************************************
Block Format Container File Implementation
Files are just a consecutive array of blocks that must be searched.
Block Format
------------
blockLength (UINT32) Total length of block in bytes.
nameLength (UINT32) Length of name field including terminating 0 (up to
1025).
commentLength (UINT32) Same as above, but for comment string.
name ... Name string (null-terminated, up to 1025 bytes).
comment ... Comment string (same as above).
data ... Raw data (blockLength - total header size).
******************************************************************************/
unsigned CBlockFile::Read(void *data, unsigned numBytes)
{
if (mode == 'r')
return ReadBytes(data, numBytes);
return 0;
}
void CBlockFile::Write(const void *data, unsigned numBytes)
{
if (mode == 'w')
WriteBytes(data, numBytes);
}
void CBlockFile::NewBlock(const char *name, const char *comment)
{
if (mode == 'w')
WriteBlockHeader(name, comment);
}
BOOL CBlockFile::FindBlock(const char *name)
{
long int curPos = 0;
unsigned blockLen, nameLen, commentLen;
if (mode != 'r')
return FAIL;
fseek(fp, 0, SEEK_SET);
while (curPos < fileSize)
{
blockStartPos = curPos;
// Read header
curPos += ReadDWord(&blockLen);
curPos += ReadDWord(&nameLen);
curPos += ReadDWord(&commentLen);
ReadString(strBuf,nameLen,1025);
// Is this the block we want?
if (!strcmp(strBuf,name))
{
fseek(fp, blockStartPos+12+nameLen+commentLen, SEEK_SET); // move to beginning of data
dataStartPos = ftell(fp);
return OKAY;
}
// Move to next block
fseek(fp, blockStartPos+blockLen, SEEK_SET);
curPos = blockStartPos+blockLen;
if (blockLen == 0) // this would never advance
break;
}
return FAIL;
}
BOOL CBlockFile::Create(const char *file, const char *headerName, const char *comment)
{
fp = fopen(file, "wb");
if (NULL == fp)
return FAIL;
mode = 'w';
WriteBlockHeader(headerName, comment);
return OKAY;
}
BOOL CBlockFile::Load(const char *file)
{
fp = fopen(file, "rb");
if (NULL == fp)
return FAIL;
mode = 'r';
// TODO: is this a valid block file?
// Get the file size
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET);
return OKAY;
}
void CBlockFile::Close(void)
{
if (fp != NULL)
fclose(fp);
fp = NULL;
mode = 0;
}
CBlockFile::CBlockFile(void)
{
fp = NULL;
mode = 0; // neither reading nor writing (do nothing)
}
CBlockFile::~CBlockFile(void)
{
if (fp != NULL) // in case user forgot
fclose(fp);
fp = NULL;
mode = 0;
}

170
BlockFile.h Normal file
View file

@ -0,0 +1,170 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* BlockFile.h
*
* Header file for block format container file management.
*/
#ifndef INCLUDED_BLOCKFILE_H
#define INCLUDED_BLOCKFILE_H
/*
* CBlockFile:
*
* Block format container file. The file format is a series of consecutive,
* variable-length blocks referenced by unique name strings.
*
* All strings (comments and names) will be truncated to 1024 bytes, not
* including the null terminator.
*
* Members do not generate any output messages.
*/
class CBlockFile
{
public:
/*
* Read(data, numBytes):
*
* Reads data from the current file position.
*
* Parameters:
* data Buffer to read to.
* numBytes Number of bytes to read.
*
* Returns:
* Number of bytes read. If not the same as numBytes, an error
* occurred.
*/
unsigned Read(void *data, unsigned numBytes);
/*
* FindBlock(name):
*
* Scans the file for a block with the given name string. When it is found,
* the file pointer is set to the beginning of the data region.
*
* Parameters:
* name Name of block to locate.
*
* Returns:
* OKAY if found, FAIL if unable to locate.
*/
BOOL FindBlock(const char *name);
/*
* Write(data, numBytes):
*
* Outputs data at the current file pointer position. Updates the block
* header appropriately.
*
* Parameters:
* data Data to write.
* numBytes Number of bytes to write.
*/
void Write(const void *data, unsigned numBytes);
/*
* NewBlock(name, comment):
*
* Begins a new block. Writes the block header and sets the file pointer to
* the beginning of its data area.
*
* Parameters:
* name Block name. Must be unique and not NULL.
* comment Comment string to embed in the block header.
*/
void NewBlock(const char *title, const char *comment);
/*
* Create(file, headerName, comment):
*
* Opens a block file for writing and creates the header block. This
* function must be called before attempting to write data. Otherwise, all
* write commands will be silently ignored. Read commands will be ignored
* and will always return 0's.
*
* Parameters:
* file File path.
* headerName Block name for header. Must be unique and not NULL.
* comment Comment string that will be embedded into file header.
*
* Returns:
* OKAY if successfully opened, otherwise FAIL.
*/
BOOL Create(const char *file, const char *headerName, const char *comment);
/*
* Load(file):
*
* Open a block file file for reading.
*
* Parameters:
* file File path.
*
* Returns:
* OKAY if successfully opened and confirmed to be a valid Supermodel
* block file, otherwise FAIL. If the file could not be opened, all
* subsequent operations will be silently ignored (reads will return
* 0's). Write commands will be ignored.
*/
BOOL Load(const char *file);
/*
* Close(void):
*
* Closes the file.
*/
void Close(void);
/*
* CBlockFile(void):
* ~CBlockFile(void):
*
* Constructor and destructor.
*/
CBlockFile(void);
~CBlockFile(void);
private:
// Helper functions
void ReadString(char *str, unsigned strLen, unsigned maxLen);
unsigned ReadBytes(void *data, unsigned numBytes);
unsigned ReadDWord(UINT32 *data);
void UpdateBlockSize(void);
void WriteByte(UINT8 data);
void WriteDWord(UINT32 data);
void WriteBytes(const void *data, unsigned numBytes);
void WriteBlockHeader(const char *name, const char *comment);
// File state data
char strBuf[1026]; // buffers up to a 1024-character string, its terminator, and an extra terminator (just in case)
FILE *fp;
int mode; // 'r' for read, 'w' for write
long int fileSize; // size of file in bytes
long int blockStartPos; // points to beginning of current block (or file) header
long int dataStartPos; // points to beginning of current block's data section
};
#endif // INCLUDED_BLOCKFILE_H

8938
CPU/68K/Make68K.c Normal file

File diff suppressed because it is too large Load diff

1299
CPU/68K/README.TXT Normal file

File diff suppressed because it is too large Load diff

158
CPU/68K/Turbo68K.h Normal file
View file

@ -0,0 +1,158 @@
/*
* Turbo68K: Motorola 680X0 emulator
* Copyright 2000-2002 Bart Trzynadlowski, see "README.TXT" for terms of use
*/
#ifndef _TURBO68K_H_
#define _TURBO68K_H_
#ifdef __cplusplus
extern "C" {
#endif
/*****************************************************************************
* Generic Macros */
#define TURBO68K_OKAY 0 /* no error */
#define TURBO68K_NULL 0 /* null */
#define TURBO68K_SUPERVISOR 0 /* affects supervisor space */
#define TURBO68K_USER 1 /* function affects user space */
#define TURBO68K_UNINITIALIZED 15 /* uninitialized interrupt */
#define TURBO68K_SPURIOUS 24 /* spurious interrupt */
#define TURBO68K_AUTOVECTOR 256 /* autovectored interrupt */
/*****************************************************************************
* Error Codes */
#define TURBO68K_ERROR_FETCH 1 /* fetch error (PC bounds) */
#define TURBO68K_ERROR_INVINST 2 /* invalid instruction */
#define TURBO68K_ERROR_INTLEVEL 3 /* invalid interrupt level */
#define TURBO68K_ERROR_INTVECTOR 4 /* invalid interrupt vector */
#define TURBO68K_ERROR_INTPENDING 5 /* level already pending */
#define TURBO68K_ERROR_STACKFRAME 6 /* unhandled stack frame */
/*****************************************************************************
* Data Types */
typedef unsigned int TURBO68K_UINT32; /* unsigned 32-bit */
typedef signed int TURBO68K_INT32; /* signed 32-bit */
typedef unsigned short TURBO68K_UINT16; /* unsigned 16-bit */
typedef signed short TURBO68K_INT16; /* signed 16-bit */
typedef unsigned char TURBO68K_UINT8; /* unsigned 8-bit */
typedef signed char TURBO68K_INT8; /* signed 8-bit */
/*****************************************************************************
* Data Structures */
struct TURBO68K_FETCHREGION
{
TURBO68K_UINT32 base;
TURBO68K_UINT32 limit;
TURBO68K_UINT32 ptr;
};
struct TURBO68K_DATAREGION
{
TURBO68K_UINT32 base;
TURBO68K_UINT32 limit;
TURBO68K_UINT32 ptr;
void *handler;
};
struct TURBO68K_CONTEXT_68000
{
void *fetch, *pcfetch;
void *read_byte, *read_word, *read_long;
void *write_byte, *write_word, *write_long;
void *super_fetch, *super_pcfetch;
void *super_read_byte, *super_read_word, *super_read_long;
void *super_write_byte, *super_write_word, *super_write_long;
void *user_fetch, *user_pcfetch;
void *user_read_byte, *user_read_word, *user_read_long;
void *user_write_byte, *user_write_word, *user_write_long;
TURBO68K_UINT32 intr[8], cycles, remaining;
TURBO68K_UINT32 d[8], a[8], sp, sr, pc, status;
void *InterruptAcknowledge;
void *Reset;
};
struct TURBO68K_CONTEXT_68010
{
void *fetch, *pcfetch;
void *read_byte, *read_word, *read_long;
void *write_byte, *write_word, *write_long;
void *super_fetch, *super_pcfetch;
void *super_read_byte, *super_read_word, *super_read_long;
void *super_write_byte, *super_write_word, *super_write_long;
void *user_fetch, *user_pcfetch;
void *user_read_byte, *user_read_word, *user_read_long;
void *user_write_byte, *user_write_word, *user_write_long;
TURBO68K_UINT32 intr[8], cycles, remaining;
TURBO68K_UINT32 d[8], a[8], sp, sr, pc, fc, vbr, status;
void *InterruptAcknowledge;
void *Reset, *Bkpt;
};
/*****************************************************************************
* Functions and External Data */
#define TURBO68K_ID(ID) \
\
extern struct TURBO68K_CONTEXT_68000 ID##turbo68kcontext_68000; \
extern struct TURBO68K_CONTEXT_68010 ID##turbo68kcontext_68010; \
\
TURBO68K_INT32 ID##Turbo68KInit(); \
TURBO68K_INT32 ID##Turbo68KReset(); \
TURBO68K_INT32 ID##Turbo68KRun(TURBO68K_INT32); \
TURBO68K_INT32 ID##Turbo68KProcessInterrupts(); \
TURBO68K_INT32 ID##Turbo68KInterrupt(TURBO68K_INT32, TURBO68K_UINT32); \
TURBO68K_INT32 ID##Turbo68KCancelInterrupt(TURBO68K_INT32); \
\
TURBO68K_UINT32 ID##Turbo68KReadPC(); \
\
void ID##Turbo68KSetFetch(void *, TURBO68K_INT32); \
void ID##Turbo68KSetPCFetch(void *, TURBO68K_INT32); \
void ID##Turbo68KSetReadByte(void *, TURBO68K_INT32); \
void ID##Turbo68KSetReadWord(void *, TURBO68K_INT32); \
void ID##Turbo68KSetReadLong(void *, TURBO68K_INT32); \
void ID##Turbo68KSetWriteByte(void *, TURBO68K_INT32); \
void ID##Turbo68KSetWriteWord(void *, TURBO68K_INT32); \
void ID##Turbo68KSetWriteLong(void *, TURBO68K_INT32); \
void *ID##Turbo68KGetFetch(TURBO68K_INT32); \
void *ID##Turbo68KGetPCFetch(TURBO68K_INT32); \
void *ID##Turbo68KGetReadByte(TURBO68K_INT32); \
void *ID##Turbo68KGetReadWord(TURBO68K_INT32); \
void *ID##Turbo68KGetReadLong(TURBO68K_INT32); \
void *ID##Turbo68KGetWriteByte(TURBO68K_INT32); \
void *ID##Turbo68KGetWriteWord(TURBO68K_INT32); \
void *ID##Turbo68KGetWriteLong(TURBO68K_INT32); \
\
TURBO68K_UINT8 *ID##Turbo68KFetchPtr(TURBO68K_UINT32); \
TURBO68K_UINT8 ID##Turbo68KReadByte(TURBO68K_UINT32); \
TURBO68K_UINT16 ID##Turbo68KReadWord(TURBO68K_UINT32); \
TURBO68K_UINT32 ID##Turbo68KReadLong(TURBO68K_UINT32); \
void ID##Turbo68KWriteByte(TURBO68K_UINT32, TURBO68K_UINT8); \
void ID##Turbo68KWriteWord(TURBO68K_UINT32, TURBO68K_UINT16);\
void ID##Turbo68KWriteLong(TURBO68K_UINT32, TURBO68K_UINT32);\
\
void ID##Turbo68KSetContext(void *); \
void ID##Turbo68KGetContext(void *); \
TURBO68K_UINT32 ID##Turbo68KGetContextSize(); \
\
void ID##Turbo68KClearCycles(); \
void ID##Turbo68KFreeTimeSlice(); \
TURBO68K_INT32 ID##Turbo68KGetElapsedCycles(); \
TURBO68K_ID( ); /* default identifiers begin with nothing */
#ifdef __cplusplus
}
#endif
#endif /* _TURBO68K_H_ */

1260
CPU/PowerPC/PPCDisasm.cpp Normal file

File diff suppressed because it is too large Load diff

58
CPU/PowerPC/PPCDisasm.h Normal file
View file

@ -0,0 +1,58 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* PPCDisasm.h
*
* Header file for PowerPC disassembler.
*/
#ifndef INCLUDED_PPCDISASM_H
#define INCLUDED_PPCDISASM_H
/*
* DisassemblePowerPC(op, vpc, mnem, oprs, simplify):
*
* Disassembles one PowerPC 603e instruction.
*
* A non-zero return code indicates that the instruction could not be
* recognized or that the operands to an instruction were invalid. To
* determine which case occured, check if mnem[0] == '\0'. If it does not,
* then the latter case happened.
*
* Arguments:
* op Instruction word to disassemble.
* vpc Current instruction address.
* mnem Buffer to write instruction mnemonic to. If no
* instruction was decoded, mnem[0] and oprs[0] will be set
* to '\0'.
* oprs Buffer to write any operands to.
* simplify If non-zero, simplified forms of instructions will be
* printed in certain cases.
*
* Returns:
* OKAY if successful, FAIL if the instruction was unrecognized or had an
* invalid form (see note above in function description.)
*/
extern BOOL DisassemblePowerPC(UINT32 op, UINT32 vpc, char *mnem, char *oprs,
BOOL simplify);
#endif // INCLUDED_PPCDISASM_H

1046
CPU/PowerPC/ppc.cpp Normal file

File diff suppressed because it is too large Load diff

366
CPU/PowerPC/ppc.h Normal file
View file

@ -0,0 +1,366 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* ppc.h
*
* Header file for PowerPC emulator.
*/
#ifndef INCLUDED_PPC_H
#define INCLUDED_PPC_H
/******************************************************************************
Definitions
******************************************************************************/
#define SPR_XER 1 /* Fixed Point Exception Register Read / Write */
#define SPR_LR 8 /* Link Register Read / Write */
#define SPR_CTR 9 /* Count Register Read / Write */
#define SPR_SRR0 26 /* Save/Restore Register 0 Read / Write */
#define SPR_SRR1 27 /* Save/Restore Register 1 Read / Write */
#define SPR_SPRG0 272 /* SPR General 0 Read / Write */
#define SPR_SPRG1 273 /* SPR General 1 Read / Write */
#define SPR_SPRG2 274 /* SPR General 2 Read / Write */
#define SPR_SPRG3 275 /* SPR General 3 Read / Write */
#define SPR_PVR 287 /* Processor Version Number Read Only */
#define SPR403_ICDBDR 0x3D3 /* 406GA Instruction Cache Debug Data Register Rad Only */
#define SPR403_ESR 0x3D4 /* 406GA Exception Syndrome Register Read / Write */
#define SPR403_DEAR 0x3D5 /* 406GA Data Exception Address Register Read Only */
#define SPR403_EVPR 0x3D6 /* 406GA Exception Vector Prefix Register Read / Write */
#define SPR403_CDBCR 0x3D7 /* 406GA Cache Debug Control Register Read/Write */
#define SPR403_TSR 0x3D8 /* 406GA Timer Status Register Read / Clear */
#define SPR403_TCR 0x3DA /* 406GA Timer Control Register Read / Write */
#define SPR403_PIT 0x3DB /* 406GA Programmable Interval Timer Read / Write */
#define SPR403_TBHI 988 /* 406GA Time Base High Read / Write */
#define SPR403_TBLO 989 /* 406GA Time Base Low Read / Write */
#define SPR403_SRR2 0x3DE /* 406GA Save/Restore Register 2 Read / Write */
#define SPR403_SRR3 0x3DF /* 406GA Save/Restore Register 3 Read / Write */
#define SPR403_DBSR 0x3F0 /* 406GA Debug Status Register Read / Clear */
#define SPR403_DBCR 0x3F2 /* 406GA Debug Control Register Read / Write */
#define SPR403_IAC1 0x3F4 /* 406GA Instruction Address Compare 1 Read / Write */
#define SPR403_IAC2 0x3F5 /* 406GA Instruction Address Compare 2 Read / Write */
#define SPR403_DAC1 0x3F6 /* 406GA Data Address Compare 1 Read / Write */
#define SPR403_DAC2 0x3F7 /* 406GA Data Address Compare 2 Read / Write */
#define SPR403_DCCR 0x3FA /* 406GA Data Cache Cacheability Register Read / Write */
#define SPR403_ICCR 0x3FB /* 406GA I Cache Cacheability Registe Read / Write */
#define SPR403_PBL1 0x3FC /* 406GA Protection Bound Lower 1 Read / Write */
#define SPR403_PBU1 0x3FD /* 406GA Protection Bound Upper 1 Read / Write */
#define SPR403_PBL2 0x3FE /* 406GA Protection Bound Lower 2 Read / Write */
#define SPR403_PBU2 0x3FF /* 406GA Protection Bound Upper 2 Read / Write */
#define SPR403_SGR 0x3b9 /* 403GCX Storage Guarded Register */
#define SPR403_DCWR 0x3ba /* 403GCX Data Cache Write Through */
#define SPR403_PID 0x3b1 /* 403GCX Process ID */
#define SPR403_TBHU 0x3cc /* 403GCX Time Base High User-mode */
#define SPR403_TBLU 0x3cd /* 403GCX Time Base Low User-mode */
#define SPR603E_DSISR 18 /* 603E */
#define SPR603E_DAR 19 /* 603E */
#define SPR603E_DEC 22 /* 603E */
#define SPR603E_SDR1 25 /* 603E */
#define SPR603E_TBL_R 268 /* 603E Time Base Low (Read-only) */
#define SPR603E_TBU_R 269 /* 603E Time Base High (Read-only) */
#define SPR603E_TBL_W 284 /* 603E Time Base Low (Write-only) */
#define SPR603E_TBU_W 285 /* 603E Time Base Hight (Write-only) */
#define SPR603E_EAR 282 /* 603E */
#define SPR603E_IBAT0U 528 /* 603E */
#define SPR603E_IBAT0L 529 /* 603E */
#define SPR603E_IBAT1U 530 /* 603E */
#define SPR603E_IBAT1L 531 /* 603E */
#define SPR603E_IBAT2U 532 /* 603E */
#define SPR603E_IBAT2L 533 /* 603E */
#define SPR603E_IBAT3U 534 /* 603E */
#define SPR603E_IBAT3L 535 /* 603E */
#define SPR603E_DBAT0U 536 /* 603E */
#define SPR603E_DBAT0L 537 /* 603E */
#define SPR603E_DBAT1U 538 /* 603E */
#define SPR603E_DBAT1L 539 /* 603E */
#define SPR603E_DBAT2U 540 /* 603E */
#define SPR603E_DBAT2L 541 /* 603E */
#define SPR603E_DBAT3U 542 /* 603E */
#define SPR603E_DBAT3L 543 /* 603E */
#define SPR603E_DABR 1013 /* 603E */
#define SPR603E_DMISS 0x3d0 /* 603E */
#define SPR603E_DCMP 0x3d1 /* 603E */
#define SPR603E_HASH1 0x3d2 /* 603E */
#define SPR603E_HASH2 0x3d3 /* 603E */
#define SPR603E_IMISS 0x3d4 /* 603E */
#define SPR603E_ICMP 0x3d5 /* 603E */
#define SPR603E_RPA 0x3d6 /* 603E */
#define SPR603E_HID0 1008 /* 603E */
#define SPR603E_HID1 1009 /* 603E */
#define SPR603E_IABR 1010 /* 603E */
#define SPR603E_HID2 1011 /* 603E */
#define SPR602_TCR 984 /* 602 */
#define SPR602_IBR 986 /* 602 */
#define SPR602_SP 1021 /* 602 */
#define SPR602_LT 1022 /* 602 */
#define DCR_BEAR 0x90 /* bus error address */
#define DCR_BESR 0x91 /* bus error syndrome */
#define DCR_BR0 0x80 /* bank */
#define DCR_BR1 0x81 /* bank */
#define DCR_BR2 0x82 /* bank */
#define DCR_BR3 0x83 /* bank */
#define DCR_BR4 0x84 /* bank */
#define DCR_BR5 0x85 /* bank */
#define DCR_BR6 0x86 /* bank */
#define DCR_BR7 0x87 /* bank */
#define DCR_DMACC0 0xc4 /* dma chained count */
#define DCR_DMACC1 0xcc /* dma chained count */
#define DCR_DMACC2 0xd4 /* dma chained count */
#define DCR_DMACC3 0xdc /* dma chained count */
#define DCR_DMACR0 0xc0 /* dma channel control */
#define DCR_DMACR1 0xc8 /* dma channel control */
#define DCR_DMACR2 0xd0 /* dma channel control */
#define DCR_DMACR3 0xd8 /* dma channel control */
#define DCR_DMACT0 0xc1 /* dma destination address */
#define DCR_DMACT1 0xc9 /* dma destination address */
#define DCR_DMACT2 0xd1 /* dma destination address */
#define DCR_DMACT3 0xd9 /* dma destination address */
#define DCR_DMADA0 0xc2 /* dma destination address */
#define DCR_DMADA1 0xca /* dma destination address */
#define DCR_DMADA2 0xd2 /* dma source address */
#define DCR_DMADA3 0xda /* dma source address */
#define DCR_DMASA0 0xc3 /* dma source address */
#define DCR_DMASA1 0xcb /* dma source address */
#define DCR_DMASA2 0xd3 /* dma source address */
#define DCR_DMASA3 0xdb /* dma source address */
#define DCR_DMASR 0xe0 /* dma status */
#define DCR_EXIER 0x42 /* external interrupt enable */
#define DCR_EXISR 0x40 /* external interrupt status */
#define DCR_IOCR 0xa0 /* io configuration */
enum {
EXCEPTION_IRQ = 1,
EXCEPTION_DECREMENTER = 2,
EXCEPTION_TRAP = 3,
EXCEPTION_SYSTEM_CALL = 4,
EXCEPTION_SMI = 5,
EXCEPTION_DSI = 6,
EXCEPTION_ISI = 7,
EXCEPTION_PROGRAMMABLE_INTERVAL_TIMER = 20,
EXCEPTION_FIXED_INTERVAL_TIMER = 21,
EXCEPTION_WATCHDOG_TIMER = 22,
EXCEPTION_CRITICAL_INTERRUPT = 23,
};
enum {
PPC_INPUT_LINE_SMI = 10,
PPC_INPUT_LINE_TLBISYNC
};
enum {
PPC_PC=1,
PPC_MSR,
PPC_CR,
PPC_LR,
PPC_CTR,
PPC_XER,
PPC_DEC,
PPC_SRR0,
PPC_SRR1,
PPC_R0,
PPC_R1,
PPC_R2,
PPC_R3,
PPC_R4,
PPC_R5,
PPC_R6,
PPC_R7,
PPC_R8,
PPC_R9,
PPC_R10,
PPC_R11,
PPC_R12,
PPC_R13,
PPC_R14,
PPC_R15,
PPC_R16,
PPC_R17,
PPC_R18,
PPC_R19,
PPC_R20,
PPC_R21,
PPC_R22,
PPC_R23,
PPC_R24,
PPC_R25,
PPC_R26,
PPC_R27,
PPC_R28,
PPC_R29,
PPC_R30,
PPC_R31
};
typedef enum {
PPC_MODEL_403GA = 0x00200000,
PPC_MODEL_403GB = 0x00200100,
PPC_MODEL_403GC = 0x00200200,
PPC_MODEL_403GCX = 0x00201400,
PPC_MODEL_405GP = 0x40110000,
PPC_MODEL_601 = 0x00010000,
PPC_MODEL_603 = 0x00030000, /* "Wart" */
PPC_MODEL_604 = 0x00040000, /* "Zephyr" */
PPC_MODEL_602 = 0x00050000, /* "Galahad" */
PPC_MODEL_603E = 0x00060103, /* "Stretch", version 1.3 */
PPC_MODEL_603EV = 0x00070000, /* "Valiant" */
PPC_MODEL_603R = 0x00071202, /* "Goldeneye", version 2.1 */
PPC_MODEL_740 = 0x00080301, /* "Arthur", version 3.1 */
PPC_MODEL_750 = PPC_MODEL_740,
PPC_MODEL_740P = 0x00080202, /* "Conan Doyle", version 1.2 */
PPC_MODEL_750P = PPC_MODEL_740P,
PPC_MODEL_755 = 0x00083203 /* "Goldfinger", version 2.3 */
} PPC_MODEL;
typedef enum {
BUS_FREQUENCY_16MHZ = 0,
BUS_FREQUENCY_20MHZ,
BUS_FREQUENCY_25MHZ,
BUS_FREQUENCY_33MHZ,
BUS_FREQUENCY_40MHZ,
BUS_FREQUENCY_50MHZ,
BUS_FREQUENCY_60MHZ,
BUS_FREQUENCY_66MHZ,
BUS_FREQUENCY_75MHZ,
} PPC_BUS_FREQUENCY;
// PLL Configuration based on the table in MPC603EUM page 7-31
static const int mpc603e_pll_config[12][9] =
{
// 16, 20, 25, 33, 40, 50, 60, 66, 75
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, -1, 0x0, -1 },
{ -1, -1, -1, -1, -1, 0xc, -1, 0xc, -1 },
{ 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, -1, -1, -1 },
{ -1, -1, -1, 0x6, 0x6, -1, -1, -1, -1 },
{ -1, -1, 0x8, 0x8, -1, -1, -1, -1, -1 },
{ -1, 0xe, 0xe, -1, -1, -1, -1, -1, -1 },
{ 0xa, 0xa, 0xa, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
};
// PLL Configuration based on the table in MPC603E7VEC page 29
static const int mpc603ev_pll_config[12][9] =
{
// 16, 20, 25, 33, 40, 50, 60, 66, 75
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
// 2:1
{ -1, -1, -1, -1, -1, -1, -1, 0x4, 0x4 },
// 2.5:1
{ -1, -1, -1, -1, -1, 0x6, 0x6, 0x6, 0x6 },
// 3:1
{ -1, -1, -1, -1, 0x8, 0x8, 0x8, 0x8, 0x8 },
// 3.5:1
{ -1, -1, -1, -1, 0xe, 0xe, 0xe, 0xe, -1 },
// 4:1
{ -1, -1, -1, 0xa, 0xa, 0xa, 0xa, -1, -1 },
// 4.5:1
{ -1, -1, -1, 0x7, 0x7, 0x7, -1, -1, -1 },
// 5:1
{ -1, -1, 0xb, 0xb, 0xb, -1, -1, -1, -1 },
// 5.5:1
{ -1, -1, 0x9, 0x9, 0x9, -1, -1, -1, -1 },
// 6:1
{ -1, -1, 0xd, 0xd, 0xd, -1, -1, -1, -1 }
};
// PLL Configuration based on the table in MPC603E7TEC page 23
static const int mpc603r_pll_config[12][9] =
{
// 16, 20, 25, 33, 40, 50, 60, 66, 75
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1, -1, -1, -1, -1 },
// 2:1
{ -1, -1, -1, -1, 0x5, 0x5, 0x5, 0x5, 0x5 },
// 2.5:1
{ -1, -1, -1, -1, -1, -1, 0x6, 0x6, 0x6 },
// 3:1
{ -1, -1, -1, -1, -1, 0x8, 0x8, 0x8, 0x8 },
// 3.5:1
{ -1, -1, -1, -1, -1, 0xe, 0xe, 0xe, 0xe },
// 4:1
{ -1, -1, -1, -1, 0xa, 0xa, 0xa, 0xa, 0xa },
// 4.5:1
{ -1, -1, -1, 0x7, 0x7, 0x7, 0x7, 0x7, -1 },
// 5:1
{ -1, -1, -1, 0xb, 0xb, 0xb, 0xb, -1, -1 },
// 5.5:1
{ -1, -1, -1, 0x9, 0x9, 0x9, -1, -1, -1 },
// 6:1
{ -1, -1, 0xd, 0xd, 0xd, 0xd, -1, -1, -1 },
};
/******************************************************************************
Configuration Data Structures
******************************************************************************/
typedef struct {
PPC_MODEL pvr;
int bus_frequency_multiplier;
PPC_BUS_FREQUENCY bus_frequency;
} PPC_CONFIG;
typedef struct
{
UINT32 start;
UINT32 end;
UINT32 * ptr;
} PPC_FETCH_REGION;
/******************************************************************************
Functions
******************************************************************************/
extern UINT32 ppc_get_pc(void);
extern void ppc_set_irq_line(int irqline);
extern int ppc_execute(int cycles);
extern void ppc_reset(void);
extern void ppc_shutdown(void);
extern void ppc_init(const PPC_CONFIG *config); // must be called second!
extern void ppc_set_fetch(PPC_FETCH_REGION * fetch);
// These have been added to support the new Supermodel
extern void ppc_attach_bus(class CBus *BusPtr); // must be called first!
extern void ppc_save_state(class CBlockFile *SaveState);
extern void ppc_load_state(class CBlockFile *SaveState);
extern UINT32 ppc_get_gpr(unsigned num);
extern UINT32 ppc_get_lr(void);
extern UINT32 ppc_read_spr(unsigned spr);
extern UINT32 ppc_read_sr(unsigned num);
#endif // INCLUDED_PPC_H

329
CPU/PowerPC/ppc603.c Normal file
View file

@ -0,0 +1,329 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* ppc603.c
*
* PowerPC 603e functions. Included from ppc.cpp; do not compile separately.
*/
void ppc603_exception(int exception)
{
switch( exception )
{
case EXCEPTION_IRQ: /* External Interrupt */
if( ppc_get_msr() & MSR_EE ) {
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = msr & 0xff73;
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0500;
else
ppc.npc = 0x00000000 | 0x0500;
ppc.interrupt_pending &= ~0x1;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_DECREMENTER: /* Decrementer overflow exception */
if( ppc_get_msr() & MSR_EE ) {
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = msr & 0xff73;
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0900;
else
ppc.npc = 0x00000000 | 0x0900;
ppc.interrupt_pending &= ~0x2;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_TRAP: /* Program exception / Trap */
{
UINT32 msr = ppc_get_msr();
SRR0 = ppc.pc;
SRR1 = (msr & 0xff73) | 0x20000; /* 0x20000 = TRAP bit */
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0700;
else
ppc.npc = 0x00000000 | 0x0700;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_SYSTEM_CALL: /* System call */
{
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = (msr & 0xff73);
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0c00;
else
ppc.npc = 0x00000000 | 0x0c00;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_SMI:
if( ppc_get_msr() & MSR_EE ) {
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = msr & 0xff73;
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x1400;
else
ppc.npc = 0x00000000 | 0x1400;
ppc.interrupt_pending &= ~0x4;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_DSI:
{
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = msr & 0xff73;
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0300;
else
ppc.npc = 0x00000000 | 0x0300;
ppc.interrupt_pending &= ~0x4;
ppc_change_pc(ppc.npc);
}
break;
case EXCEPTION_ISI:
{
UINT32 msr = ppc_get_msr();
SRR0 = ppc.npc;
SRR1 = msr & 0xff73;
msr &= ~(MSR_POW | MSR_EE | MSR_PR | MSR_FP | MSR_FE0 | MSR_SE | MSR_BE | MSR_FE1 | MSR_IR | MSR_DR | MSR_RI);
if( msr & MSR_ILE )
msr |= MSR_LE;
else
msr &= ~MSR_LE;
ppc_set_msr(msr);
if( msr & MSR_IP )
ppc.npc = 0xfff00000 | 0x0400;
else
ppc.npc = 0x00000000 | 0x0400;
ppc.interrupt_pending &= ~0x4;
ppc_change_pc(ppc.npc);
}
break;
default:
ErrorLog("PowerPC triggered an unknown exception. Emulation halted until reset.");
DebugLog("PowerPC triggered an unknown exception (%d).\n", exception);
ppc.fatalError = TRUE;
break;
}
}
static void ppc603_set_smi_line(int state)
{
if( state ) {
ppc.interrupt_pending |= 0x4;
}
}
static void ppc603_check_interrupts(void)
{
if (MSR & MSR_EE)
{
if (ppc.interrupt_pending != 0)
{
if (ppc.interrupt_pending & 0x1)
{
ppc603_exception(EXCEPTION_IRQ);
}
else if (ppc.interrupt_pending & 0x2)
{
ppc603_exception(EXCEPTION_DECREMENTER);
}
else if (ppc.interrupt_pending & 0x4)
{
ppc603_exception(EXCEPTION_SMI);
}
}
}
}
void ppc_reset(void)
{
ppc.fatalError = FALSE; // reset the fatal error flag
ppc.pc = ppc.npc = 0xfff00100;
ppc_set_msr(0x40);
ppc_change_pc(ppc.pc);
ppc.hid0 = 1;
ppc.interrupt_pending = 0;
}
int ppc_execute(int cycles)
{
UINT32 opcode;
ppc_icount = cycles;
ppc_tb_base_icount = cycles;
ppc_dec_base_icount = cycles + ppc.dec_frac;
// check if decrementer exception occurs during execution
if ((UINT32)(DEC - (cycles / (bus_freq_multiplier * 2))) > (UINT32)(DEC))
{
ppc_dec_trigger_cycle = ((cycles / (bus_freq_multiplier * 2)) - DEC) * 4;
}
else
{
ppc_dec_trigger_cycle = 0x7fffffff;
}
ppc_change_pc(ppc.npc);
/*{
char string1[200];
char string2[200];
opcode = BSWAP32(*ppc.op);
DisassemblePowerPC(opcode, ppc.npc, string1, string2, TRUE);
printf("%08X: %s %s\n", ppc.npc, string1, string2);
}*/
// printf("trigger cycle %d (%08X)\n", ppc_dec_trigger_cycle, ppc_dec_trigger_cycle);
// printf("tb = %08X %08X\n", (UINT32)(ppc.tb >> 32), (UINT32)(ppc.tb));
ppc603_check_interrupts();
while( ppc_icount > 0 && !ppc.fatalError)
{
ppc.pc = ppc.npc;
//if (ppc.pc == 0x279C)
// printf("R3=%08X\n", REG(3));
opcode = *ppc.op++; // Supermodel byte reverses each aligned word (converting them to little endian) so they can be fetched directly
//opcode = BSWAP32(*ppc.op++);
ppc.npc = ppc.pc + 4;
switch(opcode >> 26)
{
case 19: optable19[(opcode >> 1) & 0x3ff](opcode); break;
case 31: optable31[(opcode >> 1) & 0x3ff](opcode); break;
case 59: optable59[(opcode >> 1) & 0x3ff](opcode); break;
case 63: optable63[(opcode >> 1) & 0x3ff](opcode); break;
default: optable[opcode >> 26](opcode); break;
}
ppc_icount--;
if(ppc_icount == ppc_dec_trigger_cycle)
{
// printf("dec int at %d\n", ppc_icount);
ppc.interrupt_pending |= 0x2;
ppc603_check_interrupts();
}
//ppc603_check_interrupts();
}
// update timebase
// timebase is incremented once every four core clock cycles, so adjust the cycles accordingly
ppc.tb += ((ppc_tb_base_icount - ppc_icount) / 4);
// update decrementer
ppc.dec_frac = ((ppc_dec_base_icount - ppc_icount) % (bus_freq_multiplier * 2));
DEC -= ((ppc_dec_base_icount - ppc_icount) / (bus_freq_multiplier * 2));
/*
{
char string1[200];
char string2[200];
opcode = BSWAP32(*ppc.op);
DisassemblePowerPC(opcode, ppc.npc, string1, string2, TRUE);
printf("%08X: %s %s\n", ppc.npc, string1, string2);
}
*/
return cycles - ppc_icount;
}

2832
CPU/PowerPC/ppc_ops.c Normal file

File diff suppressed because it is too large Load diff

170
CPU/PowerPC/ppc_ops.h Normal file
View file

@ -0,0 +1,170 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* ppc_ops.h
*
* Header file defining PowerPC opcode handlers.
*/
static PPC_OPCODE ppc_opcode_common[] =
{
/*code subcode handler */
{ 31, 266, ppc_addx },
{ 31, 266 | 512, ppc_addx },
{ 31, 10, ppc_addcx },
{ 31, 10 | 512, ppc_addcx },
{ 31, 138, ppc_addex },
{ 31, 138 | 512, ppc_addex },
{ 14, -1, ppc_addi },
{ 12, -1, ppc_addic },
{ 13, -1, ppc_addic_rc },
{ 15, -1, ppc_addis },
{ 31, 234, ppc_addmex },
{ 31, 234 | 512, ppc_addmex },
{ 31, 202, ppc_addzex },
{ 31, 202 | 512, ppc_addzex },
{ 31, 28, ppc_andx },
{ 31, 28 | 512, ppc_andx },
{ 31, 60, ppc_andcx },
{ 28, -1, ppc_andi_rc },
{ 29, -1, ppc_andis_rc },
{ 18, -1, ppc_bx },
{ 16, -1, ppc_bcx },
{ 19, 528, ppc_bcctrx },
{ 19, 16, ppc_bclrx },
{ 31, 0, ppc_cmp },
{ 11, -1, ppc_cmpi },
{ 31, 32, ppc_cmpl },
{ 10, -1, ppc_cmpli },
{ 31, 26, ppc_cntlzw },
{ 19, 257, ppc_crand },
{ 19, 129, ppc_crandc },
{ 19, 289, ppc_creqv },
{ 19, 225, ppc_crnand },
{ 19, 33, ppc_crnor },
{ 19, 449, ppc_cror },
{ 19, 417, ppc_crorc },
{ 19, 193, ppc_crxor },
{ 31, 86, ppc_dcbf },
{ 31, 470, ppc_dcbi },
{ 31, 54, ppc_dcbst },
{ 31, 278, ppc_dcbt },
{ 31, 246, ppc_dcbtst },
{ 31, 1014, ppc_dcbz },
{ 31, 491, ppc_divwx },
{ 31, 491 | 512, ppc_divwx },
{ 31, 459, ppc_divwux },
{ 31, 459 | 512, ppc_divwux },
{ 31, 854, ppc_eieio },
{ 31, 284, ppc_eqvx },
{ 31, 954, ppc_extsbx },
{ 31, 922, ppc_extshx },
{ 31, 982, ppc_icbi },
{ 19, 150, ppc_isync },
{ 34, -1, ppc_lbz },
{ 35, -1, ppc_lbzu },
{ 31, 119, ppc_lbzux },
{ 31, 87, ppc_lbzx },
{ 42, -1, ppc_lha },
{ 43, -1, ppc_lhau },
{ 31, 375, ppc_lhaux },
{ 31, 343, ppc_lhax },
{ 31, 790, ppc_lhbrx },
{ 40, -1, ppc_lhz },
{ 41, -1, ppc_lhzu },
{ 31, 311, ppc_lhzux },
{ 31, 279, ppc_lhzx },
{ 46, -1, ppc_lmw },
{ 31, 597, ppc_lswi },
{ 31, 533, ppc_lswx },
{ 31, 20, ppc_lwarx },
{ 31, 534, ppc_lwbrx },
{ 32, -1, ppc_lwz },
{ 33, -1, ppc_lwzu },
{ 31, 55, ppc_lwzux },
{ 31, 23, ppc_lwzx },
{ 19, 0, ppc_mcrf },
{ 31, 512, ppc_mcrxr },
{ 31, 19, ppc_mfcr },
{ 31, 83, ppc_mfmsr },
{ 31, 339, ppc_mfspr },
{ 31, 144, ppc_mtcrf },
{ 31, 146, ppc_mtmsr },
{ 31, 467, ppc_mtspr },
{ 31, 75, ppc_mulhwx },
{ 31, 11, ppc_mulhwux },
{ 7, -1, ppc_mulli },
{ 31, 235, ppc_mullwx },
{ 31, 235 | 512, ppc_mullwx },
{ 31, 476, ppc_nandx },
{ 31, 104, ppc_negx },
{ 31, 104 | 512, ppc_negx },
{ 31, 124, ppc_norx },
{ 31, 444, ppc_orx },
{ 31, 412, ppc_orcx },
{ 24, -1, ppc_ori },
{ 25, -1, ppc_oris },
{ 19, 50, ppc_rfi },
{ 20, -1, ppc_rlwimix },
{ 21, -1, ppc_rlwinmx },
{ 23, -1, ppc_rlwnmx },
{ 17, -1, ppc_sc },
{ 31, 24, ppc_slwx },
{ 31, 792, ppc_srawx },
{ 31, 824, ppc_srawix },
{ 31, 536, ppc_srwx },
{ 38, -1, ppc_stb },
{ 39, -1, ppc_stbu },
{ 31, 247, ppc_stbux },
{ 31, 215, ppc_stbx },
{ 44, -1, ppc_sth },
{ 31, 918, ppc_sthbrx },
{ 45, -1, ppc_sthu },
{ 31, 439, ppc_sthux },
{ 31, 407, ppc_sthx },
{ 47, -1, ppc_stmw },
{ 31, 725, ppc_stswi },
{ 31, 661, ppc_stswx },
{ 36, -1, ppc_stw },
{ 31, 662, ppc_stwbrx },
{ 31, 150, ppc_stwcx_rc },
{ 37, -1, ppc_stwu },
{ 31, 183, ppc_stwux },
{ 31, 151, ppc_stwx },
{ 31, 40, ppc_subfx },
{ 31, 40 | 512, ppc_subfx },
{ 31, 8, ppc_subfcx },
{ 31, 8 | 512, ppc_subfcx },
{ 31, 136, ppc_subfex },
{ 31, 136 | 512, ppc_subfex },
{ 8, -1, ppc_subfic },
{ 31, 232, ppc_subfmex },
{ 31, 232 | 512, ppc_subfmex },
{ 31, 200, ppc_subfzex },
{ 31, 200 | 512, ppc_subfzex },
{ 31, 598, ppc_sync },
{ 31, 4, ppc_tw },
{ 3, -1, ppc_twi },
{ 31, 316, ppc_xorx },
{ 26, -1, ppc_xori },
{ 27, -1, ppc_xoris }
};

1051
Games.cpp Normal file

File diff suppressed because it is too large Load diff

94
Games.h Normal file
View file

@ -0,0 +1,94 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Games.h
*
* Header file containing Model 3 game and ROM file information.
*/
#ifndef INCLUDED_GAMES_H
#define INCLUDED_GAMES_H
#include "ROMLoad.h" // ROMInfo structure
/******************************************************************************
Definitions
******************************************************************************/
// Input flags
#define GAME_INPUT_COMMON 0x0001 // game has common controls
#define GAME_INPUT_VEHICLE 0x0002 // game has vehicle controls
#define GAME_INPUT_JOYSTICK1 0x0004 // game has joystick 1
#define GAME_INPUT_JOYSTICK2 0x0008 // game has joystick 2
#define GAME_INPUT_FIGHTING 0x0010 // game has fighting game controls
#define GAME_INPUT_VR 0x0020 // game has VR view buttons
#define GAME_INPUT_RALLY 0x0040 // game has rally car controls
#define GAME_INPUT_GUN1 0x0080 // game has gun 1
#define GAME_INPUT_GUN2 0x0100 // game has gun 2
#define GAME_INPUT_SHIFT4 0x0200 // game has 4-speed shifter
#define GAME_INPUT_ANALOG_JOYSTICK 0x0400 // game has analog joystick
#define GAME_INPUT_TWIN_JOYSTICKS 0x0800 // game has twin joysticks
#define GAME_INPUT_SOCCER 0x1000 // game has soccer controls
#define GAME_INPUT_ALL 0x1FFF
/******************************************************************************
Data Structures
******************************************************************************/
/*
* GameInfo:
*
* Describes a Model 3 game. List is terminated when title == NULL.
*/
struct GameInfo
{
// Game information
const char id[9]; // 8-character game identifier (also serves as zip archive file name)
const char *title; // complete game title
const char *mfgName; // name of manufacturer
unsigned year; // year released (in decimal)
int step; // Model 3 hardware stepping: 0x10 = 1.0, 0x15 = 1.5, 0x20 = 2.0, 0x21 = 2.1
unsigned cromSize; // size of fixed CROM (up to 8 MB)
BOOL mirrorLow64MB; // mirror low 64 MB of banked CROM space to upper 64 MB
unsigned vromSize; // size of video ROMs (32 or 64 MB; if the latter, will have to be mirrored)
unsigned inputFlags; // game input types
// ROM files
struct ROMInfo ROM[40];
};
/******************************************************************************
Model 3 Game List
All games supported by Supermodel. All ROMs are loaded according to their
native endianness. That is, the PowerPC ROMs are loaded just as a real
PowerPC would see them. The emulator may reorder the bytes on its own for
performance reasons (but the ROMs are not specified that way here).
******************************************************************************/
extern const struct GameInfo Model3GameList[];
#endif // INCLUDED_GAMES_H

64
Graphics/Error.cpp Normal file
View file

@ -0,0 +1,64 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Error.cpp
*
* Error reporting for 3D renderer. There are some situations in which it is
* appropriate to inform the user of an error during rendering. However, these
* will frequently lead to an avalanche of error messages. Therefore, error
* messages are managed by this interface, ensuring that they are displayed not
* more than once per frame.
*
* Error functions can always be counted on to return FAIL, like ErrorLog().
*/
#include "Supermodel.h"
// Error bit flags (must not conflict)
#define ERROR_LOCAL_VERTEX_OVERFLOW 0x1
#define ERROR_UNABLE_TO_CACHE_MODEL 0x2
// Overflow in the local vertex buffer, which holds one model
BOOL CRender3D::ErrorLocalVertexOverflow(void)
{
if ((errorMsgFlags&ERROR_LOCAL_VERTEX_OVERFLOW))
return FAIL;
errorMsgFlags |= ERROR_LOCAL_VERTEX_OVERFLOW;
return ErrorLog("Overflow in local vertex buffer!");
}
// Model could not be cached, even after dumping display list and re-caching
BOOL CRender3D::ErrorUnableToCacheModel(UINT32 modelAddr)
{
if ((errorMsgFlags&ERROR_UNABLE_TO_CACHE_MODEL))
return FAIL;
errorMsgFlags |= ERROR_UNABLE_TO_CACHE_MODEL;
return ErrorLog("Encountered a model that was too large to cache (at %08X)!", modelAddr);
}
// Call this every frame to clear the error flag, allowing errors to be printed
void CRender3D::ClearErrors(void)
{
errorMsgFlags = 0;
}

809
Graphics/Models.cpp Normal file
View file

@ -0,0 +1,809 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Models.cpp
*
* Model parsing, caching, and drawing.
*/
#include <math.h>
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Definitions and Constants
******************************************************************************/
/*
* VBO Vertex Layout
*
* All vertex information is stored in an array of GLfloats. Offset and size
* information is defined here for now.
*/
#define VBO_VERTEX_OFFSET_X 0 // vertex X *
#define VBO_VERTEX_OFFSET_Y 1 // vertex Y *
#define VBO_VERTEX_OFFSET_Z 2 // vertex Z *
#define VBO_VERTEX_OFFSET_NX 3 // normal X *
#define VBO_VERTEX_OFFSET_NY 4 // normal Y *
#define VBO_VERTEX_OFFSET_NZ 5 // normal Z *
#define VBO_VERTEX_OFFSET_R 6 // color (untextured polys) and material (textured polys) R c (w/ texenable?)
#define VBO_VERTEX_OFFSET_G 7 // color and material G
#define VBO_VERTEX_OFFSET_B 8 // color and material B
#define VBO_VERTEX_OFFSET_TRANSLUCENCE 9 // translucence level (0.0 fully transparent, 1.0 opaque) *
#define VBO_VERTEX_OFFSET_LIGHTENABLE 10 // lighting enabled (0.0 luminous, 1.0 light enabled) *
#define VBO_VERTEX_OFFSET_FOGINTENSITY 11 // fog intensity (0.0 no fog applied, 1.0 all fog applied) * (combine w/ lightenable, making one negative)
#define VBO_VERTEX_OFFSET_U 12 // texture U coordinate (in texels, relative to sub-texture) *
#define VBO_VERTEX_OFFSET_V 13 // texture V coordinate *
#define VBO_VERTEX_OFFSET_TEXTURE_X 14 // sub-texture parameters, X (position in overall texture map, in texels) *
#define VBO_VERTEX_OFFSET_TEXTURE_Y 15 // "" Y "" *
#define VBO_VERTEX_OFFSET_TEXTURE_W 16 // sub-texture parameters, width of texture in texels *
#define VBO_VERTEX_OFFSET_TEXTURE_H 17 // "" height of texture in texels *
#define VBO_VERTEX_OFFSET_TEXPARAMS_EN 18 // texture parameter: ==1 texturing enabled, ==0 disabled (per-polygon) c (w/ R?)
#define VBO_VERTEX_OFFSET_TEXPARAMS_TRANS 19 // texture parameter: >=0 use transparency bit, <0 no transparency (per-polygon) c (w/ contour?)
#define VBO_VERTEX_OFFSET_TEXPARAMS_UWRAP 20 // texture parameters: U wrap mode: ==1 mirrored repeat, ==0 normal repeat
#define VBO_VERTEX_OFFSET_TEXPARAMS_VWRAP 21 // "" V wrap mode ""
#define VBO_VERTEX_OFFSET_TEXFORMAT_CONTOUR 22 // contour texture: >0 indicates contour texture (see also texParams.trans) c (w/ trans?)
#define VBO_VERTEX_SIZE 23 // total size (may include padding for alignment)
/******************************************************************************
Display Lists
Every instance of a model encountered in the scene database during rendering
is stored in the display list along with its current transformation matrices
and other state information. Display lists are bound to model caches for
performance: only one VBO has to be bound for an entire display list.
Binding display lists to model caches may cause priority problems among
alpha polygons. Therefore, it may be necessary in the future to decouple them.
******************************************************************************/
// Draws the display list
void CRender3D::DrawDisplayList(ModelCache *Cache, POLY_STATE state)
{
DisplayList *D;
// Bind and activate VBO (pointers activate currently bound VBO)
glBindBuffer(GL_ARRAY_BUFFER, Cache->vboID);
glVertexPointer(3, GL_FLOAT, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_X*sizeof(GLfloat)));
glNormalPointer(GL_FLOAT, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_NX*sizeof(GLfloat)));
glTexCoordPointer(2, GL_FLOAT, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_U*sizeof(GLfloat)));
glColorPointer(3, GL_FLOAT, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_R*sizeof(GLfloat)));
glVertexAttribPointer(subTextureLoc, 4, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_TEXTURE_X*sizeof(GLfloat)));
glVertexAttribPointer(texParamsLoc, 4, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_TEXPARAMS_EN*sizeof(GLfloat)));
glVertexAttribPointer(texFormatLoc, 1, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_TEXFORMAT_CONTOUR*sizeof(GLfloat)));
glVertexAttribPointer(transLevelLoc, 1, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_TRANSLUCENCE*sizeof(GLfloat)));
glVertexAttribPointer(lightEnableLoc, 1, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_LIGHTENABLE*sizeof(GLfloat)));
glVertexAttribPointer(fogIntensityLoc, 1, GL_FLOAT, GL_FALSE, VBO_VERTEX_SIZE*sizeof(GLfloat), (GLvoid *) (VBO_VERTEX_OFFSET_FOGINTENSITY*sizeof(GLfloat)));
// Set up state
if (state == POLY_STATE_ALPHA)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
glDisable(GL_BLEND);
}
// Draw if there are items in the list
D = Cache->ListHead[state];
while (D != NULL)
{
if (D->isViewport)
{
if (D->next != NULL) // if nothing follows, no point in doing this
{
if (!D->next->isViewport)
{
glUniform3fv(lightingLoc, 2, D->Data.Viewport.lightingParams);
glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, D->Data.Viewport.projectionMatrix);
glFogf(GL_FOG_DENSITY, D->Data.Viewport.fogParams[3]);
glFogf(GL_FOG_START, D->Data.Viewport.fogParams[4]);
glFogfv(GL_FOG_COLOR, &(D->Data.Viewport.fogParams[0]));
glUniform4fv(spotEllipseLoc, 1, D->Data.Viewport.spotEllipse);
glUniform2fv(spotRangeLoc, 1, D->Data.Viewport.spotRange);
glUniform3fv(spotColorLoc, 1, D->Data.Viewport.spotColor);
glViewport(D->Data.Viewport.x, D->Data.Viewport.y, D->Data.Viewport.width, D->Data.Viewport.height);
}
}
}
else
{
glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, D->Data.Model.modelViewMatrix);
glDrawArrays(GL_TRIANGLES, D->Data.Model.index, D->Data.Model.numVerts);
}
D = D->next;
}
}
// Appends an instance of a model or viewport to the display list, copying over the required state information
BOOL CRender3D::AppendDisplayList(ModelCache *Cache, BOOL isViewport, int modelNum)
{
int lm, i;
if ((Cache->listSize+2) > Cache->maxListSize) // a model may have 2 states (viewports are added to both display lists)
return FAIL;
//return ErrorLog("Display list is full.");
// Insert states into the display list
for (i = 0; i < 2; i++)
{
if (isViewport)
{
// Get index for new display list item and advance to next one
lm = Cache->listSize++;
// Viewport parameters
Cache->List[lm].Data.Viewport.x = viewportX;
Cache->List[lm].Data.Viewport.y = viewportY;
Cache->List[lm].Data.Viewport.width = viewportWidth;
Cache->List[lm].Data.Viewport.height = viewportHeight;
// Copy over lighting and fog state
memcpy(Cache->List[lm].Data.Viewport.lightingParams, lightingParams, sizeof(lightingParams));
memcpy(Cache->List[lm].Data.Viewport.fogParams, fogParams, sizeof(fogParams));
memcpy(Cache->List[lm].Data.Viewport.spotEllipse, spotEllipse, sizeof(spotEllipse));
memcpy(Cache->List[lm].Data.Viewport.spotRange, spotRange, sizeof(spotRange));
memcpy(Cache->List[lm].Data.Viewport.spotColor, spotColor, sizeof(spotColor));
// Copy projection matrix
glGetFloatv(GL_PROJECTION_MATRIX, Cache->List[lm].Data.Viewport.projectionMatrix);
}
else if (Cache->Models[modelNum].numVerts[i] > 0) // vertices exist for this state
{
// Get index for new display list item and advance to next one
lm = Cache->listSize++;
// Point to VBO for current model and state
Cache->List[lm].Data.Model.index = Cache->Models[modelNum].index[i];
Cache->List[lm].Data.Model.numVerts = Cache->Models[modelNum].numVerts[i];
// Copy modelview matrix
glGetFloatv(GL_MODELVIEW_MATRIX, Cache->List[lm].Data.Model.modelViewMatrix);
}
else // nothing to do, continue loop
continue;
// Update list pointers and set list node type
Cache->List[lm].isViewport = isViewport;
Cache->List[lm].next = NULL; // current end of list
if (Cache->ListHead[i] == NULL)
{
Cache->ListHead[i] = &(Cache->List[lm]);
Cache->ListTail[i] = Cache->ListHead[i];
}
else
{
Cache->ListTail[i]->next = &(Cache->List[lm]);
Cache->ListTail[i] = &(Cache->List[lm]);
}
}
return OKAY;
}
// Clears the display list in preparation for a new frame
void CRender3D::ClearDisplayList(ModelCache *Cache)
{
Cache->listSize = 0;
for (int i = 0; i < 2; i++)
{
Cache->ListHead[i] = NULL;
Cache->ListTail[i] = NULL;
}
}
/******************************************************************************
Model Caching
Note that as vertices are inserted into the appropriate local vertex buffer
(sorted by polygon state -- alpha and normal), the VBO index is advanced to
reserve space and does not correspond to the actual position of each vertex.
Vertices are copied in batches sorted by state when the model is complete.
******************************************************************************/
static void CrossProd(GLfloat out[3], GLfloat a[3], GLfloat b[3])
{
out[0] = a[1]*b[2]-a[2]*b[1];
out[1] = a[2]*b[0]-a[0]*b[2];
out[2] = a[0]*b[1]-a[1]*b[0];
}
// Inserts a vertex into the local vertex buffer, incrementing both the local and VBO pointers. The normal is scaled by normFlip.
void CRender3D::InsertVertex(ModelCache *Cache, const Vertex *V, const Poly *P, float normFlip)
{
GLfloat r, g, b;
GLfloat translucence, fogIntensity, texWidth, texHeight, texBaseX, texBaseY, contourProcessing;
unsigned baseIdx, texFormat, texEnable, lightEnable, modulate, colorIdx;
int s, texPage;
// Texture selection
texEnable = P->header[6]&0x04000000;
texFormat = (P->header[6]>>7)&7;
texWidth = (GLfloat) (32<<((P->header[3]>>3)&7));
texHeight = (GLfloat) (32<<((P->header[3]>>0)&7));
texPage = (P->header[4]&0x40) ? 1024 : 0; // treat texture page as Y coordinate
texBaseX = (GLfloat) (32*(((P->header[4]&0x1F)<<1)|((P->header[5]>>7)&1)));
texBaseY = (GLfloat) (32*(P->header[5]&0x1F)+texPage);
/*
* Lighting and Color Modulation:
*
* It appears that there is a modulate bit which causes the polygon color
* to be multiplied by texel colors. However, if polygons are luminous,
* this appears to be disabled (not quite correct yet, though).
*/
lightEnable = !(P->header[6]&0x00010000);
modulate = !(P->header[4]&0x80);
modulate = P->header[3]&0x80; // seems to work better
// Material color
if ((P->header[1]&2) == 0)
{
colorIdx = (P->header[4]>>20)&0x7FF;
b = (GLfloat) (polyRAM[0x400+colorIdx]&0xFF) * (1.0f/255.0f);
g = (GLfloat) ((polyRAM[0x400+colorIdx]>>8)&0xFF) * (1.0f/255.0f);
r = (GLfloat) ((polyRAM[0x400+colorIdx]>>16)&0xFF) * (1.0f/255.0f);
}
else
{
// Colors are 8-bit (almost certainly true, see Star Wars)
r = (GLfloat) (P->header[4]>>24) * (1.0f/255.0f);
g = (GLfloat) ((P->header[4]>>16)&0xFF) * (1.0f/255.0f);
b = (GLfloat) ((P->header[4]>>8)&0xFF) * (1.0f/255.0f);
}
// Determine modulation settings
if (texEnable)
{
//if (!lightEnable|| !modulate)
if (!modulate)
r = g = b = 1.0f;
}
#if 0
if (texFormat==5)//texFormat==6||texFormat==2)
{
//printf("%03X\n", P->header[4]>>8);
//texEnable=0;
g=b=1.0;
r=1.0f;
}
#endif
#if 0
if ((P->header[testWord]&(1<<testBit)))
{
texEnable = 0;
r=b=0;
g=1.0f;
if (!lightEnable)
b=1.0f;
lightEnable=0;
}
#endif
// Determine whether polygon is translucent
translucence = (GLfloat) ((P->header[6]>>18)&0x1F) * (1.0f/31.0f);
if ((P->header[6]&0x00800000)) // if set, polygon is opaque
translucence = 1.0f;
// Fog intensity (for luminous polygons)
fogIntensity = (GLfloat) ((P->header[6]>>11)&0x1F) * (1.0f/31.0f);
if (!(P->header[6]&0x00010000)) // if not luminous, always use full fog intensity
fogIntensity = 1.0f;
// Contour processing
if ((P->header[6]&0x80000000) || (texFormat==7)) // contour processing enabled or alpha texture
contourProcessing = 1.0f;
else
contourProcessing = -1.0f;
// Store to local vertex buffer
s = P->state;
baseIdx = Cache->curVertIdx[s]*VBO_VERTEX_SIZE;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_X] = V->x;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_Y] = V->y;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_Z] = V->z;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_R] = r;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_G] = g;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_B] = b;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TRANSLUCENCE] = translucence;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_LIGHTENABLE] = lightEnable ? 1.0f : 0.0f;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_FOGINTENSITY] = fogIntensity;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_NX] = V->n[0]*normFlip;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_NY] = V->n[1]*normFlip;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_NZ] = V->n[2]*normFlip;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_U] = V->u;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_V] = V->v;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXTURE_X] = texBaseX;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXTURE_Y] = texBaseY;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXTURE_W] = texWidth;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXTURE_H] = texHeight;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXPARAMS_EN] = texEnable ? 1.0f : 0.0f;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXPARAMS_TRANS] = contourProcessing;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXPARAMS_UWRAP] = (P->header[2]&2) ? 1.0f : 0.0f;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXPARAMS_VWRAP] = (P->header[2]&1) ? 1.0f : 0.0f;
Cache->verts[s][baseIdx + VBO_VERTEX_OFFSET_TEXFORMAT_CONTOUR] = (texFormat==0) ? 1.0f : 0.0f;
Cache->curVertIdx[s]++;
Cache->vboCurOffset += VBO_VERTEX_SIZE*sizeof(GLfloat);
}
BOOL CRender3D::InsertPolygon(ModelCache *Cache, const Poly *P)
{
GLfloat n[3], v1[3], v2[3], normZFlip;
int i;
BOOL doubleSided;
// Bounds testing: up to 12 triangles will be inserted (worst case: double sided quad is 6 triangles)
if ((Cache->curVertIdx[P->state]+6*2) >= Cache->maxVertIdx)
return ErrorLocalVertexOverflow(); // local buffers are not expected to overflow
if ((Cache->vboCurOffset+6*2*VBO_VERTEX_SIZE*sizeof(GLfloat)) >= Cache->vboMaxOffset)
return FAIL; // this just indicates we may need to re-cache
// Is the polygon double sided?
doubleSided = (P->header[1]&0x10) ? TRUE : FALSE;
/*
* Determine polygon winding by taking cross product of vectors formed from
* 3 polygon vertices (the middle one being the origin). In reality, back-
* face culling is determined by the polygon normal and two-sided polygons
* exist. This is just a temporary hack.
*
* If the cross product points the same way as the normal, the winding is
* clockwise and can be kept, otherwise it must be reversed.
*
* NOTE: This assumes that the Model 3 base coordinate system's Z axis
* (into the screen) -1, like OpenGL's. For some games (eg., Lost World),
* this is not the case. Assuming games consistently use the same type of
* coordinate system matrix, it seems that inverting the whole dot product
* when Z is positive helps. I don't understand why...
*/
v1[0] = P->Vert[0].x-P->Vert[1].x;
v1[1] = P->Vert[0].y-P->Vert[1].y;
v1[2] = P->Vert[0].z-P->Vert[1].z;
v2[0] = P->Vert[2].x-P->Vert[1].x;
v2[1] = P->Vert[2].y-P->Vert[1].y;
v2[2] = P->Vert[2].z-P->Vert[1].z;
CrossProd(n,v1,v2);
normZFlip = -1.0f*matrixBasePtr[0x5]; // coordinate system m13 component
if (normZFlip*(n[0]*P->n[0]+n[1]*P->n[1]+n[2]*P->n[2]) >= 0.0) // clockwise winding confirmed
{
// Store the first triangle
for (i = 0; i < 3; i++)
{
InsertVertex(Cache, &(P->Vert[i]), P, 1.0f);
}
if (doubleSided) // store backside as counter-clockwise
{
for (i = 2; i >=0; i--)
{
InsertVertex(Cache, &(P->Vert[i]), P, -1.0f);
}
}
// If quad, second triangle will just be vertices 1, 3, 4
if (P->numVerts == 4)
{
InsertVertex(Cache, &(P->Vert[0]), P, 1.0f);
InsertVertex(Cache, &(P->Vert[2]), P, 1.0f);
InsertVertex(Cache, &(P->Vert[3]), P, 1.0f);
if (doubleSided)
{
InsertVertex(Cache, &(P->Vert[0]), P, -1.0f);
InsertVertex(Cache, &(P->Vert[3]), P, -1.0f);
InsertVertex(Cache, &(P->Vert[2]), P, -1.0f);
}
}
}
else // counterclockwise winding, reverse it
{
for (i = 2; i >=0; i--)
{
InsertVertex(Cache, &(P->Vert[i]), P, 1.0f);
}
if (doubleSided) // store backside as clockwise
{
for (i = 0; i < 3; i++)
{
InsertVertex(Cache, &(P->Vert[i]), P, -1.0f);
}
}
if (P->numVerts == 4)
{
InsertVertex(Cache, &(P->Vert[0]), P, 1.0f);
InsertVertex(Cache, &(P->Vert[3]), P, 1.0f);
InsertVertex(Cache, &(P->Vert[2]), P, 1.0f);
if (doubleSided)
{
InsertVertex(Cache, &(P->Vert[0]), P, -1.0f);
InsertVertex(Cache, &(P->Vert[2]), P, -1.0f);
InsertVertex(Cache, &(P->Vert[3]), P, -1.0f);
}
}
}
return OKAY;
}
// Begins caching a new model by resetting to the start of the local vertex buffer
BOOL CRender3D::BeginModel(ModelCache *Cache)
{
int m;
// Determine whether we've exceeded the model cache limits (caller will have to recache)
if (Cache->numModels >= Cache->maxModels)
return FAIL; // too many models
//return ErrorLog("Too many %s models.", Cache->dynamic?"dynamic":"static");
m = Cache->numModels;
// Reset to the beginning of the local vertex buffer
for (int i = 0; i < 2; i++)
Cache->curVertIdx[i] = 0;
// Clear the VBO reference to 0
memset(&(Cache->Models[m]), 0, sizeof(VBORef));
// Record starting index of first opaque polygon in VBO (alpha poly index will be re-set in EndModel())
Cache->Models[m].index[POLY_STATE_NORMAL] = Cache->vboCurOffset/(VBO_VERTEX_SIZE*sizeof(GLfloat));
Cache->Models[m].index[POLY_STATE_ALPHA] = Cache->Models[m].index[POLY_STATE_NORMAL];
return OKAY;
}
// Uploads all vertices from the local vertex buffer to the VBO, sets up the VBO reference, updates the LUT
void CRender3D::EndModel(ModelCache *Cache, int lutIdx)
{
int m;
m = Cache->numModels++;
// Record the number of vertices, completing the VBORef
for (int i = 0; i < 2; i++)
Cache->Models[m].numVerts[i] = Cache->curVertIdx[i];
// First alpha polygon immediately follows the normal polygons
Cache->Models[m].index[POLY_STATE_ALPHA] = Cache->Models[m].index[POLY_STATE_NORMAL]+Cache->Models[m].numVerts[POLY_STATE_NORMAL];
// Upload from local vertex buffer to real VBO
glBindBuffer(GL_ARRAY_BUFFER, Cache->vboID);
if (Cache->Models[m].numVerts[POLY_STATE_NORMAL] > 0)
glBufferSubData(GL_ARRAY_BUFFER, Cache->Models[m].index[POLY_STATE_NORMAL]*VBO_VERTEX_SIZE*sizeof(GLfloat), Cache->curVertIdx[POLY_STATE_NORMAL]*VBO_VERTEX_SIZE*sizeof(GLfloat), Cache->verts[POLY_STATE_NORMAL]);
if (Cache->Models[m].numVerts[POLY_STATE_ALPHA] > 0)
glBufferSubData(GL_ARRAY_BUFFER, Cache->Models[m].index[POLY_STATE_ALPHA]*VBO_VERTEX_SIZE*sizeof(GLfloat), Cache->curVertIdx[POLY_STATE_ALPHA]*VBO_VERTEX_SIZE*sizeof(GLfloat), Cache->verts[POLY_STATE_ALPHA]);
// Update the LUT
Cache->lut[lutIdx] = m;
// Record LUT index in the model VBORef
Cache->Models[m].lutIdx = lutIdx;
}
/*
* CacheModel():
*
* Decodes and caches a complete model. Returns FAIL if any sort of overflow in
* the cache occurred. In this case, the model cache should be cleared before
* being used again because an incomplete model will be stored, wasting vertex
* buffer space.
*/
BOOL CRender3D::CacheModel(ModelCache *Cache, int lutIdx, const UINT32 *data)
{
Vertex Prev[4]; // previous vertices
int numPolys = 0;
BOOL done = FALSE;
// Sega Rally 2 bad models
//if (lutIdx == 0x27a1 || lutIdx == 0x21e0)
// return FAIL;
if (data == NULL)
return FAIL;
// Start constructing a new model
if (FAIL == BeginModel(Cache))
return FAIL; // too many models!
// Cache all polygons
while (!done)
{
Poly P; // current polygon
GLfloat mag;
GLfloat uvScale;
int texFormat, texWidth, texHeight, texPage, texBaseX, texBaseY;
unsigned i, j, vmask;
UINT32 ix, iy, iz, it;
// Set current header pointer (header is 7 words)
P.header = data;
data += 7; // data will now point to first vertex
if (P.header[6]==0)// || P.header[0]==0)
break;
// Obtain basic polygon parameters
done = P.header[1]&4; // last polygon?
P.numVerts = (P.header[0]&0x40)?4:3;
// Texture data
texFormat = (P.header[6]>>7)&7;
texWidth = (32<<((P.header[3]>>3)&7));
texHeight = (32<<((P.header[3]>>0)&7));
texPage = (P.header[4]&0x40) ? 1024 : 0; // treat texture page as Y coordinate
texBaseX = (32*(((P.header[4]&0x1F)<<1)|((P.header[5]>>7)&1)));
texBaseY = (32*(P.header[5]&0x1F)+texPage);
uvScale = (P.header[1]&0x40)?1.0f:(1.0f/8.0f);
// Determine whether this is an alpha polygon
if (((P.header[6]&0x00800000)==0) || // translucent polygon
(texFormat==7) || // RGBA4 texture
(texFormat==4)) // A4L4 texture
P.state = POLY_STATE_ALPHA;
else
P.state = POLY_STATE_NORMAL;
if (texFormat==1) // A4L4 interleaved
{
if ((P.header[6]&2))
P.state = POLY_STATE_ALPHA;
else
P.state = POLY_STATE_NORMAL;
}
if (texFormat==3) // A4L4 interleaved
{
if ((P.header[6]&4))
P.state = POLY_STATE_ALPHA;
else
P.state = POLY_STATE_NORMAL;
}
// Decode the texture
DecodeTexture(texFormat, texBaseX, texBaseY, texWidth, texHeight);
// Polygon normal is in upper 24 bits: sign + 1.22 fixed point
P.n[0] = (GLfloat) (((INT32)P.header[1])>>8) * (1.0f/4194304.0f);
P.n[1] = (GLfloat) (((INT32)P.header[2])>>8) * (1.0f/4194304.0f);
P.n[2] = (GLfloat) (((INT32)P.header[3])>>8) * (1.0f/4194304.0f);
// Fetch reused vertices according to bitfield, then new verts
i = 0;
j = 0;
vmask = 1;
for (i = 0; i < 4; i++) // up to 4 reused vertices
{
if ((P.header[0x00]&vmask))
{
P.Vert[j] = Prev[i];
++j;
}
vmask <<= 1;
}
for (; j < P.numVerts; j++) // remaining vertices are new and defined here
{
// Fetch vertices
ix = data[0];
iy = data[1];
iz = data[2];
it = data[3];
/*
// Check for bad vertices (Sega Rally 2)
if (((ix>>28)==7) || ((iy>>28)==7) || ((iz>>28)==7))
{
//printf("%X ix=%08X, iy=%08X, iz=%08X\n", lutIdx, ix, iy, iz);
goto StopDecoding;
}
*/
// Decode vertices
P.Vert[j].x = (GLfloat) (((INT32)ix)>>8) * vertexFactor;
P.Vert[j].y = (GLfloat) (((INT32)iy)>>8) * vertexFactor;
P.Vert[j].z = (GLfloat) (((INT32)iz)>>8) * vertexFactor;
P.Vert[j].n[0] = P.n[0]+(GLfloat)(INT8)(ix&0xFF); // vertex normals are offset from polygon normal
P.Vert[j].n[1] = P.n[1]+(GLfloat)(INT8)(iy&0xFF);
P.Vert[j].n[2] = P.n[2]+(GLfloat)(INT8)(iz&0xFF);
P.Vert[j].u = (GLfloat) ((UINT16)(it>>16)) * uvScale; // TO-DO: might these be signed?
P.Vert[j].v = (GLfloat) ((UINT16)(it&0xFFFF)) * uvScale;
data += 4;
// Normalize the vertex normal
mag = sqrt(P.Vert[j].n[0]*P.Vert[j].n[0]+P.Vert[j].n[1]*P.Vert[j].n[1]+P.Vert[j].n[2]*P.Vert[j].n[2]);
P.Vert[j].n[0] /= mag;
P.Vert[j].n[1] /= mag;
P.Vert[j].n[2] /= mag;
}
// Copy current vertices into previous vertex array
for (i = 0; i < 4; i++)
Prev[i] = P.Vert[i];
// Copy this polygon into the model buffer
if (OKAY != InsertPolygon(Cache,&P))
return FAIL;
++numPolys;
}
StopDecoding:
// Finish model and enter it into the LUT
EndModel(Cache,lutIdx);
return OKAY;
}
/******************************************************************************
Cache Management
******************************************************************************/
// Use this to determine if a model needs to be cached (returns TRUE if so)
BOOL CRender3D::NeedToCache(ModelCache *Cache, int lutIdx)
{
//if (Cache->dynamic) // never permanently store models in dynamic caches
// return TRUE;
return (Cache->lut[lutIdx]<0) ? TRUE : FALSE;
}
// Discard all models in the cache and the display list
void CRender3D::ClearModelCache(ModelCache *Cache)
{
Cache->vboCurOffset = 0;
for (int i = 0; i < 2; i++)
Cache->curVertIdx[i] = 0;
if (!Cache->dynamic)
memset(Cache->lut, 0xFF, sizeof(INT16)*Cache->lutSize); // set all to -1
else
{
for (int i = 0; i < Cache->numModels; i++)
Cache->lut[Cache->Models[i].lutIdx] = -1;
}
Cache->numModels = 0;
ClearDisplayList(Cache);
}
BOOL CRender3D::CreateModelCache(ModelCache *Cache, unsigned vboMaxVerts,
unsigned localMaxVerts, unsigned maxNumModels, unsigned numLUTEntries,
unsigned displayListSize, BOOL isDynamic)
{
unsigned i;
int vboBytes, localBytes;
BOOL success;
Cache->dynamic = isDynamic;
/*
* VBO allocation:
*
* Progressively smaller VBOs, in steps of localMaxVerts are allocated
* until successful. If the size dips below localMaxVerts, localMaxVerts is
* attempted as the final try.
*/
glGetError(); // clear error flag
glGenBuffers(1, &(Cache->vboID));
glBindBuffer(GL_ARRAY_BUFFER, Cache->vboID);
vboBytes = vboMaxVerts*VBO_VERTEX_SIZE*sizeof(GLfloat);
localBytes = localMaxVerts*VBO_VERTEX_SIZE*sizeof(GLfloat);
// Try allocating until size is
success = FALSE;
while (vboBytes >= localBytes)
{
glBufferData(GL_ARRAY_BUFFER, vboBytes, 0, isDynamic?GL_STREAM_DRAW:GL_STATIC_DRAW);
if (glGetError() == GL_NO_ERROR)
{
success = TRUE;
break;
}
vboBytes -= localBytes;
}
if (!success)
{
// Last ditch attempt: try the local buffer size
vboBytes = localBytes;
glBufferData(GL_ARRAY_BUFFER, vboBytes, 0, isDynamic?GL_STREAM_DRAW:GL_STATIC_DRAW);
if (glGetError() != GL_NO_ERROR)
return ErrorLog("OpenGL was unable to provide a %s vertex buffer.", isDynamic?"dynamic":"static");
}
DebugLog("%s vertex buffer size: %1.2f MB", isDynamic?"Dynamic":"Static", (float)vboBytes/(float)0x100000);
InfoLog("%s vertex buffer size: %1.2f MB", isDynamic?"Dynamic":"Static", (float)vboBytes/(float)0x100000);
// Set the VBO to the size we obtained
Cache->vboMaxOffset = vboBytes;
Cache->vboCurOffset = 0;
// Attempt to allocate space for local VBO
for (i = 0; i < 2; i++)
{
Cache->verts[i] = new(std::nothrow) GLfloat[localMaxVerts*VBO_VERTEX_SIZE];
Cache->curVertIdx[i] = 0;
}
Cache->maxVertIdx = localMaxVerts;
// ... model array
Cache->Models = new(std::nothrow) VBORef[maxNumModels];
Cache->maxModels = maxNumModels;
Cache->numModels = 0;
// ... LUT
Cache->lut = new(std::nothrow) INT16[numLUTEntries];
Cache->lutSize = numLUTEntries;
// ... display list
Cache->List = new(std::nothrow) DisplayList[displayListSize];
ClearDisplayList(Cache);
Cache->maxListSize = displayListSize;
// Check if memory allocation succeeded
if ((Cache->verts[0]==NULL) || (Cache->verts[1]==NULL) || (Cache->Models==NULL) || (Cache->lut==NULL) || (Cache->List==NULL))
{
DestroyModelCache(Cache);
return ErrorLog("Insufficient memory for model cache.");
}
// Clear LUT (MUST be done here because ClearModelCache() won't do it for dynamic models)
for (i = 0; i < numLUTEntries; i++)
Cache->lut[i] = -1;
// All good!
return OKAY;
}
void CRender3D::DestroyModelCache(ModelCache *Cache)
{
for (int i = 0; i < 2; i++)
{
if (Cache->verts[i] != NULL)
delete [] Cache->verts[i];
}
if (Cache->Models != NULL)
delete [] Cache->Models;
if (Cache->lut != NULL)
delete [] Cache->lut;
if (Cache->List != NULL)
delete [] Cache->List;
memset(Cache, 0, sizeof(ModelCache));
}

170
Graphics/Render.h Normal file
View file

@ -0,0 +1,170 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Render.h
*
* Header file defining the CRender class: container for both the 2D and 3D
* renderers.
*/
#ifndef INCLUDED_RENDER_H
#define INCLUDED_RENDER_H
/*
* CRender:
*
* Tile generator graphics engine. This must be constructed and initialized
* before being attached to any objects that want to make use of it. Apart from
* the constructor, all members assume that a global GL device
* context is available and that GL functions may be called.
*/
class CRender2D
{
public:
/*
* BeginFrame(layerFormats):
*
* Prepare to render a new frame. Must be called once per frame prior to
* drawing anything.
*
* Parameters:
* layerFormats A bit field indicating how to interpret each of the
* four layers: 0=8-bit pixels, 1=4-bit pixels. Bits
* 3-0 correspond to layers 3-0.
*/
void BeginFrame(unsigned layerFormats);
/*
* EndFrame(void):
*
* Signals the end of rendering for this frame. Must be called last during
* the frame.
*/
void EndFrame(void);
/*
* WriteVRAM(addr, data):
*
* Indicates what will be written next to the tile generator's RAM. The
* VRAM address must not have yet been updated, to allow the renderer to
* check for changes. Data is accepted in the same form as the tile
* generator: the MSB is what was written to addr+3. This function is
* intended to facilitate on-the-fly decoding of tiles and palette data.
*
* Parameters:
* addr Address in tile generator RAM. Caller must ensure it is
* clamped to the range 0x000000 to 0x11FFFF because this
* function does not.
* data The data to write.
*/
void WriteVRAM(unsigned addr, UINT32 data);
/*
* AttachRegisters(regPtr):
*
* Attaches tile generator registers. This must be done prior to any
* rendering otherwise the program may crash with an access violation.
*
* Parameters:
* regPtr Pointer to the base of the tile generator registers.
* There are assumed to be 64 in all.
*/
void AttachRegisters(const UINT32 *regPtr);
/*
* AttachVRAM(vramPtr):
*
* Attaches tile generator RAM. This must be done prior to any rendering
* otherwise the program may crash with an access violation.
*
* Parameters:
* vramPtr Pointer to the base of the tile generator RAM (0x120000
* bytes). VRAM is assumed to be in little endian format.
*/
void AttachVRAM(const UINT8 *vramPtr);
/*
* Init(xOffset, yOffset, xRes, yRes):
*
* One-time initialization of the context. Must be called before any other
* members (meaning it should be called even before being attached to any
* other objects that want to use it).
*
* Parameters:
* xOffset X offset within OpenGL display surface in pixels.
* yOffset Y offset.
* xRes Horizontal resolution of display surface in pixels.
* yRes Vertical resolution.
*
* Returns:
* OKAY is successful, otherwise FAILED if a non-recoverable error
* occurred. Prints own error messages.
*/
BOOL Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes);
/*
* CRender2D(void):
* ~CRender2D(void):
*
* Constructor and destructor.
*/
CRender2D(void);
~CRender2D(void);
private:
// Private member functions
void DrawTile4Bit(unsigned tile, UINT32 *buf, unsigned pitch);
void DrawTile8Bit(unsigned tile, UINT32 *buf, unsigned pitch);
void DrawRect(int layerNum, UINT32 *layerSurf, const UINT32 *nameTable, unsigned x, unsigned y, unsigned w, unsigned h);
void UpdateLayer(int layerNum);
void DisplayLayer(int layerNum, GLfloat z, GLfloat scroll);
void Setup2D(void);
// Data received from tile generator device object
const UINT32 *vram;
const UINT32 *regs;
// OpenGL data
GLuint texID[4]; // IDs for the 4 layer textures
unsigned xPixels, yPixels; // display surface resolution
unsigned xOffs, yOffs; // offset
// Shader programs and input data locations
GLuint shaderProgram; // shader program object
GLuint vertexShader; // vertex shader handle
GLuint fragmentShader; // fragment shader
GLuint textureMapLoc; // location of "textureMap" uniform
GLuint bottomLayerLoc; // uniform
// Dirty rectangles (non-zero indicates region is dirty)
UINT8 dirty[4][64/DIRTY_RECT_HEIGHT][64/DIRTY_RECT_WIDTH];
BOOL allDirty; // global dirty flag (forces everything to be updated)
// Buffers
UINT8 *memoryPool; // all memory is allocated here
UINT32 *surf; // 4 512x512x32bpp pixel surfaces
UINT32 *pal; // 0x20000 byte (32K colors) palette
};
#endif // INCLUDED_RENDER2D_H

871
Graphics/Render2D.cpp Normal file
View file

@ -0,0 +1,871 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Render2D.cpp
*
* Implementation of the CRender2D class: OpenGL tile generator graphics.
*
* To-Do List
* ----------
* - Add dirty rectangles? There is already some inactive code in here for
* this purpose and it needs to be updated or deleted once and for all.
* - Are v-scroll values 9 or 10 bits?
* - Add fast paths for no scrolling (including unclipped tile rendering).
* - Inline the loops in the tile renderers.
* - Update description of tile generator before you forget :)
* - A proper shut-down function is needed! OpenGL might not be available when
* the destructor for this class is called.
*/
#include <string.h>
#include "Pkgs/glew.h"
#include "Supermodel.h"
#include "Graphics/Shaders2D.h" // fragment and vertex shaders
/******************************************************************************
Definitions and Constants
******************************************************************************/
// Shader program files (for use in development builds only)
#define VERTEX_2D_SHADER_FILE "Src/Graphics/Vertex2D.glsl"
#define FRAGMENT_2D_SHADER_FILE "Src/Graphics/Fragment2D.glsl"
/******************************************************************************
Tile Drawing Functions
******************************************************************************/
// Draw 4-bit tile line, no clipping performed
void CRender2D::DrawTileLine4BitNoClip(UINT32 *buf, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
*buf++ = pal[((pattern>>28)&0xF) | palette];
*buf++ = pal[((pattern>>24)&0xF) | palette];
*buf++ = pal[((pattern>>20)&0xF) | palette];
*buf++ = pal[((pattern>>16)&0xF) | palette];
*buf++ = pal[((pattern>>12)&0xF) | palette];
*buf++ = pal[((pattern>>8)&0xF) | palette];
*buf++ = pal[((pattern>>4)&0xF) | palette];
*buf++ = pal[((pattern>>0)&0xF) | palette];
}
// Draw 8-bit tile line, clipped at left edge
void CRender2D::DrawTileLine8BitNoClip(UINT32 *buf, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
*buf++ = pal[((pattern>>24)&0xFF) | palette];
*buf++ = pal[((pattern>>16)&0xFF) | palette];
*buf++ = pal[((pattern>>8)&0xFF) | palette];
*buf++ = pal[((pattern>>0)&0xFF) | palette];
pattern = vram[tileOffset+tileLine+1];
*buf++ = pal[((pattern>>24)&0xFF) | palette];
*buf++ = pal[((pattern>>16)&0xFF) | palette];
*buf++ = pal[((pattern>>8)&0xFF) | palette];
*buf++ = pal[((pattern>>0)&0xFF) | palette];
}
// Draw 4-bit tile line, clipped at left edge
void CRender2D::DrawTileLine4Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
for (int bitPos = 28; bitPos >= 0; bitPos -= 4)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xF) | palette];
++offset;
}
}
// Draw 4-bit tile line, clipped at right edge
void CRender2D::DrawTileLine4BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
int bitPos;
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
bitPos = 28;
for (int i = 0; i < numPixels; i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xF) | palette];
++offset;
bitPos -= 4;
}
}
// Draw 8-bit tile line, clipped at left edge
void CRender2D::DrawTileLine8Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
for (int bitPos = 24; bitPos >= 0; bitPos -= 8)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
}
pattern = vram[tileOffset+tileLine+1];
for (int bitPos = 24; bitPos >= 0; bitPos -= 8)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
}
}
// Draw 8-bit tile line, clipped at right edge
void CRender2D::DrawTileLine8BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
int bitPos;
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
bitPos = 24;
for (int i = 0; (i < 4) && (i < numPixels); i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
bitPos -= 8;
}
pattern = vram[tileOffset+tileLine+1];
bitPos = 24;
for (int i = 0; (i < 4) && (i < numPixels); i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
bitPos -= 8;
}
}
/******************************************************************************
Layer Rendering
******************************************************************************/
/*
* DrawCompleteLayer():
*
* Updates the complete layer.
*/
void CRender2D::DrawCompleteLayer(int layerNum, const UINT16 *nameTableBase)
{
UINT32 *dest = surf; // destination surface to write to
UINT32 *lineBufferPri = &surf[512*496]; // line buffer for primary and alternate layer
UINT32 *lineBufferAlt = &surf[512*497];
UINT32 *buf;
const UINT16 *maskTable; // pointer to start of mask table
const UINT16 *hScrollTablePri, *hScrollTableAlt; // pointers to line scroll tables
const UINT16 *nameTablePri = nameTableBase; // primary (this layer) name table
const UINT16 *nameTableAlt = &nameTableBase[64*64]; // alternate layer's name table
const UINT16 *nameTable;
int colorDepthPri, colorDepthAlt; // primary and alternate layer color depths
int hScrollPri, hScrollAlt; // primary and alternate layer scroll offsets
int vScrollPri, vScrollAlt;
int hFullScrollPri, hFullScrollAlt; // full-screen horizontal scroll values (from registers)
int vOffset; // vertical pixel offset within tile
int tx, i, j;
BOOL lineScrollPri, lineScrollAlt; // line scrolling enable/disable
UINT16 mask;
// Determine layer color depths (1 if 4-bit, 0 if 8-bit)
colorDepthPri = regs[0x20/4] & (1<<(12+layerNum*2));
colorDepthAlt = regs[0x20/4] & (1<<(12+layerNum*2+1));
// Line scroll tables
hScrollTablePri = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400)/4];
hScrollTableAlt = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400+0x400)/4];
// Get correct offset into mask table
maskTable = (UINT16 *) &vram[0xF7000/4];
if (layerNum == 0)
++maskTable; // little endian, layer 0 is second word in each pair
// Load horizontal full-screen scroll values and scroll mode
hFullScrollPri = regs[0x60/4+layerNum*2]&0x3FF;
hFullScrollAlt = regs[0x60/4+layerNum*2+1]&0x3FF;
lineScrollPri = regs[0x60/4+layerNum*2]&0x8000;
lineScrollAlt = regs[0x60/4+layerNum*2+1]&0x8000;
// Load vertical scroll values
vScrollPri = (regs[0x60/4+layerNum*2]>>16)&0x1FF;
vScrollAlt = (regs[0x60/4+layerNum*2+1]>>16)&0x1FF;
// Iterate over all displayed lines
for (int y = 0; y < 384; y++)
{
/*
* Draw all tiles from primary layer first. Horizontal scrolling is not
* applied yet, but vertical scrolling is taken into account. An entire
* 512-pixel line is rendered so that it can be scrolled during mixing.
*/
nameTable = &nameTablePri[(64*((y+vScrollPri)/8)) & 0xFFF]; // clamp to 64x64=0x1000
vOffset = (y+vScrollPri)&7;
buf = lineBufferPri; // output to primary line buffer
for (tx = 0; tx < 64; tx += 4) // 4 tiles at a time (for masking)
{
if (colorDepthPri) //TODO: move this test outside of loop
{
DrawTileLine4BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
else
{
DrawTileLine8BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
// Next set of 4 tiles
nameTable += 4;
}
/*
* Draw the alternate layer wherever the primary layer was masked
*/
nameTable = &nameTableAlt[(64*((y+vScrollAlt)/8))&0xFFF];
vOffset = (y+vScrollAlt)&7;
buf = lineBufferAlt; // output to alternate line buffer
for (tx = 0; tx < 64; tx += 4) // 4 tiles at a time (for masking)
{
if (colorDepthAlt) //TODO: move this test outside of loop
{
DrawTileLine4BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
else
{
DrawTileLine8BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
// Next set of 4 tiles
nameTable += 4;
}
/*
* Mix the two layers into the current line under control of the
* stencil mask, applying scrolling in the process.
*/
// Load horizontal scroll values
if (lineScrollPri)
hScrollPri = hScrollTablePri[y];
else
hScrollPri = hFullScrollPri;
if (lineScrollAlt)
hScrollAlt = hScrollTableAlt[y];
else
hScrollAlt = hFullScrollAlt;
// Mix first 60 tiles (4 at a time)
mask = *maskTable;
i = hScrollPri&511; // primary line index
j = hScrollAlt&511; // alternate line index
for (tx = 0; tx < 60; tx += 4)
{
if ((mask&0x8000)) // copy tiles from primary layer
{
if (i <= (512-32)) // safe to use memcpy for fast blit?
{
memcpy(dest, &lineBufferPri[i], 32*sizeof(UINT32));
i += 32;
dest += 32;
}
else // slow copy, wrap line boundary
{
for (int k = 0; k < 32; k++)
{
i &= 511;
*dest++ = lineBufferPri[i++];
}
}
j += 32; // update alternate pointer as well
}
else // copy tiles from alternate layer
{
if (j <= (512-32))
{
memcpy(dest, &lineBufferAlt[j], 32*sizeof(UINT32));
j += 32;
dest += 32;
}
else
{
for (int k = 0; k < 32; k++)
{
j &= 511;
*dest++ = lineBufferAlt[j++];
}
}
i += 32; // update primary
}
mask <<= 1;
}
// Mix last two tiles
if ((mask&0x8000)) // copy tiles from primary layer
{
for (int k = 0; k < 16; k++)
{
i &= 511;
*dest++ = lineBufferPri[i++];
}
}
else // copy from alternate
{
for (int k = 0; k < 16; k++)
{
j &= 511;
*dest++ = lineBufferAlt[j++];
}
}
// Next line
maskTable += 2; // next mask line
}
// Upload
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 496, 384, GL_RGBA, GL_UNSIGNED_BYTE, surf);
}
/*
* DrawRect():
*
* Draws a rectangular portion of the given layer and uploads it. Scrolling is
* applied but the result must be clipped against the rectangular window
* defined here by tileX, tileY, tileW, and tileH.
*
* Clipping for the right side is not checked, which will always work as long
* as the X and Y tile positions never exceed 61 and 47, respectively (the
* dimensions of the physical display; there is border space because the
* layers are 64x64).
*
* Parameters:
* layerNum Layer number (0 or 1).
* nameTableBase Pointer to the layer's name table.
* tileX Position of upper-left corner of rectangle on the
* screen, in units of 8-pixel tiles (0-61).
* tileY "" (0-47)
* tileW Width of rectangular region in tiles.
* tileH "" (height)
*/
void CRender2D::DrawRect(int layerNum, const UINT16 *nameTableBase, int tileX, int tileY, int tileW, int tileH)
{
UINT32 *dest = surf; // destination surface to write to
const UINT16 *maskTable; // pointer to start of mask table
const UINT16 *hScrollTablePri, *hScrollTableAlt; // pointers to line scroll tables
const UINT16 *nameTablePri = nameTableBase; // primary (this layer) name table
const UINT16 *nameTableAlt = &nameTableBase[64*64]; // alternate layer's name table
const UINT16 *nameTable;
int colorDepthPri, colorDepthAlt; // primary and alternate layer color depths
int hScrollPri, hScrollAlt; // primary and alternate layer scroll offsets
int vScrollPri, vScrollAlt;
int hFullScrollPri, hFullScrollAlt; // full-screen horizontal scroll values (from registers)
int hOffset, vOffset; // pixel offsets
int ntOffset; // offset in name table
int tx;
BOOL lineScrollPri, lineScrollAlt; // line scrolling enable/disable
UINT16 mask;
// Determine layer color depths (1 if 4-bit, 0 if 8-bit)
colorDepthPri = regs[0x20/4] & (1<<(12+layerNum*2));
colorDepthAlt = regs[0x20/4] & (1<<(12+layerNum*2+1));
// Line scroll tables
hScrollTablePri = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400)/4];
hScrollTableAlt = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400+0x400)/4];
// Get correct offset into mask table
maskTable = (UINT16 *) &vram[0xF7000/4];
if (layerNum == 0)
++maskTable; // little endian, layer 0 is second word in each pair
// Load horizontal full-screen scroll values and scroll mode
hFullScrollPri = regs[0x60/4+layerNum*2]&0x3FF;
hFullScrollAlt = regs[0x60/4+layerNum*2+1]&0x3FF;
lineScrollPri = regs[0x60/4+layerNum*2]&0x8000;
lineScrollAlt = regs[0x60/4+layerNum*2+1]&0x8000;
// Load vertical scroll values
vScrollPri = (regs[0x60/4+layerNum*2]>>16)&0x3FF;
vScrollAlt = (regs[0x60/4+layerNum*2+1]>>16)&0x3FF;
// Iterate over actual line on screen
for (int y = tileY*8; y < (tileY+tileH)*8; y++)
{
// Load horizontal scroll values
if (lineScrollPri)
hScrollPri = hScrollTablePri[y];
else
hScrollPri = hFullScrollPri;
if (lineScrollAlt)
hScrollAlt = hScrollTableAlt[y];
else
hScrollAlt = hFullScrollAlt;
/*
* Draw all tiles from primary layer first
*/
// Compute scroll offsets into name table and destination
hOffset = -(hScrollPri&7);
vOffset = (y+vScrollPri)&7;
ntOffset = tileX+hScrollPri/8;
// Advance name table to our line (prior to h-scrolling)
nameTable = &nameTablePri[64*((y+vScrollPri)/8) & 0xFFF]; // clamp to 64x64=0x1000
// Each bit in the mask table corresponds to 4 tiles
mask = maskTable[y*2];
// Render a line!
//TODO: add one if scrolling
for (tx = 0; tx < tileW; tx++)
{
if ( ((mask<<((tileX+tx)/4)) & 0x8000) )
{
if (colorDepthPri)
DrawTileLine4Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset); // make sure ^1 belongs inside parenthesis...
else
DrawTileLine8Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
}
hOffset += 8;
ntOffset++;
}
// When scrolling, extra tile must be rendered at right edge of region
if ( ((mask<<((tileX+tx)/4)) & 0x8000) ) // re-use the last mask bit (mask doesn't scroll)
{
if (colorDepthPri)
DrawTileLine4BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollPri&7);
else
DrawTileLine8BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollPri&7);
}
/*
* Draw the alternate layer wherever the primary layer was masked
*/
hOffset = -(hScrollAlt&7);
vOffset = (y+vScrollAlt)&7;
ntOffset = tileX+hScrollAlt/8;
nameTable = &nameTableAlt[64*((y+vScrollAlt)/8)];
mask = maskTable[y*2];
for (tx = 0; tx < tileW; tx++)
{
if (0 == ((mask<<((tileX+tx)/4)) & 0x8000))
{
if (colorDepthAlt)
DrawTileLine4Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
else
DrawTileLine8Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
}
hOffset += 8;
ntOffset++;
}
// When scrolling, extra tile must be rendered at right edge of region
if (0 == ((mask<<((tileX+tx)/4)) & 0x8000)) // re-use the last mask bit (mask doesn't scroll)
{
if (colorDepthAlt)
DrawTileLine4BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollAlt&7);
else
DrawTileLine8BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollAlt&7);
}
// Next line
dest += tileW*8; // image surface is only as wide as the rectangle we're updating
}
// Upload
glTexSubImage2D(GL_TEXTURE_2D, 0, tileX*8, tileY*8, tileW*8, tileH*8, GL_RGBA, GL_UNSIGNED_BYTE, surf);
}
// Updates any changed portions of a layer
void CRender2D::UpdateLayer(int layerNum)
{
glBindTexture(GL_TEXTURE_2D, texID[layerNum]);
allDirty = TRUE;
if (allDirty)
{
// If everything is dirty, update the whole thing at once
DrawCompleteLayer(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4]);
//DrawRect(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4], 0, 0, 62, 48);
memset(dirty, 0, sizeof(dirty));
}
else
{
// Otherwise, for now, use a dumb approach that updates each rectangle individually
for (int y = 0; y < 64/DIRTY_RECT_HEIGHT; y++)
{
for (int x = 0; x < 48/DIRTY_RECT_WIDTH; x++)
{
if (dirty[layerNum][y][x])
{
DrawRect(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4], x*DIRTY_RECT_WIDTH, y*DIRTY_RECT_HEIGHT, DIRTY_RECT_WIDTH, DIRTY_RECT_HEIGHT);
dirty[layerNum][y][x] = 0; // not dirty anymore
}
}
}
}
}
/******************************************************************************
Frame Display Functions
******************************************************************************/
// Draws a layer to the screen
void CRender2D::DisplayLayer(int layerNum, GLfloat z)
{
glBindTexture(GL_TEXTURE_2D, texID[layerNum]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f/512.0f, 0.0f); glVertex3f(0.0f, 0.0f, z);
glTexCoord2f(496.0f/512.0f, 0.0f); glVertex3f(1.0f, 0.0f, z);
glTexCoord2f(496.0f/512.0f, 384.0f/512.0f); glVertex3f(1.0f, 1.0f, z);
glTexCoord2f(0.0f/512.0f, 384.0f/512.0f); glVertex3f(0.0f, 1.0f, z);
glEnd();
}
// Set up viewport and OpenGL state for 2D rendering (sets up blending function but disables blending)
void CRender2D::Setup2D(void)
{
// Set up the viewport and orthogonal projection
glViewport(xOffs, yOffs, xPixels, yPixels);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, 1.0, 1.0, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Enable texture mapping and blending
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha of 1.0 is opaque, 0 is transparent
glDisable(GL_BLEND);
// Disable Z-buffering
glDisable(GL_DEPTH_TEST);
// Shader program
glUseProgram(shaderProgram);
}
// Convert color offset register data to RGB
void CRender2D::ColorOffset(GLfloat colorOffset[3], UINT32 reg)
{
INT8 ir, ig, ib;
ib = (reg>>16)&0xFF;
ig = (reg>>8)&0xFF;
ir = (reg>>0)&0xFF;
/*
* Uncertain how these should be interpreted. It appears to be signed,
* which means the values range from -128 to +127. The division by 128
* normalizes this to roughly -1,+1.
*/
colorOffset[0] = (GLfloat) ir * (1.0f/128.0f);
colorOffset[1] = (GLfloat) ig * (1.0f/128.0f);
colorOffset[2] = (GLfloat) ib * (1.0f/128.0f);
//printf("%08X -> %g,%g,%g\n", reg, colorOffset[2], colorOffset[1], colorOffset[0]);
}
// Bottom layers
void CRender2D::BeginFrame(void)
{
GLfloat colorOffset[3];
// Update all layers
for (int i = 0; i < 2; i++)
{
UpdateLayer(i);
}
allDirty = FALSE;
// Draw bottom layer
Setup2D();
ColorOffset(colorOffset, regs[0x44/4]);
glUniform3fv(colorOffsetLoc, 1, colorOffset);
DisplayLayer(1, 0.0);
}
// Top layers
void CRender2D::EndFrame(void)
{
GLfloat colorOffset[3];
// Draw top layer
Setup2D();
glEnable(GL_BLEND);
ColorOffset(colorOffset, regs[0x40/4]);
glUniform3fv(colorOffsetLoc, 1, colorOffset);
DisplayLayer(0, -0.5);
}
/******************************************************************************
Emulation Callbacks
******************************************************************************/
void CRender2D::WriteVRAM(unsigned addr, UINT32 data)
{
unsigned color;
UINT8 r, g, b, a;
if (vram[addr/4] == data) // do nothing if no changes
return;
// For now, mark everything as dirty
allDirty = TRUE;
// Palette
if (addr >= 0x100000)
{
color = (addr-0x100000)/4; // color index
a = 0xFF * ((data>>15)&1); // decode the RGBA (make alpha 0xFF or 0x00)
a = ~a; // invert it (set on Model 3 means clear pixel)
if ((data&0x8000))
r = g = b = 0;
else
{
b = (data>>7)&0xF8;
g = (data>>2)&0xF8;
r = (data<<3)&0xF8;
}
pal[color] = (a<<24)|(b<<16)|(g<<8)|r;
}
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void CRender2D::AttachRegisters(const UINT32 *regPtr)
{
regs = regPtr;
DebugLog("Render2D attached registers\n");
}
void CRender2D::AttachVRAM(const UINT8 *vramPtr)
{
vram = (UINT32 *) vramPtr;
DebugLog("Render2D attached VRAM\n");
}
#define MEMORY_POOL_SIZE (512*512*4+0x20000)
BOOL CRender2D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes)
{
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
// Load shaders
if (OKAY != LoadShaderProgram(&shaderProgram,&vertexShader,&fragmentShader,NULL,NULL,vertexShaderSource,fragmentShaderSource))
return FAIL;
// Get locations of the uniforms
glUseProgram(shaderProgram); // bind program
textureMapLoc = glGetUniformLocation(shaderProgram, "textureMap");
glUniform1i(textureMapLoc,0); // attach it to texture unit 0
colorOffsetLoc = glGetUniformLocation(shaderProgram, "colorOffset");
// Allocate memory for layer surfaces and palette
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
if (NULL == memoryPool)
return ErrorLog("Insufficient memory for tile layer surfaces (need %1.1f MB).", memSizeMB);
memset(memoryPool,0,MEMORY_POOL_SIZE);
// Set up pointers to memory regions
surf = (UINT32 *) memoryPool;
pal = (UINT32 *) &memoryPool[512*512*4];
// Resolution
xPixels = xRes;
yPixels = yRes;
xOffs = xOffset;
yOffs = yOffset;
// Clear textures and dirty rectangles (all memory)
memset(memoryPool, 0, MEMORY_POOL_SIZE);
memset(dirty, 0, sizeof(dirty));
allDirty = TRUE;
// Create textures
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(2, texID);
for (int i = 0; i < 2; i++)
{
glBindTexture(GL_TEXTURE_2D, texID[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf);
if (glGetError() != GL_NO_ERROR)
return ErrorLog("OpenGL was unable to provide 512x512-texel texture maps for tile map layers.");
}
DebugLog("Render2D initialized (allocated %1.1f MB)\n", memSizeMB);
return OKAY;
}
CRender2D::CRender2D(void)
{
xPixels = 496;
yPixels = 384;
xOffs = 0;
yOffs = 0;
memoryPool = NULL;
vram = NULL;
surf = NULL;
DebugLog("Built Render2D\n");
}
CRender2D::~CRender2D(void)
{
DestroyShaderProgram(shaderProgram,vertexShader,fragmentShader);
glDeleteTextures(2, texID);
if (memoryPool != NULL)
{
delete [] memoryPool;
memoryPool = NULL;
}
surf = NULL;
vram = NULL;
DebugLog("Destroyed Render2D\n");
}

177
Graphics/Render2D.h Normal file
View file

@ -0,0 +1,177 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Render2D.h
*
* Header file defining the CRender2D class: OpenGL tile generator graphics.
*/
#ifndef INCLUDED_RENDER2D_H
#define INCLUDED_RENDER2D_H
#include "Pkgs/glew.h"
// Dirty rectangle size, must be multiples of 4 because masking code relies on this
#define DIRTY_RECT_WIDTH 8 // width of a dirty rectangle in 8-pixel tiles (multiples of 4, divisible into 64)
#define DIRTY_RECT_HEIGHT 8 // height (multiples of 4, divisible into 48)
/*
* CRender2D:
*
* Tile generator graphics engine. This must be constructed and initialized
* before being attached to any objects that want to make use of it. Apart from
* the constructor, all members assume that a global GL device
* context is available and that GL functions may be called.
*/
class CRender2D
{
public:
/*
* BeginFrame(void):
*
* Prepare to render a new frame. Must be called once per frame prior to
* drawing anything.
*/
void BeginFrame(void);
/*
* EndFrame(void):
*
* Signals the end of rendering for this frame. Must be called last during
* the frame.
*/
void EndFrame(void);
/*
* WriteVRAM(addr, data):
*
* Indicates what will be written next to the tile generator's RAM. The
* VRAM address must not have yet been updated, to allow the renderer to
* check for changes. Data is accepted in the same form as the tile
* generator: the MSB is what was written to addr+3. This function is
* intended to facilitate on-the-fly decoding of tiles and palette data.
*
* Parameters:
* addr Address in tile generator RAM. Caller must ensure it is
* clamped to the range 0x000000 to 0x11FFFF because this
* function does not.
* data The data to write.
*/
void WriteVRAM(unsigned addr, UINT32 data);
/*
* AttachRegisters(regPtr):
*
* Attaches tile generator registers. This must be done prior to any
* rendering otherwise the program may crash with an access violation.
*
* Parameters:
* regPtr Pointer to the base of the tile generator registers.
* There are assumed to be 64 in all.
*/
void AttachRegisters(const UINT32 *regPtr);
/*
* AttachVRAM(vramPtr):
*
* Attaches tile generator RAM. This must be done prior to any rendering
* otherwise the program may crash with an access violation.
*
* Parameters:
* vramPtr Pointer to the base of the tile generator RAM (0x120000
* bytes). VRAM is assumed to be in little endian format.
*/
void AttachVRAM(const UINT8 *vramPtr);
/*
* Init(xOffset, yOffset, xRes, yRes);
*
* One-time initialization of the context. Must be called before any other
* members (meaning it should be called even before being attached to any
* other objects that want to use it).
*
* Parameters:
* xOffset X offset within OpenGL display surface in pixels.
* yOffset Y offset.
* xRes Horizontal resolution of display surface in pixels.
* yRes Vertical resolution.
*
* Returns:
* OKAY is successful, otherwise FAILED if a non-recoverable error
* occurred. Prints own error messages.
*/
BOOL Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes);
/*
* CRender2D(void):
* ~CRender2D(void):
*
* Constructor and destructor.
*/
CRender2D(void);
~CRender2D(void);
private:
// Private member functions
void DrawTileLine8BitNoClip(UINT32 *buf, UINT16 tile, int tileLine);
void DrawTileLine4BitNoClip(UINT32 *buf, UINT16 tile, int tileLine);
void DrawTileLine4Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine);
void DrawTileLine4BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels);
void DrawTileLine8Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine);
void DrawTileLine8BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels);
void DrawCompleteLayer(int layerNum, const UINT16 *nameTableBase);
void DrawRect(int layerNum, const UINT16 *nameTableBase, int tileX, int tileY, int tileW, int tileH);
void UpdateLayer(int layerNum);
void DisplayLayer(int layerNum, GLfloat z);
void Setup2D(void);
void ColorOffset(GLfloat colorOffset[3], UINT32 reg);
// Data received from tile generator device object
const UINT32 *vram;
const UINT32 *regs;
// OpenGL data
GLuint texID[2]; // IDs for the 2 layer textures
unsigned xPixels, yPixels; // display surface resolution
unsigned xOffs, yOffs; // offset
// Shader programs and input data locations
GLuint shaderProgram; // shader program object
GLuint vertexShader; // vertex shader handle
GLuint fragmentShader; // fragment shader
GLuint textureMapLoc; // location of "textureMap" uniform
GLuint colorOffsetLoc; // uniform
// Dirty rectangles (non-zero indicates region is dirty)
UINT8 dirty[2][64/DIRTY_RECT_HEIGHT][48/DIRTY_RECT_WIDTH];
BOOL allDirty; // global dirty flag (forces everything to be updated)
// Buffers
UINT8 *memoryPool; // all memory is allocated here
UINT32 *surf; // 512x512x32bpp pixel surface
UINT32 *pal; // 0x20000 byte (32K colors) palette
};
#endif // INCLUDED_RENDER2D_H

1388
Graphics/Render3D.cpp Normal file

File diff suppressed because it is too large Load diff

412
Graphics/Render3D.h Normal file
View file

@ -0,0 +1,412 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Render3D.h
*
* Header file defining the CRender3D class: OpenGL Real3D graphics engine.
*/
#ifndef INCLUDED_RENDER3D_H
#define INCLUDED_RENDER3D_H
#include "Pkgs/glew.h"
/******************************************************************************
Internal Definitions and Data Structures
NOTE: These should probably be moved inside the Render3D namespace at some
point.
******************************************************************************/
// Model caches sort models by alpha (translucency) state
enum POLY_STATE
{
POLY_STATE_NORMAL = 0,
POLY_STATE_ALPHA
};
struct Vertex
{
GLfloat x,y,z; // vertex
GLfloat n[3]; // normal X, Y, Z
GLfloat u,v; // texture U, V coordinates (in texels, relative to selected texture)
};
struct Poly
{
Vertex Vert[4];
GLfloat n[3]; // polygon normal (used for backface culling)
POLY_STATE state; // alpha or normal?
unsigned numVerts; // triangle (3) or quad (4)
const UINT32 *header; // pointer to Real3D 7-word polygon header
};
// References to model polygons stored in a VBO
struct VBORef
{
unsigned index[2]; // index of model polygons in VBO
unsigned numVerts[2]; // number of vertices
unsigned lutIdx; // LUT index associated with this model (for fast LUT clearing)
};
// Display list items: model instances and viewport settings
struct DisplayList
{
BOOL isViewport; // if true, this is a viewport node
union
{
// Viewport data
struct
{
GLfloat projectionMatrix[4*4]; // projection matrix
GLfloat lightingParams[6]; // lighting parameters (see RenderViewport() and vertex shader)
GLfloat spotEllipse[4]; // spotlight ellipse (see RenderViewport())
GLfloat spotRange[2]; // Z range
GLfloat spotColor[3]; // color
GLfloat fogParams[5]; // fog parameters (...)
GLint x, y; // viewport coordinates (scaled and in OpenGL format)
GLint width, height; // viewport dimensions (scaled for display surface size)
} Viewport;
// Model data
struct
{
GLfloat modelViewMatrix[4*4]; // model-view matrix
unsigned index; // index in VBO
unsigned numVerts; // number of vertices
} Model;
} Data;
DisplayList *next; // next display list item with the same state (alpha or non-alpha)
};
/*
* ModelCache:
*
* A model cache tracks all models in a particular region (ie., VROM or polygon
* RAM). It contains a look-up table to quickly obtain VBO indices. Be careful
* when accessing the LUT, there are some special cases.
*
* If the model cache is marked dynamic, cached models may not necessarily be
* retained. Clearing the model cache is also much faster. The LUT entry for
* the last model cached will be valid, but because the LUT may not be
* cleared, one cannot assume a model exists because there is a LUT entry
* pointing to it. Always use NeedToCache() to determine whether caching is
* necessary before reading the LUT!
*/
struct ModelCache
{
// Cache type
BOOL dynamic;
// Vertex buffer object
unsigned vboMaxOffset; // size of VBO (in bytes)
unsigned vboCurOffset; // current offset in VBO (in bytes)
GLuint vboID; // OpenGL VBO handle
// Local vertex buffers (enough for a single model)
unsigned maxVertIdx; // size of each local vertex buffer (in vertices)
unsigned curVertIdx[2]; // current vertex index (in vertices)
GLfloat *verts[2];
// Array of cached models
unsigned maxModels; // maximum number of models
unsigned numModels; // current number stored
VBORef *Models;
/*
* Look-Up Table:
*
* Can be accessed directly with a LUT index to determine the model index.
* However, it should not be used to determine whether a model needs to be
* cached. Use NeedToCache() instead. A valid index, for example, may still
* have to be re-cached if the model cache is dynamic (polygon RAM).
*/
unsigned lutSize; // number of elements in LUT
INT16 *lut; // stores indices into Models[] or -1 if not yet cached
// Display list
unsigned maxListSize; // maximum number of display list items
unsigned listSize; // number of items in display list
DisplayList *List; // holds all display list items
DisplayList *ListHead[2]; // heads of linked lists for each state
DisplayList *ListTail[2]; // current tail node for each state
};
/******************************************************************************
CRender3D Class Definition
******************************************************************************/
/*
* CRender3D:
*
* 3D renderer. Lots of work to do here :)
*/
class CRender3D
{
public:
/*
* RenderFrame(void):
*
* Renders the complete scene database. Must be called between BeginFrame() and
* EndFrame(). This function traverses the scene database and builds up display
* lists.
*/
void RenderFrame(void);
/*
* BeginFrame(void):
*
* Prepare to render a new frame. Must be called once per frame prior to
* drawing anything.
*/
void BeginFrame(void);
/*
* EndFrame(void):
*
* Signals the end of rendering for this frame. Must be called last during
* the frame.
*/
void EndFrame(void);
/*
* UploadTextures(x, y, width, height):
*
* Signals that a portion of texture RAM has been updated.
*
* Parameters:
* x X position within texture RAM.
* y Y position within texture RAM.
* width Width of texture data in texels.
* height Height.
*/
void UploadTextures(unsigned x, unsigned y, unsigned width, unsigned height);
/*
* AttachMemory(cullingRAMLoPtr, cullingRAMHiPtr, polyRAMPtr, vromPtr,
* textureRAMPtr):
*
* Attaches RAM and ROM areas. This must be done prior to any rendering
* otherwise the program may crash with an access violation.
*
* Parameters:
* cullingRAMLoPtr Pointer to low culling RAM (4 MB).
* cullingRAMHiPtr Pointer to high culling RAM (1 MB).
* polyRAMPtr Pointer to polygon RAM (4 MB).
* vromPtr Pointer to video ROM (64 MB).
* textureRAMPtr Pointer to texture RAM (8 MB).
*/
void AttachMemory(const UINT32 *cullingRAMLoPtr,
const UINT32 *cullingRAMHiPtr, const UINT32 *polyRAMPtr,
const UINT32 *vromPtr, const UINT16 *textureRAMPtr);
/*
* SetStep(stepID):
*
* Sets the Model 3 hardware stepping, which also determines the Real3D
* functionality. The default is Step 1.0. This should be called prior to
* any other emulation functions and after Init().
*
* Parameters:
* stepID 0x10 for Step 1.0, 0x15 for Step 1.5, 0x20 for Step 2.0,
* or 0x21 for Step 2.1. Anything else defaults to 1.0.
*/
void SetStep(int stepID);
/*
* Init(xOffset, yOffset, xRes, yRes, vsFile, fsFile):
*
* One-time initialization of the context. Must be called before any other
* members (meaning it should be called even before being attached to any
* other objects that want to use it).
*
* Parameters:
* xOffset X offset of display surface in pixels (in case resolution
* is smaller than the true display surface).
* yOffset Y offset.
* xRes Horizontal resolution of display surface in pixels.
* yRes Vertical resolution.
* vsFile External vertex shader path. If NULL, the internal
* shader is used.
* fsFile External fragment shader path. If NULL, the internal
* shader is used.
*
* Returns:
* OKAY is successful, otherwise FAILED if a non-recoverable error
* occurred. Any allocated memory will not be freed until the
* destructor is called. Prints own error messages.
*/
BOOL Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes,
const char *vsFile, const char *fsFile);
/*
* CRender3D(void):
* ~CRender3D(void):
*
* Constructor and destructor.
*/
CRender3D(void);
~CRender3D(void);
private:
/*
* Private Members
*/
// Real3D address translation
const UINT32 *TranslateCullingAddress(UINT32 addr);
const UINT32 *TranslateModelAddress(UINT32 addr);
// Model caching and display list management
void DrawDisplayList(ModelCache *Cache, POLY_STATE state);
BOOL AppendDisplayList(ModelCache *Cache, BOOL isViewport, int modelNum);
void ClearDisplayList(ModelCache *Cache);
BOOL InsertPolygon(ModelCache *cache, const Poly *p);
void InsertVertex(ModelCache *cache, const Vertex *v, const Poly *p, float normFlip);
BOOL BeginModel(ModelCache *cache);
void EndModel(ModelCache *cache, int lutIdx);
BOOL CacheModel(ModelCache *cache, int lutIdx, const UINT32 *data);
BOOL NeedToCache(ModelCache *cache, int lutIdx);
void ClearModelCache(ModelCache *cache);
BOOL CreateModelCache(ModelCache *cache, unsigned vboMaxVerts, unsigned localMaxVerts, unsigned maxNumModels, unsigned numLUTEntries, unsigned displayListSize, BOOL isDynamic);
void DestroyModelCache(ModelCache *cache);
// Texture management
void DecodeTexture(int format, int x, int y, int width, int height);
// Stack management
void MultMatrix(UINT32 matrixOffset);
void InitMatrixStack(UINT32 matrixBaseAddr);
void Push(UINT32 ptr, BOOL pushMatrix);
UINT32 Pop(void);
void ClearStack(void);
// Scene database traversal
BOOL DrawModel(UINT32 modelAddr);
void DescendCullingNode(UINT32 addr);
void DescendPointerList(UINT32 addr);
void DescendNodePtr(UINT32 nodeAddr);
void StackMachine(UINT32 nodeAddr);
void RenderViewport(UINT32 addr, int pri);
// In-frame error reporting
BOOL ErrorLocalVertexOverflow(void);
BOOL ErrorUnableToCacheModel(UINT32 modelAddr);
void ClearErrors(void);
/*
* Data
*/
// Stepping
int step;
int offset; // offset to subtract for words 3 and higher of culling nodes
GLfloat vertexFactor; // fixed-point conversion factor for vertices
// Memory (passed from outside)
const UINT32 *cullingRAMLo; // 4 MB
const UINT32 *cullingRAMHi; // 1 MB
const UINT32 *polyRAM; // 4 MB
const UINT32 *vrom; // 64 MB
const UINT16 *textureRAM; // 8 MB
// Error reporting
unsigned errorMsgFlags; // tracks which errors have been printed this frame
// Real3D Base Matrix Pointer
const float *matrixBasePtr;
// Processing stack (experimental)
UINT32 *stack;
int stackSize; // number of elements stack can contain
int stackTop; // current top of stack (free spot, last element is stackTop-1)
BOOL stackOverflow; // records stack overflows (cleared by ClearStack())
// Current viewport parameters (updated as viewports are traversed)
GLfloat lightingParams[6];
GLfloat fogParams[5];
GLfloat spotEllipse[4];
GLfloat spotRange[2];
GLfloat spotColor[3];
GLint viewportX, viewportY;
GLint viewportWidth, viewportHeight;
// Scene graph stack
int listDepth; // how many lists have we recursed into
int stackDepth; // for debugging and error handling purposes
// Resolution scaling factors (to support resolutions higher than 496x384) and offsets
GLfloat xRatio, yRatio;
unsigned xOffs, yOffs;
// Texture ID for complete 2048x2048 texture map
GLuint texID;
// Shader programs and input data locations
GLuint shaderProgram; // shader program object
GLuint vertexShader; // vertex shader handle
GLuint fragmentShader; // fragment shader
GLuint textureMapLoc; // location of "textureMap" uniform
GLuint modelViewMatrixLoc; // uniform
GLuint projectionMatrixLoc; // uniform
GLuint lightingLoc; // uniform
GLuint spotEllipseLoc; // uniform
GLuint spotRangeLoc; // uniform
GLuint spotColorLoc; // uniform
GLuint subTextureLoc; // attribute
GLuint texParamsLoc; // attribute
GLuint texFormatLoc; // attribute
GLuint transLevelLoc; // attribute
GLuint lightEnableLoc; // attribute
GLuint fogIntensityLoc; // attribute
// Model caching
ModelCache VROMCache; // VROM (static) models
ModelCache PolyCache; // polygon RAM (dynamic) models
/*
* Texture Format Buffer
*
* Records the format that a texture (at a given location within the
* texture sheet) is currently stored in. A negative value indicates the
* texture has not been accessed and converted yet and non-negative values
* correspond to the texture format bits in the polygon headers. They can
* be used to determine whether a texture needs to be updated.
*/
int textureWidth[2048/32][2048/32];
int textureHeight[2048/32][2048/32];
INT8 textureFormat[2048/32][2048/32];
/*
* Texture Decode Buffer
*
* Textures are decoded and copied from texture RAM into this temporary buffer
* before being uploaded. Dimensions are 512x512.
*/
GLfloat *textureBuffer; // RGBA8 format
};
#endif // INCLUDED_RENDER3D_H

182
Graphics/Shader.cpp Normal file
View file

@ -0,0 +1,182 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Shader.cpp
*
* OpenGL shader management.
*
* To-Do List
* ----------
* - Mesa crashes because, evidently, the function pointers are invalid. Mesa
* returns the following information:
* Vendor: Mesa project: www.mesa3d.org
* Renderer: Mesa GLX Indirect
* Version: 1.2 (1.5 Mesa 6.5.1)
* Shading Language Version: (null)
* Maximum Vertex Array Size: -1 vertices
* Maximum Texture Size: 2048 texels
* Maximum Vertex Attributes: 16
* Maximum Vertex Uniforms: 16
* - Check for OpenGL 2.0 and perhaps check some of the function pointers,
* which will be NULL, if GL 2.0 and shaders are not supported.
* - Keep in mind that all these checks should probably go somewhere else...
* - Turn this into a class.
*/
#include <new>
#include <stdio.h>
#include "Pkgs/glew.h"
#include "Supermodel.h"
// Load a source file. Pointer returned must be freed by caller. Returns NULL if failed.
static char *LoadShaderSource(const char *file)
{
FILE *fp;
char *buf;
int size;
// Open shader and get the file size
fp = fopen(file, "r");
if (NULL == fp)
{
ErrorLog("Unable to open shader source file: %s", file);
return NULL;
}
fseek(fp, 0, SEEK_END);
size = ftell(fp);
rewind(fp);
// Allocate memory and read it in
buf = new(std::nothrow) char[size+1];
if (NULL == buf)
{
ErrorLog("Insufficient memory to load shader source file: %s", file);
fclose(fp);
return NULL;
}
buf[size] = '\0'; // for safety, actual size might be smaller once newline characters are converted
size = fread(buf, sizeof(char), size, fp);
buf[size] = '\0';
fclose(fp);
return buf;
}
BOOL LoadShaderProgram(GLuint *shaderProgramPtr, GLuint *vertexShaderPtr, GLuint *fragmentShaderPtr, const char *vsFile, const char *fsFile, const char *vsString, const char *fsString)
{
char msg[2048+128], infoLog[2048];
const char *vsSource, *fsSource; // source code
GLuint shaderProgram, vertexShader, fragmentShader;
GLint result, len;
BOOL ret = OKAY;
// Load shaders from files if specified
if (vsFile != NULL)
vsSource = LoadShaderSource(vsFile);
else
vsSource = vsString;
if (fsFile != NULL)
fsSource = LoadShaderSource(fsFile);
else
fsSource = fsString;
if (vsSource == NULL || fsSource == NULL)
{
ret = FAIL;
goto Quit;
}
// Ensure that shader support exists
if ((glCreateProgram==NULL) || (glCreateShader==NULL) || (glShaderSource==NULL) || (glCompileShader==NULL))
{
ret = FAIL;
ErrorLog("OpenGL 2.0 does not appear to be present. Unable to proceed.");
goto Quit;
}
// Create the shaders and shader program
shaderProgram = glCreateProgram();
vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
*shaderProgramPtr = shaderProgram;
*vertexShaderPtr = vertexShader;
*fragmentShaderPtr = fragmentShader;
// Attempt to compile vertex shader
glShaderSource(vertexShader, 1, (const GLchar **) &vsSource, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
if (!result) // failed to compile
{
glGetShaderInfoLog(vertexShader, 2048, &len, infoLog);
ErrorLog("Vertex shader failed to compile. Compiler says:\n%s", infoLog);
ret = FAIL; // error
}
// Attempt to compile fragment shader
glShaderSource(fragmentShader, 1, (const GLchar **) &fsSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
if (!result) // failed to compile
{
glGetShaderInfoLog(fragmentShader, 2048, &len, infoLog);
ErrorLog("Fragment shader failed to compile. Compiler says:\n%s", infoLog);
fprintf(stderr, msg);
ret = FAIL; // error
}
// Link
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &result);
if (result == GL_FALSE)
{
glGetProgramInfoLog(shaderProgram, 2048, &len, infoLog);
ErrorLog("Failed to link shader objects. Linker says:\n%s\n", infoLog);
ret = FAIL; // error
}
// Enable the shader (if no errors)
if (ret == OKAY)
glUseProgram(shaderProgram);
// Clean up and quit
Quit:
if ((vsSource != NULL) && (vsFile != NULL)) // loaded from file, must delete
delete [] vsSource;
if ((fsSource != NULL) && (fsFile != NULL)) // ""
delete [] fsSource;
return ret;
}
void DestroyShaderProgram(GLuint shaderProgram, GLuint vertexShader, GLuint fragmentShader)
{
// In case LoadShaderProgram() failed above due to lack of OpenGL 2.0+ functions...
if ((glUseProgram==NULL) || (glDeleteShader==NULL) || (glDeleteProgram==NULL))
return;
glUseProgram(0); // return to fixed function pipeline
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteProgram(shaderProgram);
}

73
Graphics/Shader.h Normal file
View file

@ -0,0 +1,73 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Shader.h
*
* OpenGL shader management functions.
*/
#ifndef INCLUDED_SHADER_H
#define INCLUDED_SHADER_H
#include "Pkgs/glew.h"
/*
* LoadShaderProgram(shaderProgramPtr, vertexShaderPtr, fragmentShaderPtr,
* vsFile, fsFile, vsString, fsString):
*
* Loads and creates an OpenGL shader program.
*
* Parameters:
* shaderProgramPtr Pointer to where OpenGL shader program handle is
* written.
* vertexShaderPtr Pointer to where vertex shader handle is written.
* fragmentShaderPtr Pointer for fragment shader.
* vsFile Vertex shader file path. If this is not NULL, the
* vertex shader is loaded from the file.
* fsFile Fragment shader file path. If this is not NULL, the
* fragment shader is loaded from the file.
* vsString String containing the vertex shader.
* fsString String containing the fragment shader.
*
* Returns:
* OKAY is successfully loaded, otherwise FAIL. Prints own error messages.
*/
extern BOOL LoadShaderProgram(GLuint *shaderProgramPtr, GLuint *vertexShaderPtr,
GLuint *fragmentShaderPtr, const char *vsFile,
const char *fsFile, const char *vsString,
const char *fsString);
/*
* DestroyShaderProgram(shaderProgram, vertexShader, fragmentShader):
*
* Removes shaders and returns to the fixed function OpenGL pipeline.
*
* Parameters:
* shaderProgram Handle for shader program.
* vertexShader Handle for vertex shader.
* fragmentShader Handle for fragment shader.
*/
extern void DestroyShaderProgram(GLuint shaderProgram, GLuint vertexShader,
GLuint fragmentShader);
#endif // INCLUDED_SHADER_H

2
Graphics/Shaders/DIR.txt Normal file
View file

@ -0,0 +1,2 @@
Shader source files go here. Completed versions of the shader files should be
copied into Src/Graphics/Shaders3D.h and Src/Graphics/Shaders2D.h.

View file

@ -0,0 +1,194 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Fragment.glsl
*
* Fragment shader for 3D rendering.
*/
#version 120
// Global uniforms
uniform sampler2D textureMap; // complete texture map, 2048x2048 texels
uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (screen coordinates), .y=Y position, .z=half-width, .w=half-height)
uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit
uniform vec3 spotColor; // spotlight RGB color
// Inputs from vertex shader
varying vec4 fsSubTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels)
varying vec4 fsTexParams; // .x=texture enable (if 1, else 0), .y=use transparency (if > 0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode
varying float fsTexFormat; // .x=T1RGB5 contour texture (if > 0)
varying float fsTransLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque)
varying vec3 fsLightIntensity; // lighting intensity
varying float fsFogFactor; // fog factor
varying float fsViewZ; // Z distance to fragment from viewpoint at origin
/*
* WrapTexelCoords():
*
* Computes the normalized OpenGL S,T coordinates within the 2048x2048 texture
* sheet, taking into account wrapping behavior.
*
* Computing normalized OpenGL texture coordinates (0 to 1) within the
* Real3D texture sheet:
*
* If the texture is not mirrored, we simply have to clamp the
* coordinates to fit within the texture dimensions, add the texture
* X, Y position to select the appropriate one, and normalize by 2048
* (the dimensions of the Real3D texture sheet).
*
* = [(u,v)%(w,h)+(x,y)]/(2048,2048)
*
* If mirroring is enabled, textures are mirrored every odd multiple of
* the original texture. To detect whether we are in an odd multiple,
* simply divide the coordinate by the texture dimension and check
* whether the result is odd. Then, clamp the coordinates as before but
* subtract from the last texel to mirror them:
*
* = [M*((w-1,h-1)-(u,v)%(w,h)) + (1-M)*(u,v)%(w,h) + (x,y)]/(2048,2048)
* where M is 1.0 if the texture must be mirrored.
*
* As an optimization, this function computes TWO texture coordinates
* simultaneously. The first is texCoord.xy, the second is in .zw. The other
* parameters must have .xy = .zw.
*/
vec4 WrapTexelCoords(vec4 texCoord, vec4 texOffset, vec4 texSize, vec4 mirrorEnable)
{
vec4 clampedCoord, mirror, glTexCoord;
clampedCoord = mod(texCoord,texSize); // clamp coordinates to within texture size
mirror = mirrorEnable * mod(floor(texCoord/texSize),2.0); // whether this texel needs to be mirrored
glTexCoord = ( mirror*(texSize-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
/*
glTexCoord = ( mirror*(texSize-vec4(1.0,1.0,1.0,1.0)-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
*/
return glTexCoord;
}
/*
* main():
*
* Fragment shader entry point.
*/
void main(void)
{
vec4 uv_top, uv_bot, c[4];
vec2 r;
vec4 fragColor;
vec2 ellipse;
vec3 lightIntensity;
float insideSpot;
// Get polygon color for untextured polygons (textured polygons will overwrite)
if (fsTexParams.x==0.0)
fragColor = gl_Color;
else
// Textured polygons: set fragment color to texel value
{
/*
* Bilinear Filtering
*
* In order to get this working on ATI, the number of operations is
* reduced by putting everything into vec4s. uv_top holds the UV
* coordinates for the top two texels (.xy=left, .zw=right) and uv_bot
* is for the lower two.
*/
// Compute fractional blending factor, r, and lower left corner of texel 0
uv_bot.xy = gl_TexCoord[0].st-vec2(0.5,0.5); // move into the lower left blending texel
r = uv_bot.xy-floor(uv_bot.xy); // fractional part
uv_bot.xy = floor(uv_bot.xy); // integral part
// Compute texel coordinates
uv_bot.xy += vec2(0.5,0.5); // offset to center of pixel (should not be needed but it fixes a lot of glitches, esp. on Nvidia)
uv_bot.zw = uv_bot.xy + vec2(1.0,0.0); // compute coordinates of the other three neighbors
uv_top = uv_bot + vec4(0.0,1.0,0.0,1.0);
// Compute the properly wrapped texel coordinates
uv_top = WrapTexelCoords(uv_top,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw));
uv_bot = WrapTexelCoords(uv_bot,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw));
// Fetch the texels
c[0]=texture2D(textureMap,uv_bot.xy); // bottom-left (base texel)
c[1]=texture2D(textureMap,uv_bot.zw); // bottom-right
c[2]=texture2D(textureMap,uv_top.xy); // top-left
c[3]=texture2D(textureMap,uv_top.zw); // top-right
// Interpolate texels and blend result with material color to determine final (unlit) fragment color
// fragColor = (c[0]*(1.0-r.s)*(1.0-r.t) + c[1]*r.s*(1.0-r.t) + c[2]*(1.0-r.s)*r.t + c[3]*r.s*r.t);
// Faster method:
c[0] += (c[1]-c[0])*r.s; // 2 alu
c[2] += (c[3]-c[2])*r.s; // 2 alu
fragColor = c[0]+(c[2]-c[0])*r.t; //2 alu
/*
* T1RGB5:
*
* The transparency bit determines whether to discard pixels (if set).
* What is unknown is how this bit behaves when interpolated. OpenGL
* processes it as an alpha value, so it might concievably be blended
* with neighbors. Here, an arbitrary threshold is chosen.
*
* To-do: blending could probably enabled and this would work even
* better with a hard threshold.
*
* Countour processing also seems to be enabled for RGBA4 textures.
* When the alpha value is 0.0 (or close), pixels are discarded
* entirely.
*/
if (fsTexParams.y > 0.0) // contour processing enabled
{
if (fragColor.a < 0.01) // discard anything with alpha == 0
discard;
}
// If contour texture and not discarded, force alpha to 1.0 because will later be modified by polygon translucency
if (fsTexFormat > 0.0) // contour (T1RGB5) texture map
fragColor.a = 1.0;
}
// Compute spotlight and apply lighting
ellipse = (gl_FragCoord.xy-spotEllipse.xy)/spotEllipse.zw;
insideSpot = dot(ellipse,ellipse);
if ((insideSpot <= 1.0) && (fsViewZ>=spotRange.x) && (fsViewZ<spotRange.y))
lightIntensity = min(fsLightIntensity+(1.0-insideSpot)*spotColor,1.0);
else
lightIntensity = fsLightIntensity;
fragColor.rgb *= lightIntensity;
// Translucency (modulates existing alpha channel for RGBA4 texels)
fragColor.a *= fsTransLevel;
// Apply fog under the control of fog factor setting from polygon header
fragColor.rgb = mix(gl_Fog.color.rgb, fragColor.rgb, fsFogFactor );
// Store final color
gl_FragColor = fragColor;
}

View file

@ -0,0 +1,44 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Fragment2D.glsl
*
* Fragment shader for 2D tilemap rendering.
*/
#version 120
// Global uniforms
uniform sampler2D textureMap; // 512x512 layer surface
uniform vec3 colorOffset; // color offset for this layer
/*
* main():
*
* Fragment shader entry point.
*/
void main(void)
{
gl_FragColor = texture2D(textureMap, gl_TexCoord[0].st);
gl_FragColor.rgb = clamp(gl_FragColor.rgb+colorOffset,0.0,1.0);
}

View file

@ -0,0 +1,142 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Fragment_NoSpotlight.glsl
*
* Fragment shader for 3D rendering. Spotlight effect removed. Fixes fragment
* shader link errors on older ATI Radeon GPUs.
*
* To load external fragment shaders, use the -frag-shader=<file> option when
* starting Supermodel.
*/
#version 120
// Global uniforms
uniform sampler2D textureMap; // complete texture map, 2048x2048 texels
uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (screen coordinates), .y=Y position, .z=half-width, .w=half-height)
uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit
uniform vec3 spotColor; // spotlight RGB color
// Inputs from vertex shader
varying vec4 fsSubTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels)
varying vec4 fsTexParams; // .x=texture enable (if 1, else 0), .y=use transparency (if > 0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode
varying float fsTexFormat; // .x=T1RGB5 contour texture (if > 0)
varying float fsTransLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque)
varying vec3 fsLightIntensity; // lighting intensity
varying float fsFogFactor; // fog factor
varying float fsViewZ; // Z distance to fragment from viewpoint at origin
/*
* WrapTexelCoords():
*
* Computes the normalized OpenGL S,T coordinates within the 2048x2048 texture
* sheet, taking into account wrapping behavior.
*
* Computing normalized OpenGL texture coordinates (0 to 1) within the
* Real3D texture sheet:
*
* If the texture is not mirrored, we simply have to clamp the
* coordinates to fit within the texture dimensions, add the texture
* X, Y position to select the appropriate one, and normalize by 2048
* (the dimensions of the Real3D texture sheet).
*
* = [(u,v)%(w,h)+(x,y)]/(2048,2048)
*
* If mirroring is enabled, textures are mirrored every odd multiple of
* the original texture. To detect whether we are in an odd multiple,
* simply divide the coordinate by the texture dimension and check
* whether the result is odd. Then, clamp the coordinates as before but
* subtract from the last texel to mirror them:
*
* = [M*((w-1,h-1)-(u,v)%(w,h)) + (1-M)*(u,v)%(w,h) + (x,y)]/(2048,2048)
* where M is 1.0 if the texture must be mirrored.
*
* As an optimization, this function computes TWO texture coordinates
* simultaneously. The first is texCoord.xy, the second is in .zw. The other
* parameters must have .xy = .zw.
*/
vec4 WrapTexelCoords(vec4 texCoord, vec4 texOffset, vec4 texSize, vec4 mirrorEnable)
{
vec4 clampedCoord, mirror, glTexCoord;
clampedCoord = mod(texCoord,texSize); // clamp coordinates to within texture size
mirror = mirrorEnable * mod(floor(texCoord/texSize),2.0); // whether this texel needs to be mirrored
glTexCoord = ( mirror*(texSize-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
/*
glTexCoord = ( mirror*(texSize-vec4(1.0,1.0,1.0,1.0)-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
*/
return glTexCoord;
}
/*
* main():
*
* Fragment shader entry point.
*/
void main(void)
{
vec4 uv_top, uv_bot, c[4];
vec2 r;
vec4 fragColor;
vec2 ellipse;
vec3 lightIntensity;
float insideSpot;
// Get polygon color for untextured polygons (textured polygons will overwrite)
if (fsTexParams.x==0.0)
fragColor = gl_Color;
else
// Textured polygons: set fragment color to texel value
{
fragColor = texture2D(textureMap,(fsSubTexture.xy+fsSubTexture.zw/2.0)/2048.0);
//fragColor += texture2D(textureMap,(fsSubTexture.xy+fsSubTexture.zw))/2048.0);
}
// Compute spotlight and apply lighting
ellipse = (gl_FragCoord.xy-spotEllipse.xy)/spotEllipse.zw;
insideSpot = dot(ellipse,ellipse);
if ((insideSpot <= 1.0) && (fsViewZ>=spotRange.x) && (fsViewZ<spotRange.y))
lightIntensity = min(fsLightIntensity+(1.0-insideSpot)*spotColor,1.0);
else
lightIntensity = fsLightIntensity;
fragColor.rgb *= lightIntensity;
fragColor.rgb *= fsLightIntensity;
// Translucency (modulates existing alpha channel for RGBA4 texels)
fragColor.a *= fsTransLevel;
// Apply fog under the control of fog factor setting from polygon header
fragColor.rgb = mix(gl_Fog.color.rgb, fragColor.rgb, fsFogFactor );
// Store final color
gl_FragColor = fragColor;
}

View file

@ -0,0 +1,201 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Fragment_NoSpotlight.glsl
*
* Fragment shader for 3D rendering. Spotlight effect removed. Fixes fragment
* shader link errors on older ATI Radeon GPUs.
*
* To load external fragment shaders, use the -frag-shader=<file> option when
* starting Supermodel.
*/
#version 120
// Global uniforms
uniform sampler2D textureMap; // complete texture map, 2048x2048 texels
uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (screen coordinates), .y=Y position, .z=half-width, .w=half-height)
uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit
uniform vec3 spotColor; // spotlight RGB color
// Inputs from vertex shader
varying vec4 fsSubTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels)
varying vec4 fsTexParams; // .x=texture enable (if 1, else 0), .y=use transparency (if > 0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode
varying float fsTexFormat; // .x=T1RGB5 contour texture (if > 0)
varying float fsTransLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque)
varying vec3 fsLightIntensity; // lighting intensity
varying float fsFogFactor; // fog factor
varying float fsViewZ; // Z distance to fragment from viewpoint at origin
/*
* WrapTexelCoords():
*
* Computes the normalized OpenGL S,T coordinates within the 2048x2048 texture
* sheet, taking into account wrapping behavior.
*
* Computing normalized OpenGL texture coordinates (0 to 1) within the
* Real3D texture sheet:
*
* If the texture is not mirrored, we simply have to clamp the
* coordinates to fit within the texture dimensions, add the texture
* X, Y position to select the appropriate one, and normalize by 2048
* (the dimensions of the Real3D texture sheet).
*
* = [(u,v)%(w,h)+(x,y)]/(2048,2048)
*
* If mirroring is enabled, textures are mirrored every odd multiple of
* the original texture. To detect whether we are in an odd multiple,
* simply divide the coordinate by the texture dimension and check
* whether the result is odd. Then, clamp the coordinates as before but
* subtract from the last texel to mirror them:
*
* = [M*((w-1,h-1)-(u,v)%(w,h)) + (1-M)*(u,v)%(w,h) + (x,y)]/(2048,2048)
* where M is 1.0 if the texture must be mirrored.
*
* As an optimization, this function computes TWO texture coordinates
* simultaneously. The first is texCoord.xy, the second is in .zw. The other
* parameters must have .xy = .zw.
*/
vec4 WrapTexelCoords(vec4 texCoord, vec4 texOffset, vec4 texSize, vec4 mirrorEnable)
{
vec4 clampedCoord, mirror, glTexCoord;
clampedCoord = mod(texCoord,texSize); // clamp coordinates to within texture size
mirror = mirrorEnable * mod(floor(texCoord/texSize),2.0); // whether this texel needs to be mirrored
glTexCoord = ( mirror*(texSize-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
/*
glTexCoord = ( mirror*(texSize-vec4(1.0,1.0,1.0,1.0)-clampedCoord) +
(vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord +
texOffset
) / 2048.0;
*/
return glTexCoord;
}
/*
* main():
*
* Fragment shader entry point.
*/
void main(void)
{
vec4 uv_top, uv_bot, c[4];
vec2 r;
vec4 fragColor;
vec2 ellipse;
vec3 lightIntensity;
float insideSpot;
// Get polygon color for untextured polygons (textured polygons will overwrite)
if (fsTexParams.x==0.0)
fragColor = gl_Color;
else
// Textured polygons: set fragment color to texel value
{
/*
* Bilinear Filtering
*
* In order to get this working on ATI, the number of operations is
* reduced by putting everything into vec4s. uv_top holds the UV
* coordinates for the top two texels (.xy=left, .zw=right) and uv_bot
* is for the lower two.
*/
// Compute fractional blending factor, r, and lower left corner of texel 0
uv_bot.xy = gl_TexCoord[0].st-vec2(0.5,0.5); // move into the lower left blending texel
r = uv_bot.xy-floor(uv_bot.xy); // fractional part
uv_bot.xy = floor(uv_bot.xy); // integral part
// Compute texel coordinates
uv_bot.xy += vec2(0.5,0.5); // offset to center of pixel (should not be needed but it fixes a lot of glitches, esp. on Nvidia)
uv_bot.zw = uv_bot.xy + vec2(1.0,0.0); // compute coordinates of the other three neighbors
uv_top = uv_bot + vec4(0.0,1.0,0.0,1.0);
// Compute the properly wrapped texel coordinates
uv_top = WrapTexelCoords(uv_top,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw));
uv_bot = WrapTexelCoords(uv_bot,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw));
// Fetch the texels
c[0]=texture2D(textureMap,uv_bot.xy); // bottom-left (base texel)
c[1]=texture2D(textureMap,uv_bot.zw); // bottom-right
c[2]=texture2D(textureMap,uv_top.xy); // top-left
c[3]=texture2D(textureMap,uv_top.zw); // top-right
// Interpolate texels and blend result with material color to determine final (unlit) fragment color
// fragColor = (c[0]*(1.0-r.s)*(1.0-r.t) + c[1]*r.s*(1.0-r.t) + c[2]*(1.0-r.s)*r.t + c[3]*r.s*r.t);
// Faster method:
c[0] += (c[1]-c[0])*r.s; // 2 alu
c[2] += (c[3]-c[2])*r.s; // 2 alu
fragColor = c[0]+(c[2]-c[0])*r.t; //2 alu
/*
* T1RGB5:
*
* The transparency bit determines whether to discard pixels (if set).
* What is unknown is how this bit behaves when interpolated. OpenGL
* processes it as an alpha value, so it might concievably be blended
* with neighbors. Here, an arbitrary threshold is chosen.
*
* To-do: blending could probably enabled and this would work even
* better with a hard threshold.
*
* Countour processing also seems to be enabled for RGBA4 textures.
* When the alpha value is 0.0 (or close), pixels are discarded
* entirely.
*/
if (fsTexParams.y > 0.0) // contour processing enabled
{
if (fragColor.a < 0.01) // discard anything with alpha == 0
discard;
}
// If contour texture and not discarded, force alpha to 1.0 because will later be modified by polygon translucency
if (fsTexFormat > 0.0) // contour (T1RGB5) texture map
fragColor.a = 1.0;
}
// Compute spotlight and apply lighting
/***
ellipse = (gl_FragCoord.xy-spotEllipse.xy)/spotEllipse.zw;
insideSpot = dot(ellipse,ellipse);
if ((insideSpot <= 1.0) && (fsViewZ>=spotRange.x) && (fsViewZ<spotRange.y))
lightIntensity = min(fsLightIntensity+(1.0-insideSpot)*spotColor,1.0);
else
lightIntensity = fsLightIntensity;
fragColor.rgb *= lightIntensity;
***/
fragColor.rgb *= fsLightIntensity;
// Translucency (modulates existing alpha channel for RGBA4 texels)
fragColor.a *= fsTransLevel;
// Apply fog under the control of fog factor setting from polygon header
fragColor.rgb = mix(gl_Fog.color.rgb, fragColor.rgb, fsFogFactor );
// Store final color
gl_FragColor = fragColor;
}

View file

@ -0,0 +1,144 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Vertex.glsl
*
* Vertex shader for 3D rendering.
*/
#version 120
// Global uniforms
uniform mat4 modelViewMatrix; // model -> view space matrix
uniform mat4 projectionMatrix; // view space -> screen space matrix
uniform vec3 lighting[2]; // lighting state (lighting[0] = sun direction, lighting[1].x,y = diffuse, ambient intensities from 0-1.0)
uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (normalized device coordinates), .y=Y position, .z=half-width, .w=half-height)
uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit
uniform vec3 spotColor; // spotlight RGB color
// Custom vertex attributes
attribute vec4 subTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels)
attribute vec4 texParams; // .x=texture enable (if 1, else 0), .y=use transparency (if >=0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode
attribute float texFormat; // .x=T1RGB5 contour texture (if > 0)
attribute float transLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque). if less than 1.0, replace alpha value
attribute float lightEnable; // lighting enabled (1.0) or luminous (0.0), drawn at full intensity
attribute float fogIntensity; // fog intensity (1.0, full fog effect, 0.0, no fog)
// Custom outputs to fragment shader
varying vec4 fsSubTexture;
varying vec4 fsTexParams;
varying float fsTexFormat;
varying float fsTransLevel;
varying vec3 fsLightIntensity; // total light intensity for this vertex
varying float fsFogFactor; // fog factor
varying float fsViewZ;
// Gets the 3x3 matrix out of a 4x4 (because mat3(mat4matrix) does not work on ATI!)
mat3 GetLinearPart( mat4 m )
{
mat3 result;
result[0][0] = m[0][0];
result[0][1] = m[0][1];
result[0][2] = m[0][2];
result[1][0] = m[1][0];
result[1][1] = m[1][1];
result[1][2] = m[1][2];
result[2][0] = m[2][0];
result[2][1] = m[2][1];
result[2][2] = m[2][2];
return result;
}
void main(void)
{
vec3 viewVertex; // vertex coordinates in view space
vec3 viewNormal; // vertex normal in view space
vec3 sunVector; // sun lighting vector (as reflecting away from vertex)
float sunFactor; // sun light projection along vertex normal (0.0 to 1.0)
vec3 halfway;
float specFactor;
// Transform vertex
gl_Position = projectionMatrix * modelViewMatrix * gl_Vertex;
viewVertex = vec3(modelViewMatrix * gl_Vertex);
/*
* Modulation
*
* Polygon color serves as material color (modulating the light intensity)
* for textured polygons. The fragment shader will ignore (overwrite) the
* the color passed to it if the fragment is textured.
*
* Untextured fragments must be set to the polygon color and the light
* intensity is initialized to 1.0 here. Alpha must be set to 1.0 because
* the fragment shader multiplies it by the polygon translucency setting.
*
* TO-DO: Does OpenGL set alpha to 1.0 by default if no alpha is specified
* for the vertex? If so, we can remove that line from here.
*/
gl_FrontColor = gl_Color; // untextured polygons will use this
gl_FrontColor.a = 1.0;
fsLightIntensity = vec3(1.0,1.0,1.0);
if (texParams.x > 0.0) // textured
fsLightIntensity *= gl_Color.rgb;
/*
* Sun Light
*
* Parallel light source and ambient lighting are only applied for non-
* luminous polygons.
*/
if (lightEnable > 0.5) // not luminous
{
// Normal -> view space
viewNormal = normalize(GetLinearPart(modelViewMatrix)*gl_Normal);
// Real3D -> OpenGL view space convention (TO-DO: do this outside of shader)
sunVector = lighting[0]*vec3(1.0,-1.0,-1.0);
// Compute diffuse factor for sunlight
sunFactor = max(dot(sunVector,viewNormal),0.0);
// Total light intensity: sum of all components
fsLightIntensity *= (sunFactor*lighting[1].x+lighting[1].y);
fsLightIntensity = clamp(fsLightIntensity,0.0,1.0);
}
// Fog
float z = length(viewVertex);
fsFogFactor = clamp(1.0-fogIntensity*(gl_Fog.start+z*gl_Fog.density), 0.0, 1.0);
// Pass viewspace Z coordinate (for spotlight)
fsViewZ = -viewVertex.z; // convert Z from GL->Real3D convention (want +Z to be further into screen)
// Pass remaining parameters to fragment shader
gl_TexCoord[0] = gl_MultiTexCoord0;
fsSubTexture = subTexture;
fsTexParams = texParams;
fsTransLevel = transLevel;
fsTexFormat = texFormat;
}

View file

@ -0,0 +1,34 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Vertex2D.glsl
*
* Vertex shader for 2D tilemap rendering.
*/
#version 120
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
}

119
Graphics/Shaders2D.h Normal file
View file

@ -0,0 +1,119 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Shaders2D.h
*
* Header file containing the 2D vertex and fragment shaders.
*/
#ifndef INCLUDED_SHADERS2D_H
#define INCLUDED_SHADERS2D_H
// Vertex shader
static const char vertexShaderSource[] =
{
"/** \n"
" ** Supermodel \n"
" ** A Sega Model 3 Arcade Emulator. \n"
" ** Copyright 2011 Bart Trzynadlowski \n"
" ** \n"
" ** This file is part of Supermodel. \n"
" ** \n"
" ** Supermodel is free software: you can redistribute it and/or modify it under \n"
" ** the terms of the GNU General Public License as published by the Free \n"
" ** Software Foundation, either version 3 of the License, or (at your option) \n"
" ** any later version. \n"
" ** \n"
" ** Supermodel is distributed in the hope that it will be useful, but WITHOUT \n"
" ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or \n"
" ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for \n"
" ** more details. \n"
" ** \n"
" ** You should have received a copy of the GNU General Public License along \n"
" ** with Supermodel. If not, see <http://www.gnu.org/licenses/>. \n"
" **/ \n"
"\n"
"/* \n"
" * Vertex2D.glsl \n"
" * \n"
" * Vertex shader for 2D tilemap rendering. \n"
" */ \n"
" \n"
"#version 120 \n"
"\n"
"void main(void) \n"
"{ \n"
" gl_TexCoord[0] = gl_MultiTexCoord0; \n"
" gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; \n"
"}\n"
};
// Fragment shader
static const char fragmentShaderSource[] =
{
"/** \n"
" ** Supermodel \n"
" ** A Sega Model 3 Arcade Emulator. \n"
" ** Copyright 2011 Bart Trzynadlowski \n"
" ** \n"
" ** This file is part of Supermodel. \n"
" ** \n"
" ** Supermodel is free software: you can redistribute it and/or modify it under \n"
" ** the terms of the GNU General Public License as published by the Free \n"
" ** Software Foundation, either version 3 of the License, or (at your option) \n"
" ** any later version. \n"
" ** \n"
" ** Supermodel is distributed in the hope that it will be useful, but WITHOUT \n"
" ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or \n"
" ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for \n"
" ** more details. \n"
" ** \n"
" ** You should have received a copy of the GNU General Public License along \n"
" ** with Supermodel. If not, see <http://www.gnu.org/licenses/>. \n"
" **/ \n"
"\n"
"/* \n"
" * Fragment2D.glsl \n"
" * \n"
" * Fragment shader for 2D tilemap rendering. \n"
" */ \n"
"\n"
"#version 120 \n"
"\n"
"// Global uniforms \n"
"uniform sampler2D textureMap; // 512x512 layer surface \n"
"uniform vec3 colorOffset; // color offset for this layer \n"
"\n"
"/* \n"
" * main(): \n"
" * \n"
" * Fragment shader entry point. \n"
" */ \n"
"\n"
"void main(void) \n"
"{ \n"
" gl_FragColor = texture2D(textureMap, gl_TexCoord[0].st); \n"
" gl_FragColor.rgb = clamp(gl_FragColor.rgb+colorOffset,0.0,1.0); \n"
"}\n"
};
#endif // INCLUDED_SHADERS2D_H

374
Graphics/Shaders3D.h Normal file
View file

@ -0,0 +1,374 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Shaders3D.h
*
* Header file containing the 3D vertex and fragment shaders.
*/
#ifndef INCLUDED_SHADERS3D_H
#define INCLUDED_SHADERS3D_H
// Vertex shader
static const char vertexShaderSource[] =
{
"/** \n"
" ** Supermodel \n"
" ** A Sega Model 3 Arcade Emulator. \n"
" ** Copyright 2011 Bart Trzynadlowski \n"
" ** \n"
" ** This file is part of Supermodel. \n"
" ** \n"
" ** Supermodel is free software: you can redistribute it and/or modify it under \n"
" ** the terms of the GNU General Public License as published by the Free \n"
" ** Software Foundation, either version 3 of the License, or (at your option) \n"
" ** any later version. \n"
" ** \n"
" ** Supermodel is distributed in the hope that it will be useful, but WITHOUT \n"
" ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or \n"
" ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for \n"
" ** more details. \n"
" ** \n"
" ** You should have received a copy of the GNU General Public License along \n"
" ** with Supermodel. If not, see <http://www.gnu.org/licenses/>. \n"
" **/ \n"
"\n"
"/* \n"
" * Vertex.glsl \n"
" * \n"
" * Vertex shader for 3D rendering. \n"
" */ \n"
"\n"
"#version 120 \n"
"\n"
"// Global uniforms \n"
"uniform mat4 modelViewMatrix; // model -> view space matrix \n"
"uniform mat4 projectionMatrix; // view space -> screen space matrix \n"
"uniform vec3 lighting[2]; // lighting state (lighting[0] = sun direction, lighting[1].x,y = diffuse, ambient intensities from 0-1.0) \n"
"uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (normalized device coordinates), .y=Y position, .z=half-width, .w=half-height) \n"
"uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit \n"
"uniform vec3 spotColor; // spotlight RGB color \n"
"\n"
"// Custom vertex attributes \n"
"attribute vec4 subTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels) \n"
"attribute vec4 texParams; // .x=texture enable (if 1, else 0), .y=use transparency (if >=0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode \n"
"attribute float texFormat; // .x=T1RGB5 contour texture (if > 0) \n"
"attribute float transLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque). if less than 1.0, replace alpha value \n"
"attribute float lightEnable; // lighting enabled (1.0) or luminous (0.0), drawn at full intensity \n"
"attribute float fogIntensity; // fog intensity (1.0, full fog effect, 0.0, no fog) \n"
"\n"
"// Custom outputs to fragment shader \n"
"varying vec4 fsSubTexture; \n"
"varying vec4 fsTexParams; \n"
"varying float fsTexFormat; \n"
"varying float fsTransLevel; \n"
"varying vec3 fsLightIntensity; // total light intensity for this vertex \n"
"varying float fsFogFactor; // fog factor \n"
"varying float fsViewZ; \n"
"\n"
"// Gets the 3x3 matrix out of a 4x4 (because mat3(mat4matrix) does not work on ATI!) \n"
"mat3 GetLinearPart( mat4 m ) \n"
"{ \n"
" mat3 result; \n"
"\n"
" result[0][0] = m[0][0]; \n"
" result[0][1] = m[0][1]; \n"
" result[0][2] = m[0][2]; \n"
"\n"
" result[1][0] = m[1][0]; \n"
" result[1][1] = m[1][1]; \n"
" result[1][2] = m[1][2]; \n"
"\n"
" result[2][0] = m[2][0]; \n"
" result[2][1] = m[2][1]; \n"
" result[2][2] = m[2][2]; \n"
"\n"
" return result; \n"
"} \n"
"\n"
"void main(void) \n"
"{ \n"
" vec3 viewVertex; // vertex coordinates in view space \n"
" vec3 viewNormal; // vertex normal in view space \n"
" vec3 sunVector; // sun lighting vector (as reflecting away from vertex) \n"
" float sunFactor; // sun light projection along vertex normal (0.0 to 1.0) \n"
" vec3 halfway; \n"
" float specFactor; \n"
" \n"
" // Transform vertex \n"
" gl_Position = projectionMatrix * modelViewMatrix * gl_Vertex; \n"
" viewVertex = vec3(modelViewMatrix * gl_Vertex); \n"
" \n"
" /* \n"
" * Modulation \n"
" * \n"
" * Polygon color serves as material color (modulating the light intensity) \n"
" * for textured polygons. The fragment shader will ignore (overwrite) the \n"
" * the color passed to it if the fragment is textured. \n"
" * \n"
" * Untextured fragments must be set to the polygon color and the light \n"
" * intensity is initialized to 1.0 here. Alpha must be set to 1.0 because \n"
" * the fragment shader multiplies it by the polygon translucency setting. \n"
" * \n"
" * To-do: Does OpenGL set alpha to 1.0 by default if no alpha is specified \n"
" * for the vertex? If so, we can remove that line from here. \n"
" */ \n"
" \n"
" gl_FrontColor = gl_Color; // untextured polygons will use this \n"
" gl_FrontColor.a = 1.0; \n"
" fsLightIntensity = vec3(1.0,1.0,1.0); \n"
" if (texParams.x > 0.0) // textured \n"
" fsLightIntensity *= gl_Color.rgb; \n"
" \n"
" /* \n"
" * Sun Light \n"
" * \n"
" * Parallel light source and ambient lighting are only applied for non- \n"
" * luminous polygons. \n"
" */ \n"
" if (lightEnable > 0.5) // not luminous \n"
" { \n"
" // Normal -> view space \n"
" viewNormal = normalize(GetLinearPart(modelViewMatrix)*gl_Normal); \n"
" \n"
" // Real3D -> OpenGL view space convention (TO-DO: do this outside of shader) \n"
" sunVector = lighting[0]*vec3(1.0,-1.0,-1.0); \n"
" \n"
" // Compute diffuse factor for sunlight \n"
" sunFactor = max(dot(sunVector,viewNormal),0.0); \n"
" \n"
" // Total light intensity: sum of all components \n"
" fsLightIntensity *= (sunFactor*lighting[1].x+lighting[1].y); \n"
" fsLightIntensity = clamp(fsLightIntensity,0.0,1.0); \n"
" } \n"
" \n"
" // Fog \n"
" float z = length(viewVertex); \n"
" fsFogFactor = clamp(1.0-fogIntensity*(gl_Fog.start+z*gl_Fog.density), 0.0, 1.0); \n"
" \n"
" // Pass viewspace Z coordinate (for spotlight) \n"
" fsViewZ = -viewVertex.z; // convert Z from GL->Real3D convention (want +Z to be further into screen) \n"
" \n"
" // Pass remaining parameters to fragment shader \n"
" gl_TexCoord[0] = gl_MultiTexCoord0; \n"
" fsSubTexture = subTexture; \n"
" fsTexParams = texParams; \n"
" fsTransLevel = transLevel; \n"
" fsTexFormat = texFormat; \n"
"}\n"
};
// Fragment shader
static const char fragmentShaderSource[] =
{
"/** \n"
" ** Supermodel \n"
" ** A Sega Model 3 Arcade Emulator. \n"
" ** Copyright 2011 Bart Trzynadlowski \n"
" ** \n"
" ** This file is part of Supermodel. \n"
" ** \n"
" ** Supermodel is free software: you can redistribute it and/or modify it under \n"
" ** the terms of the GNU General Public License as published by the Free \n"
" ** Software Foundation, either version 3 of the License, or (at your option) \n"
" ** any later version. \n"
" ** \n"
" ** Supermodel is distributed in the hope that it will be useful, but WITHOUT \n"
" ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or \n"
" ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for \n"
" ** more details. \n"
" ** \n"
" ** You should have received a copy of the GNU General Public License along \n"
" ** with Supermodel. If not, see <http://www.gnu.org/licenses/>. \n"
" **/ \n"
" \n"
"/* \n"
" * Fragment.glsl \n"
" * \n"
" * Fragment shader for 3D rendering. \n"
" */ \n"
" \n"
"#version 120 \n"
" \n"
"// Global uniforms \n"
"uniform sampler2D textureMap; // complete texture map, 2048x2048 texels \n"
"uniform vec4 spotEllipse; // spotlight ellipse position: .x=X position (normalized device coordinates), .y=Y position, .z=half-width, .w=half-height) \n"
"uniform vec2 spotRange; // spotlight Z range: .x=start (viewspace coordinates), .y=limit \n"
"uniform vec3 spotColor; // spotlight RGB color \n"
"\n"
"// Inputs from vertex shader \n"
"varying vec4 fsSubTexture; // .x=texture X, .y=texture Y, .z=texture width, .w=texture height (all in texels) \n"
"varying vec4 fsTexParams; // .x=texture enable (if 1, else 0), .y=use transparency (if > 0), .z=U wrap mode (1=mirror, 0=repeat), .w=V wrap mode \n"
"varying float fsTexFormat; // .x=T1RGB5 contour texture (if > 0) \n"
"varying float fsTransLevel; // translucence level, 0.0 (transparent) to 1.0 (opaque) \n"
"varying vec3 fsLightIntensity; // lighting intensity \n"
"varying float fsFogFactor; // fog factor \n"
"varying float fsViewZ; // Z distance to fragment from viewpoint at origin \n"
"\n"
"/* \n"
" * WrapTexelCoords(): \n"
" * \n"
" * Computes the normalized OpenGL S,T coordinates within the 2048x2048 texture \n"
" * sheet, taking into account wrapping behavior. \n"
" * \n"
" * Computing normalized OpenGL texture coordinates (0 to 1) within the \n"
" * Real3D texture sheet: \n"
" * \n"
" * If the texture is not mirrored, we simply have to clamp the \n"
" * coordinates to fit within the texture dimensions, add the texture \n"
" * X, Y position to select the appropriate one, and normalize by 2048 \n"
" * (the dimensions of the Real3D texture sheet). \n"
" * \n"
" * = [(u,v)%(w,h)+(x,y)]/(2048,2048) \n"
" * \n"
" * If mirroring is enabled, textures are mirrored every odd multiple of \n"
" * the original texture. To detect whether we are in an odd multiple, \n"
" * simply divide the coordinate by the texture dimension and check \n"
" * whether the result is odd. Then, clamp the coordinates as before but \n"
" * subtract from the last texel to mirror them: \n"
" * \n"
" * = [M*((w-1,h-1)-(u,v)%(w,h)) + (1-M)*(u,v)%(w,h) + (x,y)]/(2048,2048) \n"
" * where M is 1.0 if the texture must be mirrored. \n"
" * \n"
" * As an optimization, this function computes TWO texture coordinates \n"
" * simultaneously. The first is texCoord.xy, the second is in .zw. The other \n"
" * parameters must have .xy = .zw. \n"
" */ \n"
"vec4 WrapTexelCoords(vec4 texCoord, vec4 texOffset, vec4 texSize, vec4 mirrorEnable) \n"
"{ \n"
" vec4 clampedCoord, mirror, glTexCoord; \n"
" \n"
" clampedCoord = mod(texCoord,texSize); // clamp coordinates to within texture size \n"
" mirror = mirrorEnable * mod(floor(texCoord/texSize),2.0); // whether this texel needs to be mirrored \n"
" \n"
" glTexCoord = ( mirror*(texSize-clampedCoord) + \n"
" (vec4(1.0,1.0,1.0,1.0)-mirror)*clampedCoord + \n"
" texOffset \n"
" ) / 2048.0; \n"
" return glTexCoord; \n"
"} \n"
"\n"
"/* \n"
" * main(): \n"
" * \n"
" * Fragment shader entry point. \n"
" */ \n"
"\n"
"void main(void) \n"
"{ \n"
" vec4 uv_top, uv_bot, c[4]; \n"
" vec2 r; \n"
" vec4 fragColor; \n"
" vec2 ellipse; \n"
" vec3 lightIntensity; \n"
" float insideSpot; \n"
" \n"
" // Get polygon color for untextured polygons (textured polygons will overwrite) \n"
" if (fsTexParams.x==0.0) \n"
" fragColor = gl_Color; \n"
" else \n"
" // Textured polygons: set fragment color to texel value \n"
" { \n"
" /* \n"
" * Bilinear Filtering \n"
" * \n"
" * In order to get this working on ATI, the number of operations is \n"
" * reduced by putting everything into vec4s. uv_top holds the UV \n"
" * coordinates for the top two texels (.xy=left, .zw=right) and uv_bot \n"
" * is for the lower two. \n"
" */ \n"
" \n"
" // Compute fractional blending factor, r, and lower left corner of texel 0 \n"
" uv_bot.xy = gl_TexCoord[0].st-vec2(0.5,0.5); // move into the lower left blending texel \n"
" r = uv_bot.xy-floor(uv_bot.xy); // fractional part \n"
" uv_bot.xy = floor(uv_bot.xy); // integral part \n"
" \n"
" // Compute texel coordinates \n"
" uv_bot.xy += vec2(0.5,0.5); // offset to center of pixel (should not be needed but it fixes a lot of glitches, esp. on Nvidia) \n"
" uv_bot.zw = uv_bot.xy + vec2(1.0,0.0); // compute coordinates of the other three neighbors \n"
" uv_top = uv_bot + vec4(0.0,1.0,0.0,1.0); \n"
" \n"
" // Compute the properly wrapped texel coordinates \n"
" uv_top = WrapTexelCoords(uv_top,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw)); \n"
" uv_bot = WrapTexelCoords(uv_bot,vec4(fsSubTexture.xy,fsSubTexture.xy),vec4(fsSubTexture.zw,fsSubTexture.zw), vec4(fsTexParams.zw,fsTexParams.zw)); \n"
" \n"
" // Fetch the texels \n"
" c[0]=texture2D(textureMap,uv_bot.xy); // bottom-left (base texel) \n"
" c[1]=texture2D(textureMap,uv_bot.zw); // bottom-right \n"
" c[2]=texture2D(textureMap,uv_top.xy); // top-left \n"
" c[3]=texture2D(textureMap,uv_top.zw); // top-right \n"
" \n"
" // Interpolate texels and blend result with material color to determine final (unlit) fragment color \n"
" // fragColor = (c[0]*(1.0-r.s)*(1.0-r.t) + c[1]*r.s*(1.0-r.t) + c[2]*(1.0-r.s)*r.t + c[3]*r.s*r.t); \n"
" // Faster method: \n"
" c[0] += (c[1]-c[0])*r.s; // 2 alu \n"
" c[2] += (c[3]-c[2])*r.s; // 2 alu \n"
" fragColor = c[0]+(c[2]-c[0])*r.t; // 2 alu \n"
" \n"
" /* \n"
" * T1RGB5: \n"
" * \n"
" * The transparency bit determines whether to discard pixels (if set). \n"
" * What is unknown is how this bit behaves when interpolated. OpenGL \n"
" * processes it as an alpha value, so it might concievably be blended \n"
" * with neighbors. Here, an arbitrary threshold is chosen. \n"
" * \n"
" * To-do: blending could probably enabled and this would work even \n"
" * better with a hard threshold. \n"
" * \n"
" * Countour processing also seems to be enabled for RGBA4 textures. \n"
" * When the alpha value is 0.0 (or close), pixels are discarded \n"
" * entirely. \n"
" */ \n"
" if (fsTexParams.y > 0.0) // contour processing enabled \n"
" { \n"
" if (fragColor.a < 0.01) // discard anything with alpha == 0 \n"
" discard; \n"
" } \n"
" \n"
" // If contour texture and not discarded, force alpha to 1.0 because will later be modified by polygon translucency \n"
" if (fsTexFormat > 0.0) // contour (T1RGB5) texture map \n"
" fragColor.a = 1.0; \n"
" } \n"
" \n"
" // Compute spotlight and apply lighting \n"
" ellipse = (gl_FragCoord.xy-spotEllipse.xy)/spotEllipse.zw; \n"
" insideSpot = dot(ellipse,ellipse); \n"
" if ((insideSpot <= 1.0) && (fsViewZ>=spotRange.x) && (fsViewZ<spotRange.y)) \n"
" lightIntensity = min(fsLightIntensity+(1.0-insideSpot)*spotColor,1.0); \n"
" else \n"
" lightIntensity = fsLightIntensity; \n"
" fragColor.rgb *= lightIntensity; \n"
" \n"
" // Translucency (modulates existing alpha channel for RGBA4 texels) \n"
" fragColor.a *= fsTransLevel; \n"
" \n"
" // Apply fog under the control of fog factor setting from polygon header \n"
" fragColor.rgb = mix(gl_Fog.color.rgb, fragColor.rgb, fsFogFactor ); \n"
" \n"
" // Store final color \n"
" gl_FragColor = fragColor; \n"
"}\n"
};
#endif // INCLUDED_SHADERS3D_H

580
INIFile.cpp Normal file
View file

@ -0,0 +1,580 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* INIFile.cpp
*
* INI file management. Implementation of the CINIFile class.
*
* To-Do List
* ----------
* - Add boolean on/off, true/false keywords.
* - Allow a "default" section to be specified (other than "").
* - Note that linePtr does not necessarily correspond to actual lines in the
* file (newlines should be counted by the tokenizer for that).
*
* Grammar
* -------
*
* Section: '[' Identifier ']'
* Line: Identifier '=' Argument
* Argument: Number
* String
*
* Overview
* --------
* INI files are linear in nature, divided into sections. Each section has
* settings associated with it which may be either numerical values or strings.
* Presently, only unsigned 32-bit integers are supported.
*
* INI files are first opened, then parsed, and finally closed. The parse tree
* is cleared only during object construction and when the INI file is closed.
* Default settings may be inserted before a file is opened or parsed. When an
* INI file is written out, the current INI file on the disk is cleared. If an
* error occurs during this process, the data will be lost.
*
* Blank sections (sections for which no settings are defined) are not stored
* in the parse tree. Sections are added only when a setting is found in that
* section. A default section name can be specified (for settings that are not
* associated with any section), otherwise an empty string ("") is used.
*/
#include "Supermodel.h"
using namespace std;
/******************************************************************************
Basic Functions
******************************************************************************/
BOOL CINIFile::Write(const char *comment)
{
BOOL writeSuccess;
// In order to truncate, we must close and reopen as truncated
File.close();
File.clear(); // required to clear EOF flag (open() does not clear)
File.open(FileName.c_str(),fstream::out|fstream::trunc);
if (File.fail())
{
return FAIL;
}
// Output comment
if (comment != NULL)
File << comment << endl;
// Iterate through all sections sequentially
for (unsigned i = 0; i < Sections.size(); i++)
{
// Output section name
if (Sections[i].Name != "") // if null name, don't output section at all
File << "[ " << Sections[i].Name << " ]" << endl << endl;
// Iterate through all settings within this section
for (unsigned j = 0; j < Sections[i].Settings.size(); j++)
{
// Output setting
File << Sections[i].Settings[j].Name << " = ";
if (Sections[i].Settings[j].isNumber)
File << Sections[i].Settings[j].value << endl;
else
File << '\"' << Sections[i].Settings[j].String << '\"' << endl;
}
// New line
File << endl;
}
writeSuccess = File.good()?OKAY:FAIL;
// Close and reopen as read/write
File.close();
File.open(FileName.c_str(),fstream::in|fstream::out);
if (File.fail())
{
//printf("unable to re-open %s for reading/writing\n", FileName.c_str());
return FAIL;
}
// Report any errors that occurred during writing
return writeSuccess;
}
BOOL CINIFile::Open(const char *fileNameStr)
{
FileName = fileNameStr;
File.open(fileNameStr, fstream::in|fstream::out);
if (File.fail())
{
//printf("unable to open %s\n", fileNameStr);
return FAIL;
}
return OKAY;
}
void CINIFile::Close(void)
{
File.close();
Sections.clear(); // clear the parse tree!
}
/******************************************************************************
Management of Settings
******************************************************************************/
// Finds index of section in the section list. Returns FAIL if not found.
BOOL CINIFile::LookUpSection(unsigned *idx, string SectionName)
{
for (unsigned i = 0; i < Sections.size(); i++)
{
if (Sections[i].Name == SectionName)
{
*idx = i;
return OKAY;
}
}
return FAIL;
}
// Finds index of the setting in the given section. Returns FAIL if not found.
BOOL CINIFile::LookUpSetting(unsigned *idx, unsigned sectionIdx, string SettingName)
{
for (unsigned i = 0; i < Sections[sectionIdx].Settings.size(); i++)
{
if (Sections[sectionIdx].Settings[i].Name == SettingName)
{
*idx = i;
return OKAY;
}
}
return FAIL;
}
// Assigns a value to the given setting, creating the setting if it does not exist. Nulls out the string (sets it to "").
void CINIFile::Set(string SectionName, string SettingName, unsigned value)
{
unsigned sectionIdx, settingIdx;
if (OKAY != LookUpSection(&sectionIdx, SectionName))
{
//printf("unable to find %s:%s, creating section\n", SectionName.c_str(), SettingName.c_str());
struct Section NewSection;
NewSection.Name = SectionName;
Sections.push_back(NewSection);
sectionIdx = Sections.size()-1; // the new section will be at the last index
}
if (OKAY != LookUpSetting(&settingIdx, sectionIdx, SettingName))
{
//printf("unable to find %s:%s, creating setting\n", SectionName.c_str(), SettingName.c_str());
struct Setting NewSetting;
NewSetting.Name = SettingName;
Sections[sectionIdx].Settings.push_back(NewSetting);
settingIdx = Sections[sectionIdx].Settings.size()-1;
}
// Update value of this setting
Sections[sectionIdx].Settings[settingIdx].isNumber = TRUE;
Sections[sectionIdx].Settings[settingIdx].value = value;
Sections[sectionIdx].Settings[settingIdx].String = "";
}
// Assigns the string to the given setting, creating the setting if it does not exist. Zeros out the value.
void CINIFile::Set(string SectionName, string SettingName, string String)
{
unsigned sectionIdx, settingIdx;
if (OKAY != LookUpSection(&sectionIdx, SectionName))
{
//printf("unable to find %s:%s, creating section\n", SectionName.c_str(), SettingName.c_str());
struct Section NewSection;
NewSection.Name = SectionName;
Sections.push_back(NewSection);
sectionIdx = Sections.size()-1; // the new section will be at the last index
}
if (OKAY != LookUpSetting(&settingIdx, sectionIdx, SettingName))
{
//printf("unable to find %s:%s, creating setting\n", SectionName.c_str(), SettingName.c_str());
struct Setting NewSetting;
NewSetting.Name = SettingName;
Sections[sectionIdx].Settings.push_back(NewSetting);
settingIdx = Sections[sectionIdx].Settings.size()-1;
}
// Update string
Sections[sectionIdx].Settings[settingIdx].isNumber = FALSE;
Sections[sectionIdx].Settings[settingIdx].String = String;
Sections[sectionIdx].Settings[settingIdx].value = 0;
}
// Obtains a numerical setting, if it exists, otherwise does nothing.
BOOL CINIFile::Get(string SectionName, string SettingName, unsigned& value)
{
unsigned sectionIdx, settingIdx;
if (OKAY != LookUpSection(&sectionIdx, SectionName))
return FAIL;
if (OKAY != LookUpSetting(&settingIdx, sectionIdx, SettingName))
return FAIL;
value = Sections[sectionIdx].Settings[settingIdx].value;
return OKAY;
}
// Obtains a string setting, if it exists, otherwise does nothing.
BOOL CINIFile::Get(string SectionName, string SettingName, string& String)
{
unsigned sectionIdx, settingIdx;
if (OKAY != LookUpSection(&sectionIdx, SectionName))
return FAIL;
if (OKAY != LookUpSetting(&settingIdx, sectionIdx, SettingName))
return FAIL;
String = Sections[sectionIdx].Settings[settingIdx].String;
return OKAY;
}
/******************************************************************************
Tokenizer
Never need to check for newlines because it is assumed these have been
stripped by getline().
******************************************************************************/
// Token types
#define TOKEN_INVALID -1
#define TOKEN_NULL 0
#define TOKEN_IDENTIFIER 1
#define TOKEN_NUMBER 2
#define TOKEN_STRING 3
// Token constructor (initializes token to null)
CINIFile::CToken::CToken(void)
{
type = TOKEN_NULL;
}
// Returns true for white space, comment symbol, or null terminator.
static BOOL IsBlank(char c)
{
if (isspace(c) || (c==';') || (c=='\0'))
return TRUE;
return FALSE;
}
// Fetches a string. Tolerates all characters between quotes.
CINIFile::CToken CINIFile::GetString(void)
{
CToken T;
T.type = TOKEN_STRING;
// Search for next quote
++linePtr;
while (1)
{
if (linePtr[0] == '\"')
{
++linePtr; // so we can find next token
break;
}
else if (linePtr[0] == '\0')
{
//printf("tokenizer: warning: string is missing end quote\n");
break;
}
else
T.String += linePtr[0];
++linePtr;
}
return T;
}
// Fetch number (decimal or hexadecimal integer). linePtr must point to a character and therefore linePtr[1] is guaranteed to be within bounds.
CINIFile::CToken CINIFile::GetNumber(void)
{
CToken T;
unsigned long long number = 0;
int overflow = 0;
T.type = TOKEN_NUMBER;
// Hexadecimal?
if ((linePtr[0]=='0') && ((linePtr[1]=='X') || (linePtr[1]=='x')))
{
linePtr += 2; // advance to digits
// Ensure that at we have at least one digit
if (!isxdigit(linePtr[0]))
{
//printf("tokenizer: invalid hexadecimal number\n");
T.type = TOKEN_INVALID;
return T;
}
// Read number digit by digit
while (1)
{
if (isxdigit(linePtr[0]))
{
number <<= 4;
if (isdigit(linePtr[0]))
number |= (linePtr[0]-'0');
else if (isupper(linePtr[0]))
number |= (linePtr[0]-'A');
else // must be lowercase...
number |= (linePtr[0]-'a');
++linePtr;
// Check for overflows
if (number > 0x00000000FFFFFFFFULL)
overflow = 1;
}
else if (IsBlank(linePtr[0]))
break;
else
{
//printf("tokenizer: invalid hexadecimal number\n");
T.type = TOKEN_INVALID;
return T;
}
}
}
// Decimal?
else
{
// Read number digit by digit
while (1)
{
if (isdigit(linePtr[0]))
{
number *= 10;
number += (linePtr[0]-'0');
++linePtr;
// Check for overflows
if (number > 0x00000000FFFFFFFFULL)
overflow = 1;
}
else if (IsBlank(linePtr[0]))
break;
else
{
//printf("tokenizer: invalid number\n");
T.type = TOKEN_INVALID;
return T;
}
}
}
//if (overflow)
// printf("tokenizer: number exceeds 32 bits and has been truncated\n");
T.number = (unsigned) number;
return T;
}
// Fetch identifier
CINIFile::CToken CINIFile::GetIdentifier(void)
{
CToken T;
T.type = TOKEN_IDENTIFIER;
while (1)
{
if (isalpha(linePtr[0]) || isdigit(linePtr[0]) || (linePtr[0]=='_'))
{
T.String += linePtr[0];
++linePtr;
}
else
break;
}
return T;
}
// Fetch token
CINIFile::CToken CINIFile::GetToken(void)
{
CToken T;
while (1)
{
// Gobble up whitespace
if (isspace(linePtr[0]))
++linePtr;
// Comment or end of line
else if ((linePtr[0]==';') || (linePtr[0]=='\0'))
{
T.type = TOKEN_NULL;
return T;
}
// Delimiters
else if ((linePtr[0]=='[') || (linePtr[0]==']') || (linePtr[0]=='='))
{
T.type = *linePtr++;
return T;
}
// Identifier?
else if (isalpha(linePtr[0]) || (linePtr[0] == '_'))
{
T = GetIdentifier();
return T;
}
// Number? (+/-?)
else if (isdigit(linePtr[0]))
{
T = GetNumber();
return T;
}
// String?
else if (linePtr[0]=='\"')
{
T = GetString();
return T;
}
// Illegal symbol
else
break; // return null token
}
// If we got here, invalid token
T.type = TOKEN_INVALID;
return T;
}
/******************************************************************************
Parser
******************************************************************************/
BOOL CINIFile::Parse(void)
{
CToken T, U, V, W;
string currentSection; // current section we're processing
BOOL parseStatus = OKAY;
//currentSection = defaultSection; // default "global" section
lineNum = 0;
if (!File.is_open())
return FAIL;
File.clear();
if (!File.good())
return FAIL;
while (!File.eof())
{
++lineNum;
File.getline(lineBuf,2048);
if (File.fail())
return FAIL;
linePtr = lineBuf; // beginning of line
// Top level
T = GetToken();
U = GetToken();
V = GetToken();
W = GetToken(); // should always be null
switch (T.type)
{
// [ Identifier ]
case '[':
if ((U.type==TOKEN_IDENTIFIER) && (V.type==']') && (W.type==TOKEN_NULL))
{
//printf("Section: %s\n", U.String.c_str());
currentSection = U.String;
}
else
{
parseStatus = FAIL;
//printf("%d: parse error\n", lineNum);
}
break;
// Identifier '=' Argument
case TOKEN_IDENTIFIER:
if (U.type != '=')
{
parseStatus = FAIL;
//printf("%d: expected '=' after identifier\n", lineNum);
}
else
{
if (((V.type==TOKEN_NUMBER) || (V.type==TOKEN_STRING)) && (W.type==TOKEN_NULL))
{
if (V.type == TOKEN_NUMBER)
{
//printf("\t%s = %X\n", T.String.c_str(), V.number);
Set(currentSection, T.String, V.number);
}
else if (V.type == TOKEN_STRING)
{
//printf("\t%s = %s\n", T.String.c_str(), V.String.c_str());
Set(currentSection, T.String, V.String);
}
}
else
{
parseStatus = FAIL;
//printf("%d: expected a number or string after '='\n", lineNum);
}
}
break;
// Blank line
case TOKEN_NULL:
break;
// Illegal
default:
parseStatus = FAIL;
//printf("%d: parse error\n", lineNum);
break;
}
}
//printf("end of file reached\n");
return parseStatus;
}

198
INIFile.h Normal file
View file

@ -0,0 +1,198 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* INIFile.h
*
* Header file for INI file management.
*/
#ifndef INCLUDED_INIFILE_H
#define INCLUDED_INIFILE_H
// Standard C++ and STL headers
#include <fstream>
#include <string>
#include <vector>
using namespace std;
/*
* CINIFile:
*
* INI file parser.
*/
class CINIFile
{
public:
/*
* Get(SectionName, SettingName, value):
* Get(SectionName, SettingName, String):
*
* Obtains the value of a setting associated with a particular section. It
* is assumed that the caller knows whether the setting should be a string
* or an integer value. If the setting was specified as a string in the
* INI file, the value will be set to 0. Otherwise, the string will be set
* to "".
*
* Parameters:
* SectionName String defining the section name.
* SettingName String defining the setting name.
* value Reference to where the setting value will be
* copied.
* String Reference to where the string will be copied.
*
* Returns:
* OKAY if the setting was found, FAIL otherwise. The type is not
* checked.
*/
BOOL Get(string SectionName, string SettingName, unsigned& value);
BOOL Get(string SectionName, string SettingName, string& String);
/*
* Set(SectionName, SettingName, value):
* Set(SectionName, SettingName, String):
*
* Sets a setting associated with a particular section. For each overload
* type (string or integer), the opposite type is cleard (0 is written to
* values when a string is set, "" is written to strings when a value is
* set). If the setting does not exist, it is created. If the section does
* not exist, it will be added as well.
*
* Parameters:
* SectionName String defining the section name.
* SettingName String defining the setting name.
* value Value to write. String will be set to "".
* String String to write. Value will be set to 0.
*/
void Set(string SectionName, string SettingName, unsigned value);
void Set(string SectionName, string SettingName, string String);
/*
* Write(comment):
*
* Outputs the parse tree to the INI file. The current contents of the file
* on the disk are discarded and overwritten. This means that comments are
* lost.
*
* Parameters:
* comment An optional C-string containing a comment to insert at
* the beginning of the file, otherwise NULL. The comment
* lines must include semicolons at the beginning, they
* will not be inserted automatically. The string is
* output directly, as-is.
*
* Returns:
* OKAY if successful. FAIL if an error occurred at any point. In
* order to truncate the original contents of the file, this function
* closes the file, reopends it as write-only, then reopens it again
* as a read/write file. If an error occurs during the reopening
* procedure, it is possible that nothing will be output and the
* previous contents will be lost.
*/
BOOL Write(const char *comment);
/*
* Parse(void):
*
* Parses the contents of the file, building the parse tree. Settings
* already present in the parse tree will continue to exist and will be
* overwritten if new, matching settings are found in the file.
*
* Returns:
*/
BOOL Parse(void);
/*
* Open(fileNameStr):
*
* Opens an INI file.
*
* Parameters:
* fileNameStr File path (C string).
*
* Returns:
* OKAY if successful, FAIL if unable to open for reading and writing.
*/
BOOL Open(const char *fileNameStr);
/*
* Close(void):
*
* Closes the INI file and clears the parse tree.
*/
void Close(void);
private:
// Token
class CToken
{
public:
int type; // token type (defined privately in INIFile.cpp)
unsigned number; // numbers and bools
string String; // strings and identifiers
// Constructor (initialize to null token)
CToken(void);
};
// Searching/modification of settings
BOOL LookUpSection(unsigned *idx, string SectionName);
BOOL LookUpSetting(unsigned *idx, unsigned sectionIdx, string SettingName);
// Tokenizer
CToken GetString(void);
CToken GetNumber(void);
CToken GetIdentifier(void);
CToken GetToken(void);
// File state
fstream File;
string FileName; // name of last file opened
// Parser state
char lineBuf[2048]; // holds current line
char *linePtr; // points to current position within line (for tokenization)
unsigned lineNum; // line number
// Parse tree: a list of sections each of which is a list of settings for that section
struct Setting // it is up to caller to determine whether to use value or string
{
string Name; // setting name
BOOL isNumber; // internal flag: true if the setting is a number, false if it is a string
unsigned value; // value of number
string String; // string
Setting(void)
{
value = 0; // initialize value to 0
isNumber = TRUE; // indicate the setting is initially a number
}
};
struct Section
{
string Name; // section name
vector<struct Setting> Settings; // list of settings associated w/ this section
};
vector<struct Section> Sections; // a list of sections
};
#endif // INCLUDED_INIFILE_H

105
Inputs/Input.cpp Normal file
View file

@ -0,0 +1,105 @@
#include "Supermodel.h"
CInput::CInput(const char *inputId, const char *inputLabel, unsigned inputFlags, unsigned inputGameFlags, const char *defaultMapping, UINT16 initValue) :
id(inputId), label(inputLabel), flags(inputFlags), gameFlags(inputGameFlags), m_defaultMapping(defaultMapping), value(initValue), prevValue(initValue),
m_system(NULL), m_source(NULL)
{
ResetToDefaultMapping();
}
void CInput::CreateSource()
{
// If no system set yet or mapping is empty or NONE, then set source to NULL
if (m_system == NULL || m_mapping[0] == '\0' || stricmp(m_mapping, "NONE") == 0)
m_source = NULL;
else
{
// Otherwise, ask system to parse mapping into appropriate input source
m_source = m_system->ParseSource(m_mapping, !!(flags & INPUT_FLAGS_AXIS));
// Check that mapping was parsed okay and if not then fall back to default mapping
if (m_source == NULL && stricmp(m_mapping, m_defaultMapping) != 0)
{
ErrorLog("Unable to map input %s to [%s] - switching to default [%s].\n", id, m_mapping, m_defaultMapping);
ResetToDefaultMapping();
}
}
}
void CInput::Initialize(CInputSystem *system)
{
m_system = system;
CreateSource();
}
const char* CInput::GetInputGroup()
{
switch (gameFlags)
{
case GAME_INPUT_COMMON: return "Common Controls";
case GAME_INPUT_JOYSTICK1: // Fall through to below
case GAME_INPUT_JOYSTICK2: return "8-Way Joysticks";
case GAME_INPUT_FIGHTING: return "Fighting Game Buttons";
case GAME_INPUT_SOCCER: return "Virtua Striker Buttons";
case GAME_INPUT_VEHICLE: return "Racing Game Steering Controls";
case GAME_INPUT_SHIFT4: return "Racing Game Gear Shift";
case GAME_INPUT_VR: return "Racing Game VR View Buttons";
case GAME_INPUT_RALLY: return "Sega Rally Buttons";
case GAME_INPUT_TWIN_JOYSTICKS: return "Virtua On Controls";
case GAME_INPUT_ANALOG_JOYSTICK: return "Analog Joystick";
case GAME_INPUT_GUN1: // Fall through to below
case GAME_INPUT_GUN2: return "Lightguns";
default: return "Misc";
}
}
const char *CInput::GetMapping()
{
return m_mapping;
}
void CInput::ClearMapping()
{
SetMapping("NONE");
}
void CInput::SetMapping(const char *mapping)
{
strncpy(m_mapping, mapping, MAX_MAPPING_LENGTH - 1);
m_mapping[MAX_MAPPING_LENGTH - 1] = '\0';
CreateSource();
}
void CInput::AppendMapping(const char *mapping)
{
// If mapping is empty or NONE, then simply set mapping
if (m_mapping[0] == '\0' || stricmp(m_mapping, "NONE") == 0)
SetMapping(mapping);
else
{
// Otherwise, append to mapping string and recreate source from new mapping string
int size = MAX_MAPPING_LENGTH - strlen(m_mapping);
strncat(m_mapping, ",", size--);
strncat(m_mapping, mapping, size);
CreateSource();
}
}
void CInput::ResetToDefaultMapping()
{
SetMapping(m_defaultMapping);
}
bool CInput::Configure(bool append, const char *escapeMapping)
{
char mapping[MAX_MAPPING_LENGTH];
if (!m_system->ReadMapping(mapping, MAX_MAPPING_LENGTH, !!(flags & INPUT_FLAGS_AXIS), READ_ALL, escapeMapping))
return false;
if (append)
AppendMapping(mapping);
else
SetMapping(mapping);
return true;
}

114
Inputs/Input.h Normal file
View file

@ -0,0 +1,114 @@
#ifndef INCLUDED_INPUT_H
#define INCLUDED_INPUT_H
#include "Types.h"
class CInputSource;
class CInputSystem;
// Flags for inputs
#define INPUT_FLAGS_SWITCH 0x0001
#define INPUT_FLAGS_ANALOG 0x0002
#define INPUT_FLAGS_AXIS 0x0004
#define INPUT_FLAGS_VIRTUAL 0x0008
#define MAX_MAPPING_LENGTH 255
/*
* Base class for any type of Model3 input control.
*/
class CInput
{
private:
// Current mapping(s) for input, eg JOY1_XAXIS_POS
char m_mapping[MAX_MAPPING_LENGTH];
// Default mapping for input
const char *m_defaultMapping;
// Assigned input system
CInputSystem *m_system;
/*
* Creates an input source using the current input system and assigns it to this input.
*/
void CreateSource();
protected:
// Current input source
CInputSource *m_source;
public:
// Input identifier
const char *id;
// Input label
const char *label;
// Input flags
unsigned flags;
// Input game flags
unsigned gameFlags;
// Current input value
UINT16 value;
// Previous input value
UINT16 prevValue;
/*
* Constructs an input with the given identifier, label, flags, game flags, default mapping and initial value.
*/
CInput(const char *inputId, const char *inputLabel, unsigned inputFlags, unsigned inputGameFlags,
const char *defaultMapping = "NONE", UINT16 initValue = 0);
/*
* Initializes this input with the given input system. Must be called before any other methods are used.
*/
void Initialize(CInputSystem *system);
/*
* Returns the name of the group of controls that this input belongs to.
*/
const char* GetInputGroup();
/*
* Returns the current mapping(s) assigned to this input.
*/
const char* GetMapping();
/*
* Clears the current mapping(s) assigned to this input.
*/
void ClearMapping();
/*
* Sets the current mapping(s) assigned to this input. Multiple mapping assignments are comma-separated, eg KEY_RIGHT,JOY1_XAXIS_POS
*/
void SetMapping(const char *mapping);
/*
* Appends a mapping to this input to create a multiple mapping assignment.
*/
void AppendMapping(const char *mapping);
/*
* Resets the mapping(s) assigned to this input to the input's default.
*/
void ResetToDefaultMapping();
/*
* Configures the current mapping(s) assigned to this input by asking the user for input.
* If append is true, then the user's selected mapping is appended. Otherwise, it overwrites the existing mapping(s).
* escapeMapping holds the input mapping used to exit the configuration without applying the changes.
*/
bool Configure(bool append, const char *escapeMapping = "KEY_ESCAPE");
/*
* Polls (updates) this input, updating its value from the input source
*/
virtual void Poll() = 0;
};
#endif // INCLUDED_INPUT_H

84
Inputs/InputSource.cpp Normal file
View file

@ -0,0 +1,84 @@
#include "InputSource.h"
#include <vector>
using namespace std;
CInputSource::CInputSource(ESourceType sourceType) : type(sourceType)
{
//
}
int CInputSource::Clamp(int val, int minVal, int maxVal)
{
if (val > maxVal) return maxVal;
else if (val < minVal) return minVal;
else return val;
}
int CInputSource::Scale(int val, int fromMinVal, int fromMaxVal, int toMinVal, int toMaxVal)
{
return Scale(val, fromMinVal, fromMinVal, fromMaxVal, toMinVal, toMinVal, toMaxVal);
}
int CInputSource::Scale(int val, int fromMinVal, int fromOffVal, int fromMaxVal, int toMinVal, int toOffVal, int toMaxVal)
{
int fromRange;
double frac;
if (fromMaxVal > fromMinVal)
{
val = Clamp(val, fromMinVal, fromMaxVal);
if (val > fromOffVal)
{
fromRange = fromMaxVal - fromOffVal;
frac = (double)(val - fromOffVal) / fromRange;
}
else if (val < fromOffVal)
{
fromRange = fromOffVal - fromMinVal;
frac = (double)(val - fromOffVal) / fromRange;
}
else
return toOffVal;
}
else if (fromMinVal > fromMaxVal)
{
val = Clamp(val, fromMaxVal, fromMinVal);
if (val > fromOffVal)
{
fromRange = fromMinVal - fromOffVal;
frac = (double)(fromOffVal - val) / fromRange;
}
else if (val < fromOffVal)
{
fromRange = fromOffVal - fromMaxVal;
frac = (double)(fromOffVal - val) / fromRange;
}
else
return toOffVal;
}
else
return toOffVal;
double toRange;
if (toMaxVal > toMinVal)
{
if (frac >= 0)
toRange = (double)(toMaxVal - toOffVal);
else
toRange = (double)(toOffVal - toMinVal);
return toOffVal + (int)(toRange * frac);
}
else
{
if (frac >= 0)
toRange = (double)(toOffVal - toMaxVal);
else
toRange = (double)(toMinVal - toOffVal);
return toOffVal - (int)(toRange * frac);
}
}
bool CInputSource::IsActive()
{
bool boolVal;
return GetValueAsSwitch(boolVal);
}

72
Inputs/InputSource.h Normal file
View file

@ -0,0 +1,72 @@
#ifndef INCLUDED_INPUTSOURCE_H
#define INCLUDED_INPUTSOURCE_H
#include <string>
using namespace std;
class CInputSystem;
enum ESourceType
{
SourceInvalid = -1,
SourceEmpty = 0,
SourceSwitch = 1,
SourceHalfAxis = 2,
SourceFullAxis = 3
};
/*
* Provides values to inputs, either in the form of a bool for switch inputs or an int value within a given range for analog/axis inputs.
*/
class CInputSource
{
protected:
CInputSource(ESourceType sourceType);
public:
const ESourceType type;
//
// Static helper methods
//
/*
* Clamps the given value to between the min and max values.
*/
static int Clamp(int val, int minVal, int maxVal);
/*
* Scales the given value, that falls within the stated 'from' range, to be between the given 'to' range.
*/
static int Scale(int val, int fromMinVal, int fromMaxVal, int toMinVal, int toMaxVal);
/*
* Scales the given value, that falls within the stated 'from' range, to be between the given 'to' range.
* The off values are supplied to avoid rounding errors and ensure that the scaled value has the correct off value when required.
*/
static int Scale(int val, int fromMinVal, int fromOffVal, int fromMaxVal, int toMinVal, int toOffVal, int toMaxVal);
/*
* Returns true if this source represents a switch input (such as a button). Otherwise it is assumed to be an analog input (such as axis or pedal).
*/
ESourceType GetType();
/*
* Returns true if the source is active (taken from GetValueAsSwitch).
*/
bool IsActive();
/*
* Reads a boolean value for a switch input.
* Returns true if the value was set.
*/
virtual bool GetValueAsSwitch(bool &val) = 0;
/*
* Reads an int value for an analog/axis input, guaranteeing that the value falls within the given range and has the correct off value when required.
* Returns true if the value was set.
*/
virtual bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal) = 0;
};
#endif // INCLUDED_INPUTSOURCE_H

1899
Inputs/InputSystem.cpp Normal file

File diff suppressed because it is too large Load diff

925
Inputs/InputSystem.h Normal file
View file

@ -0,0 +1,925 @@
#ifndef INCLUDED_INPUTSYSTEM_H
#define INCLUDED_INPUTSYSTEM_H
#include <string>
#include <vector>
using namespace std;
#include "MultiInputSource.h"
class CInput;
class CInputSource;
class CINIFile;
// Read flags for ReadMapping
#define READ_KEYBOARD 1
#define READ_MOUSE 2
#define READ_JOYSTICK 4
#define READ_MERGE_KEYBOARD 8
#define READ_MERGE_MOUSE 16
#define READ_MERGE_JOYSTICK 32
#define READ_ALL (READ_KEYBOARD|READ_MOUSE|READ_JOYSTICK)
#define READ_MERGE (READ_MERGE_KEYBOARD|READ_MERGE_MOUSE|READ_MERGE_JOYSTICK)
// Used to specify any keyboard, mouse or joystick when a number is required
#define ANY_KEYBOARD -1
#define ANY_MOUSE -1
#define ANY_JOYSTICK -1
// Default keyboard, mouse and joystick settings
#define DEFAULT_KEY_SENSITIVITY 25
#define DEFAULT_KEY_DECAYSPEED 50
#define DEFAULT_MSE_DEADZONE 0
#define DEFAULT_JOY_DEADZONE 3
#define DEFAULT_JOY_SATURATION 100
// Number of valid keys and mouse and joystick parts
#define NUM_VALID_KEYS (sizeof(s_validKeyNames) / sizeof(const char*))
#define NUM_MOUSE_PARTS ((int)MouseButtonX2 + 1)
#define NUM_MOUSE_AXES 3
#define NUM_MOUSE_BUTTONS 5
#define NUM_JOY_PARTS ((int)JoyButton31 + 1)
#define NUM_JOY_AXES 6
#define NUM_JOY_POVS 4
#define NUM_JOY_BUTTONS 32
// Axis numbers
#define AXIS_X 0
#define AXIS_Y 1
#define AXIS_Z 2
#define AXIS_RX 3
#define AXIS_RY 4
#define AXIS_RZ 5
// Axis directions
#define AXIS_FULL 0
#define AXIS_INVERTED 1
#define AXIS_POS 2
#define AXIS_NEG 3
// POV directions
#define POV_UP 0
#define POV_DOWN 1
#define POV_LEFT 2
#define POV_RIGHT 3
/*
* Enumeration of all recognised mouse parts
*/
enum EMousePart
{
MouseUnknown = -1,
MouseXAxis = 0,
MouseXAxisInv,
MouseXAxisPos,
MouseXAxisNeg,
MouseYAxis,
MouseYAxisInv,
MouseYAxisPos,
MouseYAxisNeg,
MouseZAxis,
MouseZAxisInv,
MouseZAxisPos,
MouseZAxisNeg,
MouseButtonLeft,
MouseButtonMiddle,
MouseButtonRight,
MouseButtonX1,
MouseButtonX2,
};
/*
* Enumeration of all supported joystick parts
*/
enum EJoyPart
{
JoyUnknown = -1,
JoyXAxis = 0,
JoyXAxisInv,
JoyXAxisPos,
JoyXAxisNeg,
JoyYAxis,
JoyYAxisInv,
JoyYAxisPos,
JoyYAxisNeg,
JoyZAxis,
JoyZAxisInv,
JoyZAxisPos,
JoyZAxisNeg,
JoyRXAxis,
JoyRXAxisInv,
JoyRXAxisPos,
JoyRXAxisNeg,
JoyRYAxis,
JoyRYAxisInv,
JoyRYAxisPos,
JoyRYAxisNeg,
JoyRZAxis,
JoyRZAxisInv,
JoyRZAxisPos,
JoyRZAxisNeg,
JoyPOV0Up,
JoyPOV0Down,
JoyPOV0Left,
JoyPOV0Right,
JoyPOV1Up,
JoyPOV1Down,
JoyPOV1Left,
JoyPOV1Right,
JoyPOV2Up,
JoyPOV2Down,
JoyPOV2Left,
JoyPOV2Right,
JoyPOV3Up,
JoyPOV3Down,
JoyPOV3Left,
JoyPOV3Right,
JoyButton0,
JoyButton1,
JoyButton2,
JoyButton3,
JoyButton4,
JoyButton5,
JoyButton6,
JoyButton7,
JoyButton8,
JoyButton9,
JoyButton10,
JoyButton11,
JoyButton12,
JoyButton13,
JoyButton14,
JoyButton15,
JoyButton16,
JoyButton17,
JoyButton18,
JoyButton19,
JoyButton20,
JoyButton21,
JoyButton22,
JoyButton23,
JoyButton24,
JoyButton25,
JoyButton26,
JoyButton27,
JoyButton28,
JoyButton29,
JoyButton30,
JoyButton31
};
struct MousePartsStruct
{
const char* id;
EMousePart msePart;
};
struct JoyPartsStruct
{
const char* id;
EJoyPart joyPart;
};
/*
* Struct that holds settings for a keyboard
*/
struct KeySettings
{
int kbdNum; // Keyboard number (or ANY_KEYBOARD for settings that apply to all keyboards)
unsigned sensitivity; // Key sensitivity for analog controls as percentage 1-100, where 100 is extremely responsive
unsigned decaySpeed; // Decay speed as percentage 1-200 of on speed
/*
* Creates a KeySettings with default settings
*/
KeySettings()
{
kbdNum = ANY_KEYBOARD;
sensitivity = DEFAULT_KEY_SENSITIVITY;
decaySpeed = DEFAULT_KEY_DECAYSPEED;
}
};
/*
* Struct that holds settings for a mouse
*/
struct MouseSettings
{
int mseNum; // Mouse number (or ANY_MOUSE for settings that apply to all mice)
unsigned xDeadZone; // X-Axis dead zone as a percentage 0-99 of display width
unsigned yDeadZone; // Y-Axis dead zone as a percentage 0-99 of display height
unsigned zDeadZone; // Z-Axis dead zone as a percentage 0-99 of axis range
/*
* Creates a MouseSettings with default settings
*/
MouseSettings()
{
mseNum = ANY_MOUSE;
xDeadZone = DEFAULT_MSE_DEADZONE;
yDeadZone = DEFAULT_MSE_DEADZONE;
zDeadZone = 0;
}
};
/*
* Struct that holds settings for a joystick
*/
struct JoySettings
{
int joyNum; // Joystick number (or ANY_JOYSTICK for settings that apply to all joysticks)
unsigned xDeadZone; // X-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned xSaturation; // X-Axis saturation as a percentage 1-100 of axis positive/negative ranges
unsigned yDeadZone; // Y-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned ySaturation; // Y-Axis saturation as a percentage 1-100 of axis positive/negative ranges
unsigned zDeadZone; // Z-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned zSaturation; // Z-Axis saturation as a percentage 1-100 of axis positive/negative ranges
unsigned rxDeadZone; // RX-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned rxSaturation; // RX-Axis saturation as a percentage 1-100 of joystick axis positive/negative ranges
unsigned ryDeadZone; // RY-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned rySaturation; // RY-Axis saturation as a percentage 1-100 of joystick axis positive/negative ranges
unsigned rzDeadZone; // RZ-Axis dead zone as a percentage 0-99 of axis positive/negative ranges
unsigned rzSaturation; // RZ-Axis saturation as a percentage 1-100 of joystick axis positive/negative ranges
/*
* Creates a JoySettings with default settings
*/
JoySettings()
{
joyNum = ANY_JOYSTICK;
xDeadZone = DEFAULT_JOY_DEADZONE;
xSaturation = DEFAULT_JOY_SATURATION;
yDeadZone = DEFAULT_JOY_DEADZONE;
ySaturation = DEFAULT_JOY_SATURATION;
zDeadZone = DEFAULT_JOY_DEADZONE;
zSaturation = DEFAULT_JOY_SATURATION;
rxDeadZone = DEFAULT_JOY_DEADZONE;
rxSaturation = DEFAULT_JOY_SATURATION;
ryDeadZone = DEFAULT_JOY_DEADZONE;
rySaturation = DEFAULT_JOY_SATURATION;
rzDeadZone = DEFAULT_JOY_DEADZONE;
rzSaturation = DEFAULT_JOY_SATURATION;
}
};
struct JoyDetails
{
bool hasXAxis; // Flags to indicate which axes available on joystick
bool hasYAxis;
bool hasZAxis;
bool hasRXAxis;
bool hasRYAxis;
bool hasRZAxis;
int numAxes; // Total number of axes on joystick
int numPOVs; // Total number of POV hat controllers on joystick
int numButtons; // Total number of buttons on joystick
};
/*
* Abstract base class that represents an input system. An input system encapsulates all the O/S dependent code to read keyboards,
* mice and joysticks.
*/
class CInputSystem
{
private:
// Array of valid key names
static const char *s_validKeyNames[];
// Lookup table for translating mouse mapping strings to their respective mouse parts
static MousePartsStruct s_mseParts[];
// Lookup table for translating joystick mapping strings to their respective joystick parts
static JoyPartsStruct s_joyParts[];
// Empty input source
static CMultiInputSource *s_emptySource;
// Number of keyboards, mice and joysticks
int m_numKbds;
int m_numMice;
int m_numJoys;
// Cached input sources
CInputSource **m_anyKeySources;
CInputSource **m_anyMseSources;
CInputSource **m_anyJoySources;
CInputSource ***m_keySources;
CInputSource ***m_mseSources;
CInputSource ***m_joySources;
// Default key, mouse and joystick settings
KeySettings m_defKeySettings;
MouseSettings m_defMseSettings;
JoySettings m_defJoySettings;
// Current key, mouse and joystick settings for attached keyboards, mice and joysticks
vector<KeySettings*> m_keySettings;
vector<MouseSettings*> m_mseSettings;
vector<JoySettings*> m_joySettings;
//
// Helper methods
//
/*
* Creates cache for all sources.
*/
void CreateSourceCache();
/*
* Deletes cache for all sources.
*/
void DeleteSourceCache();
/*
* Deletes an input source
*/
void DeleteSource(CInputSource *source);
/*
* Returns a key source for the given keyboard number (or all keyboards if ANY_KEYBOARD supplied) and key index
*/
CInputSource *GetKeySource(int kbdNum, int keyIndex);
/*
* Returns a mouse source for the given mouse number (or all mice if ANY_MOUSE supplied) and mouse index
*/
CInputSource *GetMouseSource(int mseNum, EMousePart msePart);
/*
* Returns a joystick source for the given joystick number (or all joysticks if ANY_JOYSTICK supplied) and joystick index
*/
CInputSource *GetJoySource(int joyNum, EJoyPart joyPart);
/*
* Finds any currently activated key sources for the given keyboard number (or all keyboards if ANY_KEYBOARD supplied)
* and adds them to the sources vector, aswell as constructing the corresponding mapping(s) in the given string.
* If fullAxisOnly is true, then only sources that represent a full axis range (eg MouseXAxis) are considered.
*/
void CheckKeySources(int kbdNum, bool fullAxisOnly, vector<CInputSource*> &sources, string &mapping);
/*
* Finds any currently activated mouse sources for the given mouse number (or all mice if ANY_MOUSE supplied)
* and adds them to the sources vector, aswell as constructing the corresponding mapping(s) in the given string.
* If fullAxisOnly is true, then only sources that represent a full axis range (eg MouseXAxis) are considered.
*/
void CheckMouseSources(int mseNum, bool fullAxisOnly, bool mseCentered, vector<CInputSource*> &sources, string &mapping);
/*
* Finds any currently activated joystick sources for the given joystick number (or all joysticks if ANY_JOYSTICK supplied)
* and adds them to the sources vector, aswell as constructing the corresponding mapping(s) in the given string.
* If fullAxisOnly is true, then only sources that represent a full axis range (eg MouseXAxis) are considered.
*/
void CheckJoySources(int joyNum, bool fullAxisOnly, vector<CInputSource*> &sources, string &mapping);
bool ParseInt(string str, int &num);
string IntToString(int num);
bool EqualsIgnoreCase(string str1, const char *str2);
bool StartsWithIgnoreCase(string str1, const char *str2);
/*
* Returns true if the given string represents a valid key name.
*/
bool IsValidKeyName(string str);
/*
* Returns the EMousePart with the given mapping name or MouseUnknown if not found.
*/
EMousePart LookupMousePart(string str);
/*
* Returns the mapping name for the given EMousePart.
*/
const char *LookupName(EMousePart msePart);
/*
* Returns the EJoyPart with the given mapping name or JoyUnknown if not found.
*/
EJoyPart LookupJoyPart(string str);
/*
* Returns the mapping name for the given EJoyPart.
*/
const char *LookupName(EJoyPart joyPart);
size_t ParseDevMapping(string str, const char *devType, int &devNum);
/*
* Parses the given mapping string, possibly representing more than one mapping, and returns an input source for it or NULL if the
* mapping is invalid.
* If fullAxisOnly is true, then only mappings that represent a full axis range (eg MouseXAxis) are parsed.
*/
CInputSource* ParseMultiSource(string str, bool fullAxisOnly, bool isOr);
/*
* Parses the given single mapping string and returns an input source for it, or NULL if non exists.
*/
CInputSource* ParseSingleSource(string str);
/*
* Prints the given key settings to stdout.
*/
void PrintKeySettings(KeySettings *settings);
/*
* Reads key settings from an INI file for the given keyboard number, or common settings if ANY_KEYBOARD specified.
* Returns NULL if no relevant settings were found in the INI file.
*/
KeySettings *ReadKeySettings(CINIFile *ini, const char *section, int kbdNum);
/*
* Writes the given key settings to an INI file, only writing out settings that are different to their defaults.
*/
void WriteKeySettings(CINIFile *ini, const char *section, KeySettings *settings);
/*
* Prints the given mouse settings to stdout.
*/
void PrintMouseSettings(MouseSettings *settings);
/*
* Reads mouse settings from an INI file for the given mouse number, or common settings if ANY_MOUSE specified.
* Returns NULL if no relevant settings were found in the INI file.
*/
MouseSettings *ReadMouseSettings(CINIFile *ini, const char *section, int mseNum);
/*
* Writes the given mouse settings to an INI file, only writing out settings that are different to their defaults.
*/
void WriteMouseSettings(CINIFile *ini, const char *section, MouseSettings *settings);
/*
* Prints the given joystick settings to stdout.
*/
void PrintJoySettings(JoySettings *settings);
/*
* Reads joystick settings from an INI file for the given joystick number, or common settings if ANY_JOYSTICK specified.
* Returns NULL if no relevant settings were found in the INI file.
*/
JoySettings *ReadJoySettings(CINIFile *ini, const char *section, int joyNum);
/*
* Writes the given joystick settings to an INI file, only writing out settings that are different to their defaults.
*/
void WriteJoySettings(CINIFile *ini, const char *section, JoySettings *settings);
protected:
// Current display geometry
unsigned m_dispX;
unsigned m_dispY;
unsigned m_dispW;
unsigned m_dispH;
// Flag to indicate if system currently being configured
bool m_isConfiguring;
/*
* Constructs an input system with the given name.
*/
CInputSystem(const char *systemName);
/*
* Returns the current key settings for given keyboard number, or common settings if ANY_KEYBOARD specified.
* If no settings are found and useDefault is false, NULL is returned. If useDefault is true then default settings are returned.
*/
KeySettings *GetKeySettings(int kbdNum, bool useDefault);
/*
* Returns the current mouse settings for given mouse number, or common settings if ANY_MOUSE specified.
* If no settings are found and useDefault is false, NULL is returned. If useDefault is true then default settings are returned.
*/
MouseSettings *GetMouseSettings(int mseNum, bool useDefault);
/*
* Returns the current joystick settings for given joystick number, or common settings if ANY_JOYSTICK specified.
* If no settings are found and useDefault is false, NULL is returned. If useDefault is true then default settings are returned.
*/
JoySettings *GetJoySettings(int joyNum, bool useDefault);
/*
* Returns true if the given EMousePart is an axis.
*/
bool IsAxis(EMousePart msePart);
/*
* Returns true if the given EMousePart represents a full axis, eg MouseXAxis or MouseXAxisInv.
*/
bool IsFullAxis(EMousePart msePart);
/*
* Returns true if the EMousePart represents an axis and sets axisPart and axisDir as follows:
* axisNum will be the axis number (AXIS_X, AXIS_Y or AXIS_Z)
* axisDir will be AXIS_FULL, AXIS_INVERTED, AXIS_POS or AXIS_POS depending on whether the axis has the full range, has
* the full range but inverted, is negative only, or is positive only.
*/
bool GetAxisDetails(EMousePart msePart, int &axisNum, int &axisDir);
/*
* Returns true if the given EMousePart represets a button, eg MouseButtonLeft.
*/
bool IsButton(EMousePart msePart);
/*
* Returns the button number (indexed from 0) of the given EMousePart if it is a button, or -1 otherwise.
*/
int GetButtonNumber(EMousePart msePart);
/*
* Returns the EMousePart that represents the axis component for the given axis number (AXIS_X, AXIS_Y or AXIS_Z) and direction
* (AXIS_FULL, AXIS_INVERTED, AXIS_POS or AXIS_POS), or MouseUnknown otherwise.
*/
EMousePart GetMouseAxis(int axisNum, int axisDir);
/*
* Returns the EMousePart that represents the mouse button with the given number (0-4), or MouseUnknown otherwise.
*/
EMousePart GetMouseButton(int butNum);
/*
* Returns true if the given EJoyPart is an axis.
*/
bool IsAxis(EJoyPart joyPart);
/*
* Returns true if the given EJoyPart represents a full axis, eg JoyXAxis.
*/
bool IsFullAxis(EJoyPart joyPart);
/*
* Returns true if joystick part represents an axis and sets axisPart and axisDir as follows:
* axisNum will be the axis number (AXIS_X, AXIS_Y, AXIS_Z, AXIS_RX, AXIS_RY or AXIS_RZ)
* axisDir is AXIS_FULL, AXIS_INVERTED, AXIS_POS or AXIS_POS depending on whether the axis has the full range, has
* the full range but inverted, is negative only, or is positive only.
*/
bool GetAxisDetails(EJoyPart joyPart, int &axisNum, int &axisDir);
/*
* Returns true if the given EJoyPart represents a POV hat direction, eg JoyPOV0Left.
*/
bool IsPOV(EJoyPart joyPart);
/*
* Returns true if the EJoyPart represents a POV hat direction and sets povNum and povDir as follows:
* povNum will be the POV hat number 0-4,
* povDir will be POV_UP, POV_DOWN, POV_LEFT or POV_RIGHT.
*/
bool GetPOVDetails(EJoyPart joyPart, int &povNum, int &povDir);
/*
* Returns true if the given EJoyPart is a button
*/
bool IsButton(EJoyPart joyPart);
/*
* Returns the button number (indexed from 0) of the given EJoyPart if it is a button, or -1 otherwise.
*/
int GetButtonNumber(EJoyPart joyPart);
/*
* Returns the EJoyPart that represents the axis component for the given axis number (AXIS_X, AXIS_Y, AXIS_Z, AXIS_RX, AXIS_RY or AXIS_RZ) and
* direction (AXIS_FULL, AXIS_INVERTED, AXIS_POS or AXIS_POS), or JoyUnknown otherwise.
*/
EJoyPart GetJoyAxis(int axisNum, int axisDir);
/*
* Returns the EJoyPart that represents the POV hot direction for the given POV number (0-4) and direction (POV_UP, POV_DOWN,
* POV_LEFT or POV_RIGHT), JoyUnknown otherwise.
*/
EJoyPart GetJoyPOV(int povNum, int povDir);
/*
* Returns the EJoyPart that represents the joystick button with the given number (0-31), or JoyUnknown otherwise.
*/
EJoyPart GetJoyButton(int butNum);
//
// Abstract methods subclass must implement (ie system-specific code)
//
virtual bool InitializeSystem() = 0;
/*
* Returns the number of attached keyboards (or 0 if the system cannot handle keyboards at all or ANY_KEYBOARD if the system cannot
* handle multiple keyboards).
*/
virtual int GetNumKeyboards() = 0;
/*
* Returns the number of attached mice (or 0 if the system cannot handle mice at all or ANY_MOUSE if the system cannot handle
* multiple mice).
*/
virtual int GetNumMice() = 0;
/*
* Returns number of attached joysticks (or 0 if the system cannot handle joysticks at all or ANY_JOYSTICK if the system cannot
* handle multiple joysticks).
*/
virtual int GetNumJoysticks() = 0;
/*
* Returns the system-specific key index that represents the given key name.
*/
virtual int GetKeyIndex(const char *keyName) = 0;
/*
* Returns the key name of the given system-specific integer key index.
*/
virtual const char *GetKeyName(int keyIndex) = 0;
/*
* Returns details about the joystick with the given number, or NULL if it does not exist.
*/
virtual JoyDetails *GetJoyDetails(int joyNum) = 0;
/*
* Returns true if for the given keyboard the key with the system-specific key index is currently pressed.
*/
virtual bool IsKeyPressed(int kbdNum, int keyIndex) = 0;
/*
* Returns the current axis value for the given mouse and axis number (AXIS_X, AXIS_Y or AXIS_Z).
*/
virtual int GetMouseAxisValue(int mseNum, int axisNum) = 0;
/*
* Returns the current direction (-1, 0 or 1) the Z-axis (wheel) is moving in for the given mouse.
*/
virtual int GetMouseWheelDir(int mseNum) = 0;
/*
* Returns true if for the given mouse the button with the given number is currently pressed.
*/
virtual bool IsMouseButPressed(int mseNum, int butNum) = 0;
/*
* Returns the current axis value for the given joystick and axis number (AXIS_X, AXIS_Y, AXIS_Z, AXIS_RX, AXIS_RY or AXIS_RZ).
*/
virtual int GetJoyAxisValue(int joyNum, int axisNum) = 0;
/*
* Returns true if for the given joystick the POV-hat controller with the given number is pointing in a particular direction (POV_UP, POV_DOWN,
* POV_LEFT or POV_RIGHT)
*/
virtual bool IsJoyPOVInDir(int joyNum, int povNum, int povDir) = 0;
/*
* Returns true if for the given joystick the button with the given number is currently pressed.
*/
virtual bool IsJoyButPressed(int joyNum, int butNum) = 0;
/*
* Waits for the given time in milliseconds
*/
virtual void Wait(int ms) = 0;
//
// Virtual methods subclass can override if required
//
/*
* Returns true if the mouse is currently centered in the display.
*/
virtual bool ConfigMouseCentered();
/*
* Creates an input source combining all keyboards for the given key index.
*/
virtual CInputSource *CreateAnyKeySource(int keyIndex);
/*
* Creates an input source combining all mice for the given EMousePart.
*/
virtual CInputSource *CreateAnyMouseSource(EMousePart msePart);
/*
* Creates an input source combining all joysticks for the given EJoyPart.
*/
virtual CInputSource *CreateAnyJoySource(EJoyPart joyPart);
/*
* Creates an input source for the given keyboard number and key index.
*/
virtual CInputSource *CreateKeySource(int kbdNum, int keyIndex);
/*
* Creates an input source for the given mouse number and EMousePart.
*/
virtual CInputSource *CreateMouseSource(int mseNum, EMousePart msePart);
/*
* Creates an input source for the given joystick number and EJoyPart.
*/
virtual CInputSource *CreateJoySource(int joyNum, EJoyPart joyPart);
public:
// Name of this input system
const char *name;
virtual ~CInputSystem();
/*
* Initializes the input system. Must be called before any other methods are used.
* Returns false if unable to initialize the system.
*/
bool Initialize();
/*
* Sets the current display geometry so that mouse movements can be scaled properly.
*/
void SetDisplayGeom(unsigned dispX, unsigned dispY, unsigned dispW, unsigned dispH);
/*
* Returns the input source for the given mapping, or NULL if mapping is not valid.
*/
CInputSource* ParseSource(const char *mapping, bool fullAxisOnly = false);
/*
* Clears all keyboard, mouse and joystick settings.
*/
void ClearSettings();
/*
* Prints current keyboard, mouse and joystick settings to stdout.
*/
void PrintSettings();
/*
* Reads all keyboard, mouse and joystick settings (and any additional system-specific additional settings) from the given INI file.
*/
virtual void ReadFromINIFile(CINIFile *ini, const char *section);
/*
* Writes all keyboard, mouse and joystick settings (and any additional system-specific settings) to the given INI file.
*/
virtual void WriteToINIFile(CINIFile *ini, const char *section);
/*
* Waits for any input from the user and once received copies a mapping configuration representing the input (eg KEY_A or JOY1_AXIS_POS)
* into the given buffer.
* Returns true if input was successfully received or false if the user activated the given escape mapping or closed the window.
* readFlags specifies which types of inputs (keyboards, mice, joysticks) are to be read and whether to merge the inputs to a common
* mapping, eg return MOUSE_XAXIS rather than MOUSE3_XAXIS.
* If fullAxisOnly is true, then only mappings representing a full axis are returned, eg JOY1_XAXIS is allowed but not JOY1_XAXIS_POS.
*/
bool ReadMapping(char *buffer, unsigned bufSize, bool fullAxisOnly, unsigned readFlags = READ_ALL, const char *escapeMapping = "KEY_ESCAPE");
/*
* Updates the current state of the input system (called by CInputs.Poll).
*/
virtual bool Poll() = 0;
/*
* Lets the input system know that inputs are being configured (called by CInputs.ConfigureInputs).
*/
virtual void ConfigStart();
/*
* Lets the input system know that inputs are no longer being configured (called by CInputs.ConfigureInputs).
*/
virtual void ConfigEnd();
/*
* Sets the mouse visibility (some systems may choose to ignore this).
*/
virtual void SetMouseVisibility(bool visible) = 0;
//
// Nested Classes
//
/*
* Input source for a key on a keyboard.
*/
class CKeyInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_kbdNum; // Keyboard number
int m_keyIndex; // Key index
int m_incr; // Key increment for analog values
int m_decr; // Key decrement for analog values
int m_val; // Current analog key value
int m_maxVal; // Maximum analog key value
public:
CKeyInputSource(CInputSystem *system, int kbdNum, int keyIndex, unsigned sensitivity, unsigned decaySpeed);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
/*
* Input source for the X- or Y-axis of a mouse.
*/
class CMseAxisInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_mseNum; // Mouse number
int m_axisNum; // Axis number (0 = XAxis, 1 = YAxis, 2 = ZAxis)
int m_axisDir; // Axis direction (AXIS_FULL, AXIS_INVERTED, AXIS_POSITIVE or AXIS_NEGATIVE)
int m_deadPixels; // Size in pixels of dead zone in centre of axis
/*
* Scales the mouse axis value to the given range.
*/
int ScaleAxisValue(int minVal, int offVal, int maxVal);
public:
CMseAxisInputSource(CInputSystem *system, int mseNum, int axisNum, int axisDir, unsigned deadZone);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
/*
* Input source for the button of a mouse.
*/
class CMseButInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_mseNum; // Mouse number
int m_butNum; // Button number
public:
CMseButInputSource(CInputSystem *system, int mseNum, int butNum);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
/*
* Input source for the axis of a joystick.
*/
class CJoyAxisInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_joyNum; // Joystick number
int m_axisNum; // Axis number (0 = XAxis, 1 = YAxis, 2 = ZAxis, 3 = RXAxis, 4 = RYAxis, 5 = RZAxis)
int m_axisDir; // Axis direction (AXIS_FULL, AXIS_INVERTED, AXIS_POSITIVE or AXIS_NEGATIVE)
int m_posDZone; // Dead zone for positive range
int m_negDZone; // Dead zone for negative range
int m_posSat; // Saturation for positive range
int m_negSat; // Saturation for negative range
/*
* Scales the joystick axis value to the given range.
*/
int ScaleAxisValue(int minVal, int offVal, int maxVal);
public:
CJoyAxisInputSource(CInputSystem *system, int joyNum, int axisNum, int axisDir, unsigned deadZone, unsigned saturation);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
/*
* Input source for a particular direction of a POV hat controller of a joystick.
*/
class CJoyPOVInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_joyNum; // Joystick number
int m_povNum; // POV hat number
int m_povDir; // POV hat direction (POV_UP, POV_LEFT, POV_RIGHT, POV_DOWN)
public:
CJoyPOVInputSource(CInputSystem *system, int joyNum, int povNum, int povDir);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
/*
* Input source for the button of a joystick.
*/
class CJoyButInputSource : public CInputSource
{
private:
CInputSystem *m_system; // Parent input system
int m_joyNum; // Joystick number
int m_butNum; // Button number
public:
CJoyButInputSource(CInputSystem *system, int joyNum, int butNum);
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
};
#endif // INCLUDED_INPUTSYSTEM_H

148
Inputs/InputTypes.cpp Normal file
View file

@ -0,0 +1,148 @@
#include "Supermodel.h"
// All the input subclasses have been grouped together here as they are very simple classes
/*
* CSwitchInput
*/
CSwitchInput::CSwitchInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping, UINT16 offVal, UINT16 onVal) :
CInput(inputId, inputLabel, INPUT_FLAGS_SWITCH, inputGameFlags, defaultMapping), m_offVal(offVal), m_onVal(onVal)
{
//
}
void CSwitchInput::Poll()
{
prevValue = value;
bool boolValue = !!value;
if (m_source != NULL && m_source->GetValueAsSwitch(boolValue))
value = (boolValue ? m_onVal : m_offVal);
else
value = m_offVal;
}
bool CSwitchInput::Pressed()
{
return prevValue == m_offVal && value == m_onVal;
}
bool CSwitchInput::Released()
{
return prevValue == m_onVal && value == m_offVal;
}
/*
* CAnalogInput
*/
CAnalogInput::CAnalogInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping, UINT16 minVal, UINT16 maxVal) :
CInput(inputId, inputLabel, INPUT_FLAGS_ANALOG, inputGameFlags, defaultMapping, minVal), m_minVal(minVal), m_maxVal(maxVal)
{
//
}
void CAnalogInput::Poll()
{
prevValue = value;
if (m_source == NULL)
{
value = m_minVal;
return;
}
int intValue = value;
if (m_source->GetValueAsAnalog(intValue, m_minVal, m_minVal, m_maxVal))
value = intValue;
else
value = m_minVal;
}
bool CAnalogInput::HasValue()
{
return value > m_minVal;
}
double CAnalogInput::ValueAsFraction()
{
double frac = (double)(value - m_minVal)/(double)(m_maxVal - m_minVal);
return (frac >= 0.0 ? frac : -frac);
}
/*
* CAxisInput
*/
CAxisInput::CAxisInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping,
CAnalogInput *negInput, CAnalogInput *posInput, UINT16 minVal, UINT16 offVal, UINT16 maxVal) :
CInput(inputId, inputLabel, INPUT_FLAGS_AXIS, inputGameFlags, defaultMapping, offVal), m_negInput(negInput), m_posInput(posInput),
m_minVal(minVal), m_offVal(offVal), m_maxVal(maxVal)
{
//
}
void CAxisInput::Poll()
{
prevValue = value;
// Try getting value from analog inputs that represent negative and positive range of the axis first and then try the default input source
int intValue = value;
if ((m_negInput != NULL && m_negInput->HasValue()) || (m_posInput != NULL && m_posInput->HasValue()))
{
if (m_maxVal > m_minVal)
{
value = m_offVal;
if (m_posInput != NULL) value += (int)(m_posInput->ValueAsFraction() * (double)(m_maxVal - m_offVal));
if (m_negInput != NULL) value -= (int)(m_negInput->ValueAsFraction() * (double)(m_offVal - m_minVal));
}
else
{
value = m_offVal;
if (m_posInput != NULL) value += (int)(m_posInput->ValueAsFraction() * (double)(m_offVal - m_maxVal));
if (m_negInput != NULL) value -= (int)(m_negInput->ValueAsFraction() * (double)(m_minVal - m_offVal));
}
}
else if (m_source != NULL && m_source->GetValueAsAnalog(intValue, m_minVal, m_offVal, m_maxVal))
value = intValue;
else
value = m_offVal;
}
bool CAxisInput::HasValue()
{
return value != m_offVal;
}
double CAxisInput::ValueAsFraction()
{
double frac = (double)(value - m_minVal)/(double)(m_maxVal - m_minVal);
return (frac >= 0.0 ? frac : -frac);
}
/*
* CGearShift4Input
*/
CGearShift4Input::CGearShift4Input(const char *inputId, const char *inputLabel, unsigned inputGameFlags,
CSwitchInput *shift1Input, CSwitchInput *shift2Input, CSwitchInput *shift3Input, CSwitchInput *shift4Input,
CSwitchInput *shiftUpInput, CSwitchInput *shiftDownInput) :
CInput(inputId, inputLabel, INPUT_FLAGS_VIRTUAL, inputGameFlags),
m_shift1Input(shift1Input), m_shift2Input(shift2Input), m_shift3Input(shift3Input), m_shift4Input(shift4Input),
m_shiftUpInput(shiftUpInput), m_shiftDownInput(shiftDownInput)
{
//
}
void CGearShift4Input::Poll()
{
prevValue = value;
// Neutral is when all gear buttons are released so shifting here is implemented as follows:
// Gears (values 1-4) are set by pressing a button (lower gears have priority) and "stick" until a shift to another gear or until the
// button is pressed again, at which point neutral (value 0) is assumed.
if (m_shift1Input->Pressed()) value = (value == 1 ? 0 : 1);
else if (m_shift2Input->Pressed()) value = (value == 2 ? 0 : 2);
else if (m_shift3Input->Pressed()) value = (value == 3 ? 0 : 3);
else if (m_shift4Input->Pressed()) value = (value == 4 ? 0 : 4);
// Also the shift up/down controls can increase/decrease the gears too
if (m_shiftUpInput->Pressed()) value = CInputSource::Clamp(value + 1, 0, 4);
else if (m_shiftDownInput->Pressed()) value = CInputSource::Clamp(value - 1, 0, 4);
}

135
Inputs/InputTypes.h Normal file
View file

@ -0,0 +1,135 @@
#ifndef INCLUDED_INPUTTYPES_H
#define INCLUDED_INPUTTYPES_H
#include "Types.h"
#include "Input.h"
// All the input subclasses have been grouped together here as they are very simple classes
/*
* Represents a switch input (such as a button or 8-way joystick switch) whose value can be either on (usually 1) or off (usually 0).
*/
class CSwitchInput : public CInput
{
private:
// On and off values
UINT16 m_offVal;
UINT16 m_onVal;
public:
CSwitchInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping,
UINT16 offVal = 0x00, UINT16 onVal = 0x01);
/*
* Polls (updates) the input, updating its value from the input source
*/
void Poll();
/*
* Returns true if the input was pressed during last update (ie currently on but previously off)
*/
bool Pressed();
/*
* Returns true if the input was released duing last update (ie currently off but previously on)
*/
bool Released();
};
/*
* Represents an analog input (such as a pedal) whose value ranges from a minimum 'off' value (usually 0) to a maximum 'on' value (usually 0xFF).
*/
class CAnalogInput : public CInput
{
private:
// Min and max values
UINT16 m_minVal;
UINT16 m_maxVal;
public:
CAnalogInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping,
UINT16 minVal = 0x00, UINT16 maxVal = 0xFF);
/*
* Polls (updates) the input, updating its value from the input source
*/
void Poll();
/*
* Returns true if the input is currently activated
*/
bool HasValue();
/*
* Returns current value as a fraction 0.0 to 1.0 of the full range
*/
double ValueAsFraction();
};
/*
* Represents an axis input (such as a joystick axis, lightgun axis or steering wheel) whose value ranges from a minimum value (usually 0) to a maximum
* value (usually 0xFF) with an off value (usually 0x80) somewhere in-between.
* As well as having its own input source it takes two analog inputs that can represent the negative and positive ranges of the axis.
*/
class CAxisInput : public CInput
{
private:
// Analog inputs that represent the negative and positive range of the axis
CAnalogInput *m_negInput;
CAnalogInput *m_posInput;
// Min, off and max values
UINT16 m_minVal;
UINT16 m_offVal;
UINT16 m_maxVal;
public:
CAxisInput(const char *inputId, const char *inputLabel, unsigned inputGameFlags, const char *defaultMapping, CAnalogInput *negInput, CAnalogInput *posInput,
UINT16 minVal = 0x00, UINT16 offVal = 0x80, UINT16 maxVal = 0xFF);
/*
* Polls (updates) the input, updating its value from the switch inputs and/or input source
*/
void Poll();
/*
* Returns true if the input is currently activated (ie axis is not centered)
*/
bool HasValue();
/*
* Returns current value as a fraction -1.0 to 1.0
*/
double ValueAsFraction();
};
/*
* Represents a 4-gear shift input whose value ranges from 0 to 4, where neutral is 0 and 1-4 represents a gear selection.
* It takes six switch inputs that combine together to control the gear shifting. The first four inputs can set the gear
* directly, while the last two allow shifting up or down through the gears.
*/
class CGearShift4Input : public CInput
{
private:
// Four switch inputs for gears 1-4
CSwitchInput *m_shift1Input;
CSwitchInput *m_shift2Input;
CSwitchInput *m_shift3Input;
CSwitchInput *m_shift4Input;
// Two switch inputs for up/down gear
CSwitchInput *m_shiftUpInput;
CSwitchInput *m_shiftDownInput;
public:
CGearShift4Input(const char *inputId, const char *inputLabel, unsigned inputGameFlags,
CSwitchInput *shift1Input, CSwitchInput *shift2Input, CSwitchInput *shift3Input, CSwitchInput *shift4Input,
CSwitchInput *shiftUpInput, CSwitchInput *shiftDownInput);
/*
* Polls (updates) the input, updating its value from the switch inputs
*/
void Poll();
};
#endif // INCLUDED_INPUTTYPES_H

535
Inputs/Inputs.cpp Normal file
View file

@ -0,0 +1,535 @@
#include "Supermodel.h"
#include <stdarg.h>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
#define UI_INPUT 0
CInputs::CInputs(CInputSystem *system) : m_system(system)
{
// UI Controls
uiExit = AddSwitchInput("UIExit", "Exit UI", UI_INPUT, "KEY_ESCAPE");
uiReset = AddSwitchInput("UIReset", "Reset", UI_INPUT, "KEY_ALT+KEY_R");
uiPause = AddSwitchInput("UIPause", "Pause", UI_INPUT, "KEY_PAUSE");
uiSaveState = AddSwitchInput("UISaveState", "Save State", UI_INPUT, "KEY_F5");
uiChangeSlot = AddSwitchInput("UIChangeSlot", "Change Save Slot", UI_INPUT, "KEY_F6");
uiLoadState = AddSwitchInput("UILoadState", "Load State", UI_INPUT, "KEY_F7");
uiDumpInpState = AddSwitchInput("UIDumpInputState", "Dump Input State", UI_INPUT, "KEY_F8");
uiClearNVRAM = AddSwitchInput("UIClearNVRAM", "Clear NVRAM", UI_INPUT, "KEY_ALT+KEY_N");
uiToggleCursor = AddSwitchInput("UIToggleCursor", "Toggle Cursor", UI_INPUT, "KEY_ALT+KEY_I");
uiToggleFrLimit = AddSwitchInput("UIToggleFrameLimit", "Toggle Frame Limiting", UI_INPUT, "KEY_ALT+KEY_T");
// Common Controls
start[0] = AddSwitchInput("Start1", "P1 Start", GAME_INPUT_COMMON, "KEY_1,JOY1_BUTTON9");
start[1] = AddSwitchInput("Start2", "P2 Start", GAME_INPUT_COMMON, "KEY_2,JOY2_BUTTON9");
coin[0] = AddSwitchInput("Coin1", "P1 Coin", GAME_INPUT_COMMON, "KEY_3,JOY1_BUTTON10");
coin[1] = AddSwitchInput("Coin2", "P2 Coin", GAME_INPUT_COMMON, "KEY_4,JOY2_BUTTON10");
service[0] = AddSwitchInput("ServiceA", "Service A", GAME_INPUT_COMMON, "KEY_5");
service[1] = AddSwitchInput("ServiceB", "Service B", GAME_INPUT_COMMON, "KEY_7");
test[0] = AddSwitchInput("TestA", "Test A", GAME_INPUT_COMMON, "KEY_6");
test[1] = AddSwitchInput("TestB", "Test B", GAME_INPUT_COMMON, "KEY_8");
// 8-Way Joysticks
up[0] = AddSwitchInput("JoyUp", "P1 Joystick Up", GAME_INPUT_JOYSTICK1, "KEY_UP,JOY1_UP");
down[0] = AddSwitchInput("JoyDown", "P1 Joystick Down", GAME_INPUT_JOYSTICK1, "KEY_DOWN,JOY1_DOWN");
left[0] = AddSwitchInput("JoyLeft", "P1 Joystick Left", GAME_INPUT_JOYSTICK1, "KEY_LEFT,JOY1_LEFT");
right[0] = AddSwitchInput("JoyRight", "P1 Joystick Right", GAME_INPUT_JOYSTICK1, "KEY_RIGHT,JOY1_RIGHT");
up[1] = AddSwitchInput("JoyUp2", "P2 Joystick Up", GAME_INPUT_JOYSTICK2, "JOY2_UP");
down[1] = AddSwitchInput("JoyDown2", "P2 Joystick Down", GAME_INPUT_JOYSTICK2, "JOY2_DOWN");
left[1] = AddSwitchInput("JoyLeft2", "P2 Joystick Left", GAME_INPUT_JOYSTICK2, "JOY2_LEFT");
right[1] = AddSwitchInput("JoyRight2", "P2 Joystick Right", GAME_INPUT_JOYSTICK2, "JOY2_RIGHT");
// Fighting Game Buttons
punch[0] = AddSwitchInput("Punch", "P1 Punch", GAME_INPUT_FIGHTING, "KEY_A,JOY1_BUTTON1");
kick[0] = AddSwitchInput("Kick", "P1 Kick", GAME_INPUT_FIGHTING, "KEY_S,JOY1_BUTTON2");
guard[0] = AddSwitchInput("Guard", "P1 Guard", GAME_INPUT_FIGHTING, "KEY_D,JOY1_BUTTON3");
escape[0] = AddSwitchInput("Escape", "P1 Escape", GAME_INPUT_FIGHTING, "KEY_F,JOY1_BUTTON4");
punch[1] = AddSwitchInput("Punch2", "P2 Punch", GAME_INPUT_FIGHTING, "JOY2_BUTTON1");
kick[1] = AddSwitchInput("Kick2", "P2 Kick", GAME_INPUT_FIGHTING, "JOY2_BUTTON2");
guard[1] = AddSwitchInput("Guard2", "P2 Guard", GAME_INPUT_FIGHTING, "JOY2_BUTTON3");
escape[1] = AddSwitchInput("Escape2", "P2 Escape", GAME_INPUT_FIGHTING, "JOY2_BUTTON4");
// Virtua Striker Buttons
shortPass[0] = AddSwitchInput("ShortPass", "P1 Short Pass", GAME_INPUT_SOCCER, "KEY_A,JOY1_BUTTON1");
longPass[0] = AddSwitchInput("LongPass", "P1 Long Pass", GAME_INPUT_SOCCER, "KEY_S,JOY1_BUTTON2");
shoot[0] = AddSwitchInput("Shoot", "P1 Shoot", GAME_INPUT_SOCCER, "KEY_D,JOY1_BUTTON3");
shortPass[1] = AddSwitchInput("ShortPass1", "P2 Short Pass", GAME_INPUT_SOCCER, "JOY2_BUTTON1");
longPass[1] = AddSwitchInput("LongPass1", "P2 Long Pass", GAME_INPUT_SOCCER, "JOY2_BUTTON2");
shoot[1] = AddSwitchInput("Shoot1", "P2 Shoot", GAME_INPUT_SOCCER, "JOY2_BUTTON3");
// Racing Game Steering Controls
CAnalogInput* steeringLeft = AddAnalogInput("SteeringLeft", "Steer Left", GAME_INPUT_VEHICLE, "KEY_LEFT");
CAnalogInput* steeringRight = AddAnalogInput("SteeringRight", "Steer Right", GAME_INPUT_VEHICLE, "KEY_RIGHT");
steering = AddAxisInput ("Steering", "Full Steering", GAME_INPUT_VEHICLE, "JOY1_XAXIS", steeringLeft, steeringRight);
accelerator = AddAnalogInput("Accelerator", "Accelerator Pedal", GAME_INPUT_VEHICLE, "KEY_UP,JOY1_UP");
brake = AddAnalogInput("Brake", "Brake Pedal", GAME_INPUT_VEHICLE, "KEY_DOWN,JOY1_DOWN");
// Racing Game Gear Shift
CSwitchInput *shift1 = AddSwitchInput("GearShift1", "Shift 1/Up", GAME_INPUT_SHIFT4, "KEY_Q,JOY1_BUTTON5");
CSwitchInput *shift2 = AddSwitchInput("GearShift2", "Shift 2/Down", GAME_INPUT_SHIFT4, "KEY_W,JOY1_BUTTON6");
CSwitchInput *shift3 = AddSwitchInput("GearShift3", "Shift 3", GAME_INPUT_SHIFT4, "KEY_E,JOY1_BUTTON7");
CSwitchInput *shift4 = AddSwitchInput("GearShift4", "Shift 4", GAME_INPUT_SHIFT4, "KEY_R,JOY1_BUTTON8");
CSwitchInput *shiftUp = AddSwitchInput("GearShiftUp", "Shift Up", GAME_INPUT_SHIFT4, "NONE");
CSwitchInput *shiftDown = AddSwitchInput("GearShiftDown", "Shift Down", GAME_INPUT_SHIFT4, "NONE");
gearShift4 = AddGearShift4Input("GearShift", "Gear Shift", GAME_INPUT_SHIFT4, shift1, shift2, shift3, shift4, shiftUp, shiftDown);
// Racing Game VR View Buttons
vr[0] = AddSwitchInput("VR1", "VR1", GAME_INPUT_VR, "KEY_A,JOY1_BUTTON1");
vr[1] = AddSwitchInput("VR2", "VR2", GAME_INPUT_VR, "KEY_S,JOY1_BUTTON2");
vr[2] = AddSwitchInput("VR3", "VR3", GAME_INPUT_VR, "KEY_D,JOY1_BUTTON3");
vr[3] = AddSwitchInput("VR4", "VR4", GAME_INPUT_VR, "KEY_F,JOY1_BUTTON4");
// Sega Rally Buttons
viewChange = AddSwitchInput("ViewChange", "View Change", GAME_INPUT_RALLY, "KEY_A,JOY1_BUTTON1");
handBrake = AddSwitchInput("HandBrake", "Hand Brake", GAME_INPUT_RALLY, "KEY_S,JOY1_BUTTON2");
// Virtua On Controls
twinJoyTurnLeft = AddSwitchInput("TwinJoyTurnLeft", "Turn Left", GAME_INPUT_TWIN_JOYSTICKS, "KEY_Q,JOY1_RZAXIS_POS");
twinJoyTurnRight = AddSwitchInput("TwinJoyTurnRight", "Turn Right", GAME_INPUT_TWIN_JOYSTICKS, "KEY_W,JOY1_RZAXIS_NEG");
twinJoyForward = AddSwitchInput("TwinJoyForward", "Forward", GAME_INPUT_TWIN_JOYSTICKS, "KEY_UP,JOY1_UP");
twinJoyReverse = AddSwitchInput("TwinJoyReverse", "Reverse", GAME_INPUT_TWIN_JOYSTICKS, "KEY_DOWN,JOY1_DOWN");
twinJoyStrafeLeft = AddSwitchInput("TwinJoyStrafeLeft", "Strafe Left", GAME_INPUT_TWIN_JOYSTICKS, "KEY_LEFT,JOY1_LEFT");
twinJoyStrafeRight = AddSwitchInput("TwinJoyStrafeRight", "Strafe Right", GAME_INPUT_TWIN_JOYSTICKS, "KEY_RIGHT,JOY1_RIGHT");
twinJoyJump = AddSwitchInput("TwinJoyJump", "Jump", GAME_INPUT_TWIN_JOYSTICKS, "KEY_E,JOY1_BUTTON1");
twinJoyCrouch = AddSwitchInput("TwinJoyCrouch", "Crouch", GAME_INPUT_TWIN_JOYSTICKS, "KEY_R,JOY1_BUTTON2");
twinJoyLeftShot = AddSwitchInput("TwinJoyLeftShot", "Left Shot Trigger", GAME_INPUT_TWIN_JOYSTICKS, "KEY_A,JOY1_BUTTON5");
twinJoyRightShot = AddSwitchInput("TwinJoyRightShot", "Right Shot Trigger", GAME_INPUT_TWIN_JOYSTICKS, "KEY_S,JOY1_BUTTON6");
twinJoyLeftTurbo = AddSwitchInput("TwinJoyLeftTurbo", "Left Turbo", GAME_INPUT_TWIN_JOYSTICKS, "KEY_Z,JOY1_BUTTON7");
twinJoyRightTurbo = AddSwitchInput("TwinJoyRightTurbo", "Right Turbo", GAME_INPUT_TWIN_JOYSTICKS, "KEY_X,JOY1_BUTTON8");
// Analog Joystick
CAnalogInput *analogJoyLeft = AddAnalogInput("AnalogJoyLeft", "Analog Left", GAME_INPUT_ANALOG_JOYSTICK, "KEY_LEFT");
CAnalogInput *analogJoyRight = AddAnalogInput("AnalogJoyRight", "Analog Right", GAME_INPUT_ANALOG_JOYSTICK, "KEY_RIGHT");
CAnalogInput *analogJoyUp = AddAnalogInput("AnalogJoyUp", "Analog Up", GAME_INPUT_ANALOG_JOYSTICK, "KEY_UP");
CAnalogInput *analogJoyDown = AddAnalogInput("AnalogJoyDown", "Analog Down", GAME_INPUT_ANALOG_JOYSTICK, "KEY_DOWN");
analogJoyX = AddAxisInput ("AnalogJoyX", "Analog X-Axis", GAME_INPUT_ANALOG_JOYSTICK, "JOY_XAXIS,MOUSE_XAXIS", analogJoyLeft, analogJoyRight);
analogJoyY = AddAxisInput ("AnalogJoyY", "Analog Y-Axis", GAME_INPUT_ANALOG_JOYSTICK, "JOY_YAXIS,MOUSE_YAXIS", analogJoyUp, analogJoyDown);
analogJoyTrigger = AddSwitchInput("AnalogJoyTrigger", "Trigger Button", GAME_INPUT_ANALOG_JOYSTICK, "KEY_A,JOY_BUTTON1,MOUSE_LEFT_BUTTON");
analogJoyEvent = AddSwitchInput("AnalogJoyEvent", "Event Button", GAME_INPUT_ANALOG_JOYSTICK, "KEY_S,JOY_BUTTON2,MOUSE_RIGHT_BUTTON");
// Lightguns
CAnalogInput *gun1Left = AddAnalogInput("GunLeft", "P1 Gun Left", GAME_INPUT_GUN1, "KEY_LEFT");
CAnalogInput *gun1Right = AddAnalogInput("GunRight", "P1 Gun Right", GAME_INPUT_GUN1, "KEY_RIGHT");
CAnalogInput *gun1Up = AddAnalogInput("GunUp", "P1 Gun Up", GAME_INPUT_GUN1, "KEY_UP");
CAnalogInput *gun1Down = AddAnalogInput("GunDown", "P1 Gun Down", GAME_INPUT_GUN1, "KEY_DOWN");
gunX[0] = AddAxisInput ("GunX", "P1 Gun X-Axis", GAME_INPUT_GUN1, "JOY1_XAXIS,MOUSE_XAXIS", gun1Left, gun1Right, 150, 400, 651); // normalize to [150,651]
gunY[0] = AddAxisInput ("GunY", "P1 Gun Y-Axis", GAME_INPUT_GUN1, "JOY1_YAXIS,MOUSE_YAXIS", gun1Up, gun1Down, 80, 272, 465); // normalize to [80,465]
trigger[0] = AddSwitchInput("Trigger", "P1 Trigger", GAME_INPUT_GUN1, "KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON");
offscreen[0] = AddSwitchInput("Offscreen", "P1 Point Off-screen", GAME_INPUT_GUN1, "KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON");
CAnalogInput *gun2Left = AddAnalogInput("GunLeft2", "P2 Gun Left", GAME_INPUT_GUN2, "NONE");
CAnalogInput *gun2Right = AddAnalogInput("GunRight2", "P2 Gun Right", GAME_INPUT_GUN2, "NONE");
CAnalogInput *gun2Up = AddAnalogInput("GunUp2", "P2 Gun Up", GAME_INPUT_GUN2, "NONE");
CAnalogInput *gun2Down = AddAnalogInput("GunDown2", "P2 Gun Down", GAME_INPUT_GUN2, "NONE");
gunX[1] = AddAxisInput ("GunX2", "P2 Gun X-Axis", GAME_INPUT_GUN2, "JOY2_XAXIS", gun2Left, gun2Right, 150, 400, 651); // normalize to [150,651]
gunY[1] = AddAxisInput ("GunY2", "P2 Gun Y-Axis", GAME_INPUT_GUN2, "JOY2_YAXIS", gun2Up, gun2Down, 80, 272, 465); // normalize to [80,465]
trigger[1] = AddSwitchInput("Trigger2", "P2 Trigger", GAME_INPUT_GUN2, "JOY2_BUTTON1");
offscreen[1] = AddSwitchInput("Offscreen2", "P2 Point Off-screen", GAME_INPUT_GUN2, "JOY2_BUTTON2");
}
CInputs::~CInputs()
{
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
delete *it;
}
CSwitchInput* CInputs::AddSwitchInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
UINT16 offVal, UINT16 onVal)
{
CSwitchInput *input = new CSwitchInput(id, label, gameFlags, defaultMapping, offVal, onVal);
m_inputs.push_back(input);
return input;
}
CAnalogInput* CInputs::AddAnalogInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
UINT16 minVal, UINT16 maxVal)
{
CAnalogInput *input = new CAnalogInput(id, label, gameFlags, defaultMapping, minVal, maxVal);
m_inputs.push_back(input);
return input;
}
CAxisInput* CInputs::AddAxisInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
CAnalogInput* axisNeg, CAnalogInput* axisPos, UINT16 minVal, UINT16 offVal, UINT16 maxVal)
{
CAxisInput *input = new CAxisInput(id, label, gameFlags, defaultMapping, axisNeg, axisPos, minVal, offVal, maxVal);
m_inputs.push_back(input);
return input;
}
CGearShift4Input* CInputs::AddGearShift4Input(const char *id, const char *label, unsigned gameFlags,
CSwitchInput *shift1, CSwitchInput *shift2, CSwitchInput *shift3, CSwitchInput *shift4, CSwitchInput *shiftUp, CSwitchInput *shiftDown)
{
CGearShift4Input *input = new CGearShift4Input(id, label, gameFlags, shift1, shift2, shift3, shift4, shiftUp, shiftDown);
m_inputs.push_back(input);
return input;
}
void CInputs::PrintHeader(const char *fmt, ...)
{
char header[1024];
va_list vl;
va_start(vl, fmt);
vsprintf(header, fmt, vl);
va_end(vl);
puts(header);
for (size_t i = 0; i < strlen(header); i++)
putchar('-');
printf("\n\n");
}
void CInputs::PrintConfigureInputsHelp()
{
puts("For each control, use the following keys to map the inputs:");
puts("");
puts(" Return Set current input mapping and move to next control,");
puts(" c Clear current input mapping and remain there,");
puts(" s Set the current input mapping and remain there,");
puts(" a Append to current input mapping (for multiple assignments)");
puts(" and remain there,");
puts(" r Reset current input mapping to default and remain there,");
puts(" Down Move onto next control,");
puts(" Up Go back to previous control,");
puts(" h Display this help again,");
puts(" q Finish and save all changes,");
puts(" Esc Finish without saving any changes.");
puts("");
puts("To assign inputs, simply press the appropriate key, mouse button or");
puts("joystick button or move the mouse along an axis or move a joystick's");
puts("axis or POV hat controller.");
puts("");
puts("NOTES:");
puts(" - in order to assign a key the configuration window must on top,");
puts(" - in order to assign a mouse button the mouse must be clicked");
puts(" within the window,");
puts(" - in order to assign a mouse axis, the cursor must be placed in");
puts(" the center of the window and moved in the corresponding");
puts(" direction to the window's edge and then returned to the center.");
puts("");
}
CInputSystem *CInputs::GetInputSystem()
{
return m_system;
}
bool CInputs::Initialize()
{
// Make sure the input system is initialized too
if (!m_system->Initialize())
return false;
// Initialize all the inputs
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
(*it)->Initialize(m_system);
return true;
}
bool CInputs::InputIsConfigurable(CInput *input)
{
// All inputs except UI and virtual ones can be configured by the user
return input->flags != UI_INPUT && !(input->flags & INPUT_FLAGS_VIRTUAL);
}
CInput* CInputs::LookupInputByID(const char* id)
{
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (stricmp(id, (*it)->id) == 0)
return *it;
}
return NULL;
}
void CInputs::ReadFromINIFile(CINIFile *ini, const char *section)
{
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (!InputIsConfigurable(*it))
continue;
string key("Input");
key.append((*it)->id);
string mapping;
if (ini->Get(section, key, mapping) == OKAY)
(*it)->SetMapping(mapping.c_str());
}
}
void CInputs::WriteToINIFile(CINIFile *ini, const char *section)
{
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (!InputIsConfigurable(*it))
continue;
string key("Input");
key.append((*it)->id);
ini->Set(section, key, (*it)->GetMapping());
}
}
bool CInputs::ConfigureInputs(const GameInfo *game, unsigned dispX, unsigned dispY, unsigned dispW, unsigned dispH)
{
m_system->ConfigStart();
// Let the input system know the display geometry
m_system->SetDisplayGeom(dispX, dispY, dispW, dispH);
// Print header and help message
int gameFlags;
if (game != NULL)
{
PrintHeader("Configure Inputs for %s", game->title);
gameFlags = game->inputFlags;
}
else
{
PrintHeader("Configure Inputs");
gameFlags = GAME_INPUT_ALL;
}
PrintConfigureInputsHelp();
// Get all inputs to be configured
vector<CInput*> toConfigure;
vector<CInput*>::iterator it;
for (it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (InputIsConfigurable(*it) && ((*it)->gameFlags & gameFlags))
toConfigure.push_back(*it);
}
// Remember current mappings for each input in case changes need to be undone later
vector<string> oldMappings(toConfigure.size());
size_t index = 0;
for (it = toConfigure.begin(); it < toConfigure.end(); it++)
oldMappings[index++] = (*it)->GetMapping();
const char *groupLabel = NULL;
// Loop through all the inputs to be configured
index = 0;
while (index < toConfigure.size())
{
// Get the current input
CInput* input = toConfigure[index];
// If have moved to a new input group, print the group heading
const char *itGroupLabel = input->GetInputGroup();
if (groupLabel == NULL || stricmp(groupLabel, itGroupLabel) != 0)
{
groupLabel = itGroupLabel;
printf("%s:\n", groupLabel);
}
Redisplay:
// Print the input label, current input mapping and available options
if (index > 0)
printf(" %s [%s]: Ret/c/s/a/r/Up/Down/h/q/Esc? ", input->label, input->GetMapping());
else
printf(" %s [%s]: Ret/c/s/a/r/Down/h/q/Esc? ", input->label, input->GetMapping());
fflush(stdout); // required on terminals that use buffering
// Loop until user has selected a valid option
bool done = false;
char mapping[50];
while (!done)
{
// Wait for input from user
if (!m_system->ReadMapping(mapping, 50, false, READ_KEYBOARD|READ_MERGE, uiExit->GetMapping()))
{
// If user pressed aborted input, then undo all changes and finish configuration
index = 0;
for (it = toConfigure.begin(); it < toConfigure.end(); it++)
{
(*it)->SetMapping(oldMappings[index].c_str());
index++;
}
puts("");
m_system->ConfigEnd();
return false;
}
if (stricmp(mapping, "KEY_RETURN") == 0 || stricmp(mapping, "KEY_S") == 0)
{
// Set the input mapping
printf("Setting...");
fflush(stdout); // required on terminals that use buffering
if (input->Configure(false, uiExit->GetMapping()))
{
printf(" %s\n", input->GetMapping());
if (stricmp(mapping, "KEY_RETURN") == 0)
index++;
done = true;
}
else
{
printf(" [Cancelled]\n");
goto Redisplay;
}
}
else if (stricmp(mapping, "KEY_A") == 0)
{
// Append to the input mapping(s)
printf("Appending...");
fflush(stdout); // required on terminals that use buffering
if (input->Configure(true, uiExit->GetMapping()))
printf(" %s\n", input->GetMapping());
else
printf(" [Cancelled]\n");
goto Redisplay;
}
else if (stricmp(mapping, "KEY_C") == 0)
{
// Clear the input mapping(s)
input->SetMapping("NONE");
printf("Cleared\n");
goto Redisplay;
}
else if (stricmp(mapping, "KEY_R") == 0)
{
// Reset the input mapping(s) to the default
input->ResetToDefaultMapping();
printf("Reset\n");
goto Redisplay;
}
else if (stricmp(mapping, "KEY_DOWN") == 0)
{
// Move forward to the next mapping
puts("");
index++;
done = true;
}
else if (stricmp(mapping, "KEY_UP") == 0)
{
// Move back to the previous mapping
if (index > 0)
{
puts("");
index--;
done = true;
}
}
else if (stricmp(mapping, "KEY_HOME") == 0)
{
// Move to first input
puts("");
index = 0;
done = true;
}
else if (stricmp(mapping, "KEY_H") == 0)
{
// Print the help message again
puts("");
PrintConfigureInputsHelp();
goto Redisplay;
}
else if (stricmp(mapping, "KEY_Q") == 0)
{
// Finish configuration
puts("");
m_system->ConfigEnd();
return true;
}
}
}
// All inputs set, finish configuration
puts("");
m_system->ConfigEnd();
return true;
}
void CInputs::PrintInputs(const GameInfo *game)
{
// Print header
int gameFlags;
if (game != NULL)
{
PrintHeader("Input Assignments for %s", game->title);
gameFlags = game->inputFlags;
}
else
{
PrintHeader("Input Assignments");
gameFlags = GAME_INPUT_ALL;
}
const char *groupLabel = NULL;
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (!InputIsConfigurable(*it) || !((*it)->gameFlags & gameFlags))
continue;
const char *itGroupLabel = (*it)->GetInputGroup();
if (groupLabel == NULL || stricmp(groupLabel, itGroupLabel) != 0)
{
groupLabel = itGroupLabel;
printf("%s:\n", groupLabel);
}
printf(" %s = %s\n", (*it)->label, (*it)->GetMapping());
}
puts("");
}
bool CInputs::Poll(const GameInfo *game, unsigned dispX, unsigned dispY, unsigned dispW, unsigned dispH)
{
// Update the input system with the current display geometry
m_system->SetDisplayGeom(dispX, dispY, dispW, dispH);
// Poll the input system
if (!m_system->Poll())
return false;
// Poll all UI inputs and all the inputs used by the current game, or all inputs if game is NULL
int gameFlags = (game != NULL ? game->inputFlags : GAME_INPUT_ALL);
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if ((*it)->gameFlags == UI_INPUT || (*it)->gameFlags & gameFlags)
(*it)->Poll();
}
return true;
}
void CInputs::DumpState(const GameInfo *game)
{
// Print header
int gameFlags;
if (game != NULL)
{
PrintHeader("Input States for %s", game->title);
gameFlags = game->inputFlags;
}
else
{
PrintHeader("Input States");
gameFlags = GAME_INPUT_ALL;
}
// Loop through the inputs used by the current game, or all inputs if game is NULL, and dump their values to stdout
for (vector<CInput*>::iterator it = m_inputs.begin(); it < m_inputs.end(); it++)
{
if (!((*it)->gameFlags & gameFlags))
continue;
if (InputIsConfigurable(*it))
printf("%s [%s] = (%d)\n", (*it)->id, (*it)->GetMapping(), (*it)->value);
else
printf("%s = (%d)\n", (*it)->id, (*it)->value);
}
}

195
Inputs/Inputs.h Normal file
View file

@ -0,0 +1,195 @@
#ifndef INCLUDED_INPUTS_H
#define INCLUDED_INPUTS_H
#include "Types.h"
#include <vector>
using namespace std;
class CInputSystem;
class CInput;
class CAnalogInput;
class CAxisInput;
class CSwitchInput;
class CGearShift4Input;
class CINIFile;
struct GameInfo;
/*
* Represents the collection of Model3 inputs.
*/
class CInputs
{
private:
// Assigned input system
CInputSystem *m_system;
// Vector of all created inputs
vector<CInput*> m_inputs;
/*
* Adds a switch input (eg button) to this collection.
*/
CSwitchInput* AddSwitchInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
UINT16 offVal = 0x00, UINT16 onVal = 0x01);
/*
* Adds an analog input (eg pedal) to this collection.
*/
CAnalogInput* AddAnalogInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
UINT16 minVal = 0x00, UINT16 maxVal = 0xFF);
/*
* Adds an axis input (eg jostick axis, light gun axis or steering wheel) to this collection.
*/
CAxisInput* AddAxisInput(const char *id, const char *label, unsigned gameFlags, const char *defaultMapping,
CAnalogInput* axisNeg, CAnalogInput* axisPos, UINT16 minVal = 0x00, UINT16 offVal = 0x80, UINT16 maxVal = 0xFF);
/*
* Adds a 4-gear shifter input to this collection.
*/
CGearShift4Input* AddGearShift4Input(const char *id, const char *label, unsigned gameFlags,
CSwitchInput *shift1, CSwitchInput *shift2, CSwitchInput *shift3, CSwitchInput *shift4, CSwitchInput *shiftUp, CSwitchInput *shiftDown);
void PrintHeader(const char *fmt, ...);
void PrintConfigureInputsHelp();
public:
// UI controls
CSwitchInput* uiExit;
CSwitchInput* uiReset;
CSwitchInput* uiPause;
CSwitchInput* uiSaveState;
CSwitchInput* uiChangeSlot;
CSwitchInput* uiLoadState;
CSwitchInput* uiDumpInpState;
CSwitchInput* uiClearNVRAM;
CSwitchInput* uiToggleCursor;
CSwitchInput* uiToggleFrLimit;
// Common controls between all games
CSwitchInput* coin[2];
CSwitchInput* start[2];
CSwitchInput* test[2];
CSwitchInput* service[2];
// Joysticks (players 1 and 2)
CSwitchInput* up[2];
CSwitchInput* down[2];
CSwitchInput* left[2];
CSwitchInput* right[2];
// Fighting game controls (players 1 and 2)
CSwitchInput* punch[2];
CSwitchInput* kick[2];
CSwitchInput* guard[2];
CSwitchInput* escape[2];
// Soccer game controls (players 1 and 2)
CSwitchInput* shortPass[2];
CSwitchInput* longPass[2];
CSwitchInput* shoot[2];
// Vehicle controls
CAxisInput* steering;
CAnalogInput* accelerator;
CAnalogInput* brake;
// VR view buttons: VR1 Red, VR2 Blue, VR3 Yellow, VR4 Green
CSwitchInput* vr[4];
// 4-speed gear shift
CGearShift4Input* gearShift4;
// Rally controls
CSwitchInput* viewChange;
CSwitchInput* handBrake;
// Twin joysticks
CSwitchInput* twinJoyTurnLeft;
CSwitchInput* twinJoyTurnRight;
CSwitchInput* twinJoyStrafeLeft;
CSwitchInput* twinJoyStrafeRight;
CSwitchInput* twinJoyForward;
CSwitchInput* twinJoyReverse;
CSwitchInput* twinJoyJump;
CSwitchInput* twinJoyCrouch;
CSwitchInput* twinJoyLeftShot;
CSwitchInput* twinJoyRightShot;
CSwitchInput* twinJoyLeftTurbo;
CSwitchInput* twinJoyRightTurbo;
// Analog joystick
CAxisInput* analogJoyX;
CAxisInput* analogJoyY;
CSwitchInput* analogJoyTrigger;
CSwitchInput* analogJoyEvent;
// Gun controls (players 1 and 2)
CAxisInput* gunX[2];
CAxisInput* gunY[2];
CSwitchInput* trigger[2];
CSwitchInput* offscreen[2];
/*
* Creates a set of inputs with the given input system.
*/
CInputs(CInputSystem *system);
~CInputs();
/*
* Returns the assigned input system.
*/
CInputSystem *GetInputSystem();
/*
* Initializes the inputs. Must be called before any other methods are used.
*/
bool Initialize();
/*
* Looks up an input by its identifier.
*/
CInput* LookupInputByID(const char* id);
/*
* Returns true if the given input is configurable and can be set by the user.
*/
bool InputIsConfigurable(CInput *input);
/*
* Reads the input mapping assignments from the given INI file.
*/
void ReadFromINIFile(CINIFile *ini, const char *section);
/*
* Writes the current input mapping assignments to the given INI file.
*/
void WriteToINIFile(CINIFile *ini, const char *section);
/*
* Configures the current input mapping assignments for the given game, or all inputs if game is NULL, by asking the user for input.
* Returns true if the inputs were configured okay or false if the user exited without requesting to save changes.
*/
bool ConfigureInputs(const GameInfo *game, unsigned dispX, unsigned dispY, unsigned dispW, unsigned dispH);
/*
* Prints to stdout the current input mapping assignments for the given game, or all inputs if game is NULL.
*/
void PrintInputs(const GameInfo *game);
/*
* Polls (updates) the inputs for the given game, or all inputs if game is NULL, updating their values from their respective input sources.
* First the input system is polled (CInputSystem.Poll()) and then each input is polled (CInput.Poll()).
*/
bool Poll(const GameInfo *game, unsigned dispX, unsigned dispY, unsigned dispW, unsigned dispH);
/*
* Prints the current values of the inputs for the given game, or all inputs if game is NULL, to stdout for debugging purposes.
*/
void DumpState(const GameInfo *game);
};
#endif // INCLUDED_INPUTS_H

117
Inputs/MultiInputSource.cpp Normal file
View file

@ -0,0 +1,117 @@
#include "Supermodel.h"
#include <vector>
using namespace std;
ESourceType CMultiInputSource::GetCombinedType(vector<CInputSource*> &sources)
{
// Check if vector is empty
if (sources.size() == 0)
return SourceEmpty;
// Otherwise, see whether all sources are switches, or if have a full- or half-axis present
bool allSwitches = true;
bool hasFullAxis = false;
bool hasHalfAxis = false;
for (vector<CInputSource*>::iterator it = sources.begin(); it < sources.end(); it++)
{
if ((*it)->type == SourceInvalid)
return SourceInvalid; // An invalid source makes the whole lot invalid
else if ((*it)->type == SourceSwitch)
continue;
allSwitches = false;
if ((*it)->type == SourceFullAxis)
{
if (hasHalfAxis)
return SourceInvalid; // A half-axis and full-axis combined makes the whole lot invalid
hasFullAxis = true;
}
else if ((*it)->type == SourceHalfAxis)
{
if (hasFullAxis)
return SourceInvalid; // A half-axis and full-axis combined makes the whole lot invalid
hasHalfAxis = true;
}
}
// Return resulting combined type
if (allSwitches) return SourceSwitch;
else if (hasFullAxis) return SourceFullAxis;
else if (hasHalfAxis) return SourceHalfAxis;
else return SourceEmpty;
}
CMultiInputSource::CMultiInputSource() : CInputSource(SourceEmpty), m_isOr(true), m_numSrcs(0), m_srcArray(NULL) { }
CMultiInputSource::CMultiInputSource(bool isOr, vector<CInputSource*> &sources) :
CInputSource(GetCombinedType(sources)), m_isOr(isOr), m_numSrcs(sources.size())
{
m_srcArray = new CInputSource*[m_numSrcs];
copy(sources.begin(), sources.end(), m_srcArray);
}
CMultiInputSource::~CMultiInputSource()
{
if (m_srcArray != NULL)
delete m_srcArray;
}
bool CMultiInputSource::GetValueAsSwitch(bool &val)
{
if (m_isOr)
{
// Return value for first input that is active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->GetValueAsSwitch(val))
return true;
}
return false;
}
else
{
// Check all switch inputs are active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->type == SourceSwitch && !m_srcArray[i]->GetValueAsSwitch(val))
return false;
}
// Then return value for first non-switch input that is active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->type != SourceSwitch && m_srcArray[i]->GetValueAsSwitch(val))
return true;
}
// Otherwise, value is only valid if not empty and all inputs are switches
return m_numSrcs > 0 && type == SourceSwitch;
}
}
bool CMultiInputSource::GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal)
{
if (m_isOr)
{
// Return value for first input that is active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->GetValueAsAnalog(val, minVal, offVal, maxVal))
return true;
}
return false;
}
else
{
// Check all switch inputs are active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->type == SourceSwitch && !m_srcArray[i]->GetValueAsAnalog(val, minVal, offVal, maxVal))
return false;
}
// Then return value for first non-switch input that is active
for (int i = 0; i < m_numSrcs; i++)
{
if (m_srcArray[i]->type != SourceSwitch && m_srcArray[i]->GetValueAsAnalog(val, minVal, offVal, maxVal))
return true;
}
// Otherwise, value is only valid if not empty and all inputs are switches
return m_numSrcs > 0 && type == SourceSwitch;
}
}

51
Inputs/MultiInputSource.h Normal file
View file

@ -0,0 +1,51 @@
#ifndef INCLUDED_MULTIINPUTSOURCE_H
#define INCLUDED_MULTIINPUTSOURCE_H
#include "InputSource.h"
#include <vector>
using namespace std;
/*
* Represents a collection of input sources and combines their values into a single value.
* When multiple mappings are assigned to an input, this is the input source that is created.
*/
class CMultiInputSource : public CInputSource
{
private:
// Controls how the inputs sources are combined
bool m_isOr;
// Number of input sources (if zero then represents an 'empty' source)
int m_numSrcs;
// Array of the input sources
CInputSource **m_srcArray;
public:
/*
* Returns the combined source type of the given vector of sources.
*/
static ESourceType GetCombinedType(vector<CInputSource*> &sources);
/*
* Constructs an 'empty' source (ie one which is always 'off').
*/
CMultiInputSource();
/*
* Constructs a multiple input source from the given vector of sources.
* If isOr is true, then the value of this input will always be the value of the first active input found. If false, then all
* switch inputs must be active for this input to have a value (which will be the value of the first non-switch input in the list,
* or the first switch input if there are none).
*/
CMultiInputSource(bool isOr, vector<CInputSource*> &sources);
~CMultiInputSource();
bool GetValueAsSwitch(bool &val);
bool GetValueAsAnalog(int &val, int minVal, int offVal, int maxVal);
};
#endif // INCLUDED_MULTIINPUTSOURCE_H

506
Model3/53C810.cpp Normal file
View file

@ -0,0 +1,506 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* 53C810.cpp
*
* Implementation of the C53C810 class: NCR 53C810 SCSI controller.
*
* TO-DO List:
* -----------
* - VF3 does something weird: it writes DSP (triggering automatic code
* execution because MAN=0) and THEN sets single step mode, expecting an
* interrupt to occur. I suspect this is incorrect operation and that
* the SCRIPTS processor either enters single-step mode while the memory
* transfer is underway (though it is unlikely because it's such a short
* transfer), or that single step can occur even when the device is "halted"
* (which would mean the SCRIPTS processor executes an invalid instruction
* once or twice without the VF3 SCSI driver noticing). Unfortunately, the
* former is not feasible to emulate. If automatic SCRIPTS execution is
* disabled when single-stepping is enabled, Scud Race breaks (glitchy,
* jerky graphics). Enabling automatic execution and also allowing single
* stepping to occur when the processor is halted seems to work, but it
* causes invalid instructions to be hit each time.
* - Check to ensure the invalid instructions hit above would never be decoded
* as real SCRIPTS instructions (in some cases, appears they can be...)
* - Is the SIP bit supposed to be set after single stepping?
* - pg 2-22 (42) of the manual has description of how to clear interrupts.
* - Another way to fix VF3 is to just set the single-step and DMA interrupt
* flags at all times.
*
*/
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Save States
******************************************************************************/
void C53C810::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("53C810", __FILE__);
SaveState->Write(Ctx.regs, sizeof(Ctx.regs));
SaveState->Write(&Ctx.regTEMP, sizeof(Ctx.regTEMP));
SaveState->Write(&Ctx.regDSP, sizeof(Ctx.regDSP));
SaveState->Write(&Ctx.regDSPS, sizeof(Ctx.regDSPS));
SaveState->Write(&Ctx.regDBC, sizeof(Ctx.regDBC));
SaveState->Write(&Ctx.regDCMD, sizeof(Ctx.regDCMD));
SaveState->Write(&Ctx.regDCNTL, sizeof(Ctx.regDCNTL));
SaveState->Write(&Ctx.regDMODE, sizeof(Ctx.regDMODE));
SaveState->Write(&Ctx.regDSTAT, sizeof(Ctx.regDSTAT));
SaveState->Write(&Ctx.regISTAT, sizeof(Ctx.regISTAT));
}
void C53C810::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("53C810"))
{
ErrorLog("Unable to load 53C810 state. Save state file is corrupted.");
return;
}
SaveState->Read(Ctx.regs, sizeof(Ctx.regs));
SaveState->Read(&Ctx.regTEMP, sizeof(Ctx.regTEMP));
SaveState->Read(&Ctx.regDSP, sizeof(Ctx.regDSP));
SaveState->Read(&Ctx.regDSPS, sizeof(Ctx.regDSPS));
SaveState->Read(&Ctx.regDBC, sizeof(Ctx.regDBC));
SaveState->Read(&Ctx.regDCMD, sizeof(Ctx.regDCMD));
SaveState->Read(&Ctx.regDCNTL, sizeof(Ctx.regDCNTL));
SaveState->Read(&Ctx.regDMODE, sizeof(Ctx.regDMODE));
SaveState->Read(&Ctx.regDSTAT, sizeof(Ctx.regDSTAT));
SaveState->Read(&Ctx.regISTAT, sizeof(Ctx.regISTAT));
}
/******************************************************************************
SCRIPTS Emulation
******************************************************************************/
static inline UINT32 Fetch(struct NCR53C810Context *Ctx)
{
UINT32 data = Ctx->Bus->Read32(Ctx->regDSP);
Ctx->regDSP += 4;
return FLIPENDIAN32(data); // remember: bus is big endian, need to convert to little endian
}
//TO-DO: check if this ever occurs in single-step mode (if so, we would need to stack interrupts)
static BOOL SCRIPTS_Int_IntFly(struct NCR53C810Context *Ctx)
{
Ctx->halt = TRUE; // halt SCRIPTS execution
Ctx->regISTAT |= 1; // DMA interrupt pending
Ctx->regDSTAT |= 4; // SCRIPTS interrupt instruction received
Ctx->IRQ->Assert(Ctx->scsiIRQ);
if ((Ctx->regDBC&0x100000)) // INTFLY
return ErrorLog("53C810 INTFLY instruction not emulated!");
return OKAY;
}
static BOOL SCRIPTS_MoveMemory(struct NCR53C810Context *Ctx)
{
UINT32 src, dest;
unsigned numBytes, i;
// Get operands
src = Ctx->regDSPS;
dest = Ctx->regTEMP = Fetch(Ctx);
numBytes = Ctx->regDBC;
// not implemented: illegal instruction interrupt when src and dest are not aligned the same way
DebugLog("53C810: Move Memory %08X -> %08X, %X\n", src, dest, numBytes);
// Perform a 32-bit copy if possible
for (i = 0; i < (numBytes/4); i++)
{
Ctx->Bus->Write32(dest, Ctx->Bus->Read32(src));
dest += 4;
src += 4;
}
// Finish off the last few odd bytes
numBytes &= 3;
while (numBytes)
{
Ctx->Bus->Write8(dest++, Ctx->Bus->Read8(src++));
--numBytes;
}
// Update registers
Ctx->regDBC = 0;
Ctx->regDSPS = src;
Ctx->regTEMP = dest;
return OKAY;
}
// Invalid instruction handler
static BOOL SCRIPTS_Invalid(struct NCR53C810Context *Ctx)
{
DebugLog("53C810 encountered an unrecognized instruction (%02X%06X, DSP=%08X)\n!", Ctx->regDCMD, Ctx->regDBC, Ctx->regDSP);
return FAIL;
}
void C53C810::Run(BOOL singleStep)
{
UINT32 op;
int i;
if (singleStep)// && !Ctx.halt)
{
// Fetch instruction (first two words are always fetched)
op = Fetch(&Ctx); // word 1
Ctx.regDBC = op&0x00FFFFFF;
Ctx.regDCMD = (op>>24)&0xFF;
Ctx.regDSPS = Fetch(&Ctx); // word 2
// Single step
OpTable[Ctx.regDCMD](&Ctx);
// Issue IRQ and finish
Ctx.regISTAT |= 3; // DMA interrupt pending (NOTE: should SIP be set? I don't think so...)
Ctx.regDSTAT |= 8; // single step interrupt
Ctx.IRQ->Assert(Ctx.scsiIRQ); // generate an interrupt
DebugLog("53C810: Asserted IRQ\n");
}
else
{
// Automatic mode: run (as long as the processor is not halted)
for (i = 0; (i < 100) && !Ctx.halt; i++)
{
// Fetch instruction (first two words are always fetched)
op = Fetch(&Ctx); // word 1
Ctx.regDBC = op&0x00FFFFFF;
Ctx.regDCMD = (op>>24)&0xFF;
Ctx.regDSPS = Fetch(&Ctx); // word 2
// Execute!
if (OpTable[Ctx.regDCMD](&Ctx) != OKAY)
break;
}
}
}
// Insert instructions into the LUT under control of the mask
void C53C810::Insert(UINT8 mask, UINT8 op, BOOL (*Handler)(struct NCR53C810Context *))
{
UINT32 i;
for (i = 0; i < 256; i++)
{
if ((i&mask) == op)
OpTable[i] = Handler;
}
}
void C53C810::BuildOpTable(void)
{
Insert(0, 0, &SCRIPTS_Invalid);
Insert(0xE0, 0xC0, &SCRIPTS_MoveMemory);
Insert(0xF8, 0x98, &SCRIPTS_Int_IntFly);
}
/******************************************************************************
Register and PCI Access Handlers
******************************************************************************/
void C53C810::WriteRegister(unsigned reg, UINT8 data)
{
if (reg >= 0x60)
{
ErrorLog("%s:%s: Invalid 53C810 register (%02X).", __FILE__, __LINE__, reg);
return;
}
DebugLog("53C810 write: %02X=%02X (PC=%08X, LR=%08X)\n", reg, data, ppc_get_pc(), ppc_get_lr());
// Dump everything into the register file
Ctx.regs[reg&0xFF] = data;
// Do something extra with the ones that we actually care about
// TO-DO: prevent invalid/reserved/read-only registers from being written?
switch(reg)
{
case 0x14: // ISTAT
Ctx.regISTAT = data;
DebugLog("ISTAT=%02X\n", data);
break;
case 0x1C: // TEMP 7-0
Ctx.regTEMP &= 0xFFFFFF00;
Ctx.regTEMP |= data;
break;
case 0x1D: // TEMP 15-8
Ctx.regTEMP &= 0xFFFF00FF;
Ctx.regTEMP |= (data<<8);
break;
case 0x1E: // TEMP 23-16
Ctx.regTEMP &= 0xFF00FFFF;
Ctx.regTEMP |= (data<<16);
break;
case 0x1F: // TEMP 31-24
Ctx.regTEMP &= 0x00FFFFFF;
Ctx.regTEMP |= (data<<24);
break;
case 0x24: // DBC 7-0
Ctx.regDBC &= 0xFFFFFF00;
Ctx.regDBC |= data;
break;
case 0x25: // DBC 15-8
Ctx.regDBC &= 0xFFFF00FF;
Ctx.regDBC |= (data<<8);
break;
case 0x26: // DBC 23-16
Ctx.regDBC &= 0xFF00FFFF;
Ctx.regDBC |= (data<<16);
break;
case 0x27: // DCMD
Ctx.regDCMD = data;
break;
case 0x2C: // DSP 7-0
Ctx.regDSP &= 0xFFFFFF00;
Ctx.regDSP |= data;
break;
case 0x2D: // DSP 15-8
Ctx.regDSP &= 0xFFFF00FF;
Ctx.regDSP |= (data<<8);
break;
case 0x2E: // DSP 23-16
Ctx.regDSP &= 0xFF00FFFF;
Ctx.regDSP |= (data<<16);
break;
case 0x2F: // DSP 31-24
Ctx.regDSP &= 0x00FFFFFF;
Ctx.regDSP |= (data<<24);
Ctx.halt = FALSE; // writing this register un-halts 53C810 operation (pg.6-31 of LSI manual)
if (!(Ctx.regDMODE&1)) // if MAN=0, start SCRIPTS automatically
// To-Do: is this correct? Should single step really be tested first?
//if (!(Ctx.regDCNTL&0x10) && !(Ctx.regDMODE&1)) // if MAN=0 and not single stepping, start SCRIPTS automatically
{
DebugLog("53C810: Automatically starting (PC=%08X, LR=%08X, single step=%d)\n", ppc_get_pc(), ppc_get_lr(), !!(Ctx.regDCNTL&0x10));
Run(FALSE); // automatic
}
break;
case 0x30: // DSPS 7-0
Ctx.regDSPS &= 0xFFFFFF00;
Ctx.regDSPS |= data;
break;
case 0x31: // DSPS 15-8
Ctx.regDSPS &= 0xFFFF00FF;
Ctx.regDSPS |= (data<<8);
break;
case 0x32: // DSPS 23-16
Ctx.regDSPS &= 0xFF00FFFF;
Ctx.regDSPS |= (data<<16);
break;
case 0x33: // DSPS 31-24
Ctx.regDSPS &= 0x00FFFFFF;
Ctx.regDSPS |= (data<<24);
break;
case 0x38: // DMODE
Ctx.regDMODE = data;
break;
case 0x3B: // DCNTL
Ctx.regDCNTL = data;
if ((Ctx.regDCNTL&0x14) == 0x14) // single step
{
DebugLog("53C810: single step: %08X, (halt=%d)\n", Ctx.regDSP, Ctx.halt);
Run(TRUE);
}
else if ((Ctx.regDCNTL&0x04)) // start DMA bit
{
DebugLog("53C810: Manually starting\n");
Run(FALSE);
}
break;
default:
break;
}
}
UINT8 C53C810::ReadRegister(unsigned reg)
{
UINT8 ret;
if (reg >= 0x60)
{
ErrorLog("%s:%s: Invalid 53C810 register (%02X).", __FILE__, __LINE__, reg);
return 0;
}
DebugLog("53C810 read: %02X (PC=%08X, LR=%08X)\n", reg, ppc_get_pc(), ppc_get_lr());
// Some registers require special handling
switch(reg)
{
case 0x0C: // DSTAT
// For now, we don't generate stacked interrupts, so always clear IRQ status
ret = Ctx.regDSTAT;
//TO-DO: manual says these should be cleared here but MAME never clears them. What's up with that?
Ctx.regISTAT &= 0xFE; // clear DIP bit (DMA interrupt)
Ctx.regDSTAT &= 0xF7; // clear SSI (single step interrupt)
//Ctx.regISTAT |= 1; // doing this is another way to fix VF3
//Ctx.regDSTAT |= 8;
Ctx.IRQ->Deassert(Ctx.scsiIRQ);
//DebugLog("53C810: DSTAT read\n");
return ret;
case 0x14: // ISTAT
//DebugLog("53C810: ISTAT read\n");
return Ctx.regISTAT;
case 0x1C: // TEMP 7-0
return Ctx.regTEMP&0xFF;
case 0x1D: // TEMP 15-8
return (Ctx.regTEMP>>8)&0xFF;
case 0x1E: // TEMP 23-16
return (Ctx.regTEMP>>16)&0xFF;
case 0x1F: // TEMP 31-24
return (Ctx.regTEMP>>24)&0xFF;
case 0x24: // DBC 7-0
return Ctx.regDBC&0xFF;
case 0x25: // DBC 15-8
return (Ctx.regDBC>>8)&0xFF;
case 0x26: // DBC 23-16
return (Ctx.regDBC>>16)&0xFF;
case 0x27: // DCMD
return Ctx.regDCMD;
case 0x2C: // DSP 7-0
return Ctx.regDSP&0xFF;
case 0x2D: // DSP 15-8
return (Ctx.regDSP>>8)&0xFF;
case 0x2E: // DSP 23-16
return (Ctx.regDSP>>16)&0xFF;
case 0x2F: // DSP 31-24
return (Ctx.regDSP>>24)&0xFF;
case 0x30: // DSPS 7-0
return Ctx.regDSPS&0xFF;
case 0x31: // DSPS 15-8
return (Ctx.regDSPS>>8)&0xFF;
case 0x32: // DSPS 23-16
return (Ctx.regDSPS>>16)&0xFF;
case 0x33: // DSPS 31-24
return (Ctx.regDSPS>>24)&0xFF;
case 0x38:
return Ctx.regDMODE;
case 0x3B: // DCNTL
return Ctx.regDCNTL;
default: // get it from the register file
break;
}
// Register file should be up to date
return Ctx.regs[reg&0xFF];
}
UINT32 C53C810::ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
{
UINT32 d;
if ((bits==8))
{
DebugLog("53C810 %d-bit PCI read request for reg=%02X\n", bits, reg);
return 0;
}
switch (reg)
{
case 0x00: // Device ID and Vendor ID
d = 0x00011000; // 0x1000 = LSI Logic
switch (bits)
{
case 8:
d >>= (3-offset)*8; // offset will be 0-3; select appropriate byte
d &= 0xFF;
break;
case 16:
d >>= (2-offset)*8; // offset will be 0 or 2 only; select either high or low word
d &= 0xFFFF;
break;
default:
break;
}
return d;
default:
DebugLog("53C810 PCI read request for reg=%02X (%d-bit)\n", reg, bits);
break;
}
return 0;
}
void C53C810::WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
{
DebugLog("53C810 PCI %d-bit write request for reg=%02X, data=%08X\n", bits, reg, data);
}
void C53C810::Reset(void)
{
memset(Ctx.regs, 0, sizeof(Ctx.regs));
Ctx.regs[0x00] = 0xC0; // SCNTL0
Ctx.regs[0x0C] = 0x80; // DSTAT
Ctx.regs[0x0F] = 0x02; // SSTAT2
Ctx.regs[0x18] = 0xFF; // reserved
Ctx.regs[0x19] = 0xF0; // CTEST1
Ctx.regs[0x1A] = 0x01; // CTEST2
Ctx.regs[0x46] = 0x60; // MACNTL
Ctx.regs[0x47] = 0x0F; // GPCNTL
Ctx.regs[0x4C] = 0x03; // STEST0
Ctx.regTEMP = 0;
Ctx.regDSP = 0;
Ctx.regDSPS = 0;
Ctx.regDBC = 0;
Ctx.regDCMD = 0;
Ctx.regDCNTL = 0;
Ctx.regDMODE = 0;
Ctx.regDSTAT = 0x80; // DMA FIFO empty
Ctx.regISTAT = 0;
Ctx.halt = FALSE;
DebugLog("53C810 reset\n");
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void C53C810::Init(CBus *BusObjectPtr, CIRQ *IRQObjectPtr, unsigned scsiIRQBit)
{
Ctx.Bus = BusObjectPtr;
Ctx.IRQ = IRQObjectPtr;
Ctx.scsiIRQ = scsiIRQBit;
}
C53C810::C53C810(void)
{
BuildOpTable();
Ctx.Bus = NULL;
Ctx.IRQ = NULL;
scsiIRQ = 0;
DebugLog("Built 53C810\n");
}
C53C810::~C53C810(void)
{
Ctx.Bus = NULL;
Ctx.IRQ = NULL;
DebugLog("Destroyed 53C810\n");
}

202
Model3/53C810.h Normal file
View file

@ -0,0 +1,202 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* 53C810.h
*
* Header file defining the C53C810 class (NCR 53C810 SCSI controller).
*/
#ifndef INCLUDED_53C810_H
#define INCLUDED_53C810_H
/*
* struct NCR53C810Context:
*
* Context information for an NCR 53C810 device. Used internally by the C53C810
* class (defined below).
*/
struct NCR53C810Context
{
UINT8 regs[0x60];
// Registers defined below should not be read from here, not regs[]
UINT32 regTEMP; // TEMP
UINT32 regDSP; // DSP: DMA SCRIPTS Pointer
UINT32 regDSPS; // DSPS: DMA SCRIPTS Pointer Save
UINT32 regDBC; // DBC: DMA Byte Counter (24 bits)
UINT8 regDCMD; // DCMD: DMA Command
UINT8 regDCNTL; // DCNTL: DMA Control
UINT8 regDMODE; // DMODE: DMA Mode
UINT8 regDSTAT; // DSTAT: DMA Status (read only)
UINT8 regISTAT; // ISTAT: Interrupt Status
// Operational status
BOOL halt; // set TRUE if halted by interrupt instruction
// Big endian bus object for DMA memory access and instruction fetching
CBus *Bus;
// IRQ handling
CIRQ *IRQ; // IRQ controller
unsigned scsiIRQ; // IRQ bit to use when calling IRQ handler
};
/*
* C53C810:
*
* NCR 53C810 SCSI controller device.
*/
class C53C810: public CPCIDevice
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* ReadRegister(reg):
*
* Read from an operating register (8 bits at a time).
*
* Parameters:
* reg Register offset ranging from 0x00 to 0x5F. Anything higher
* is ignored and returns 0.
*/
UINT8 ReadRegister(unsigned reg);
/*
* WriteRegister(reg, data):
*
* Write to an operating register (8 bits at a time). When breaking multi-
* byte words into individual bytes, make sure to write the lowest address
* first and the highest last. Some special functions registers, like DSP,
* will initiate processing only when the highest byte is written.
*
* Parameters:
* reg Register offset ranging from 0x00 to 0x5F. Anything higher
* is ignored.
* data Data to write.
*/
void WriteRegister(unsigned reg, UINT8 data);
/*
* ReadPCIConfigSpace(device, reg, bits, offset):
*
* Reads a PCI configuration space register. See CPCIDevice definition for
* more details.
*
* Parameters:
* device Device number (ignored, not needed).
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).;
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
*
* Returns:
* Register data.
*/
UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width);
/*
* WritePCIConfigSpace(device, reg, bits, offset, data):
*
* Writes to a PCI configuration space register. See CPCIDevice definition
* for more details.
*
* Parameters:
* device Device number (ignored, not needed).
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
* data Data.
*/
void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, UINT32 data);
/*
* Reset(void):
*
* Resets the device.
*/
void Reset(void);
/*
* Init(BusObjectPtr, IRQObjectPtr, scsiIRQBit):
*
* One-time initialization of the context. Must be called prior to all
* other members.
*
* Parameters:
* BusObjectPtr Pointer to the bus that the 53C810 has control
* over. Used to read/write memory.
* IRQObjectPtr Pointer to the IRQ controller. Used to trigger SCSI
* and DMA interrupts.
* scsiIRQBit IRQ identifier bit to pass along to IRQ controller
* when asserting interrupts.
*/
void Init(CBus *BusObjectPtr, CIRQ *IRQObjectPtr, unsigned scsiIRQBit);
/*
* C53C810(void):
* ~C53C810(void):
*
* Constructor and destructor.
*/
C53C810(void);
~C53C810(void);
private:
// Private members
void Run(int numOps);
void BuildOpTable(void);
void Insert(UINT8 mask, UINT8 op, BOOL (*Handler)(struct NCR53C810Context *));
BOOL (*OpTable[256])(struct NCR53C810Context *);
// Context (register file)
struct NCR53C810Context Ctx;
// IRQ controller and IRQ identifier for this SCSI controller
CIRQ *IRQ;
unsigned scsiIRQ;
};
#endif // INCLUDED_53C810_H

281
Model3/53C810Disasm.cpp Normal file
View file

@ -0,0 +1,281 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* 53C810Disasm.cpp
*
* NCR 53C810 SCRIPTS disassembler.
*
* WARNING: Not all instructions are supported. They are being added as they
* are encountered in Model 3 games. Some of the complicated operand forms may
* be decoded incorrectly.
*/
#include <stdio.h>
#include <string.h>
#ifdef STANDALONE
#include <stdlib.h>
#endif
#include "Types.h"
#define DISASM_VERSION "1.0"
/******************************************************************************
Disassembler Function
******************************************************************************/
/*
* DisassembleSCRIPTS(op, addr, mnem):
*
* Disassembles one SCRIPTS instruction.
*
* Parameters:
* op Three consecutive words.
* addr Current instruction address.
* mnem Buffer to write instruction mnemonic to. If no instruction was
* successfully decoded, will be set to '\0'.
*
* Returns:
* Number of 32-bit words decoded (1, 2, or 3) or 0 if the instruction was
* not recognized.
*/
int DisassembleSCRIPTS(UINT32 op[3], UINT32 addr, char *mnem)
{
static const char *phase[] = { "data_out", "data_in", "command", "status", "res4", "res5", "message_out", "message_in" };
int carry, bitTrue, compData, compPhase, wait, mask, data;
mnem[0] = '\0';
if ((op[0]&0xC0000000) == 0)
{
// Block Move
if ((op[0]&0x10000000)) // FROM
sprintf(mnem, "move (%d) from %08X, when ", (op[0]>>27)&1, op[1]);
else // count
{
if ((op[0]&0x20000000))
sprintf(mnem, "move (%d) %X, ptr %08X, when ", (op[0]>>27)&1, op[0]&0x00FFFFFF, op[1]);
else
sprintf(mnem, "move (%d) %X, %08X, when ", (op[0]>>27)&1, op[0]&0x00FFFFFF, op[1]);
}
strcat(mnem, phase[(op[0]>>24)&7]);
return 2;
}
else if ((op[0]&0xE0000000) == 0xC0000000)
{
// Move Memory
if ((op[0]&0x01000000))
sprintf(mnem, "move memory no flush %X, %08X, %08X", op[0]&0x00FFFFFF, op[1], op[2]);
else
sprintf(mnem, "move memory %X, %08X, %08X", op[0]&0x00FFFFFF, op[1], op[2]);
return 3;
}
else if ((op[0]&0xF8000000) == 0x98000000)
{
// INT and INTFLY
mnem += sprintf(mnem, "%s %08X, ", (op[0]&0x00100000)?"intfly ":"int ", op[1]);
carry = op[0]&0x00200000;
bitTrue = op[0]&0x00080000;
compData = op[0]&0x00040000; // data (if set)
compPhase = op[0]&0x00020000; // phase (if set) (or atn if neither phase nor data set)
wait = op[0]&0x00010000;
data = op[0]&0xFF;
mask = (op[0]>>8)&0xFF;
if (carry)
sprintf(mnem, "%s%s carry", wait?"if":"when", bitTrue?"":" not");
else
{
mnem += sprintf(mnem, "%s%s ", wait?"if":"when", bitTrue?"":" not");
if (!compPhase && !compData)
sprintf(mnem, "atn");
else
{
if (compPhase)
{
mnem += sprintf(mnem, phase[(op[0]>>24)&7]);
if (compData)
mnem += sprintf(mnem, " and ");
}
if (compData)
mnem += sprintf(mnem, "%02X mask %02X", data, mask);
}
}
return 2;
}
return 0; // no match found
}
/******************************************************************************
Standalone Disassembler
Define STANDALONE to build a command line-driven SCRIPTS disassembler.
******************************************************************************/
#ifdef STANDALONE
static void PrintUsage(void)
{
puts("scriptsd Version "DISASM_VERSION" by Bart Trzynadlowski: NCR 53C810 SCRIPTS Disassembler");
puts("Usage: scriptsd <file> [options]");
puts("Options: -?,-h Show this help text");
puts(" -s <offset> Start offset (hexadecimal)");
puts(" -l <num> Number of instructions (Default=16)");
puts(" -o <addr> Set origin (hexadecimal)");
exit(0);
}
/*
* main(argc, argv):
*
* Standalone SCRIPTS disassembler.
*/
int main(int argc, char **argv)
{
char mnem[64];
FILE *fp;
UINT8 *buffer;
unsigned i, num, offset, fsize, start = 0, len, org, file = 0;
UINT32 op[3];
BOOL len_specified = 0, org_specified = 0;
char *c;
if (argc <= 1)
PrintUsage();
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
PrintUsage();
else if (!strcmp(argv[i], "-s"))
{
++i;
if (i >= argc)
fprintf(stderr, "scriptsd: warning: no argument to %s\n", "-s");
else
start = strtoul(argv[i], &c, 16);
}
else if (!strcmp(argv[i], "-l"))
{
++i;
if (i >= argc)
fprintf(stderr, "scriptsd: warning: no argument to %s\n", "-l");
else
{
len = atoi(argv[i]);
len_specified = 1;
}
}
else if (!strcmp(argv[i], "-o"))
{
++i;
if (i >= argc)
fprintf(stderr, "scriptsd: warning: no argument to %s\n", "-o");
else
{
org = strtoul(argv[i], &c, 16);
org_specified = 1;
}
}
else
file = i;
}
if (!file)
{
fprintf(stderr, "scriptsd: no input file specified\n");
exit(1);
}
/*
* Load file
*/
if ((fp = fopen(argv[file], "rb")) == NULL)
{
fprintf(stderr, "scriptsd: failed to open file: %s\n", argv[file]);
exit(1);
}
fseek(fp, 0, SEEK_END);
fsize = ftell(fp);
rewind(fp);
// Allocate some extra padding for operands
if ((buffer = (UINT8 *) calloc(fsize+4*3, sizeof(UINT8))) == NULL)
{
fprintf(stderr, "scriptsd: not enough memory to load input file: %s, %lu bytes\n", argv[file], (unsigned long) fsize);
fclose(fp);
exit(1);
}
fread(buffer, sizeof(UINT8), fsize, fp);
fclose(fp);
if (!len_specified)
len = 16;
if (!org_specified)
org = start;
/*
* Disassemble!
*/
offset = start;
for (i = 0; (i<len) && (offset<fsize); i++)
{
op[0] = (buffer[offset+3]<<24)|(buffer[offset+2]<<16)|(buffer[offset+1]<<8)|buffer[offset+0];
op[1] = (buffer[offset+7]<<24)|(buffer[offset+6]<<16)|(buffer[offset+5]<<8)|buffer[offset+4];
op[2] = (buffer[offset+11]<<24)|(buffer[offset+10]<<16)|(buffer[offset+9]<<8)|buffer[offset+8];
num = DisassembleSCRIPTS(op, org, mnem);
if (0 == num)
{
printf("%08X: %08X ?\n", org, op[0]);
offset += 4;
org += 4;
}
else
{
if (num == 3)
printf("%08X: %08X %08X %08X %s\n", org, op[0], op[1], op[2], mnem);
else if (num == 2)
printf("%08X: %08X %08X %s\n", org, op[0], op[1], mnem);
else
printf("%08X: %08X %s\n", org, op[0], mnem);
offset += num*4;
org += num*4;
}
}
free(buffer);
return 0;
}
#endif

263
Model3/93C46.cpp Normal file
View file

@ -0,0 +1,263 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* 93C46.cpp
*
* Implementation of the 93C46 serial EEPROM device.
*
* To-Do List
* ----------
* - Manual says that when a READ command is issued, the data will be
* preceded by a leading 0. This seems to cause problems. Perhaps DO
* should just be set to 0?
*/
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Save States
******************************************************************************/
void C93C46::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("93C46", __FILE__);
SaveState->Write(regs, sizeof(regs));
SaveState->Write(&CS, sizeof(CS));
SaveState->Write(&CLK, sizeof(CLK));
SaveState->Write(&DI, sizeof(DI));
SaveState->Write(&DO, sizeof(DO));
SaveState->Write(&bitBufferOut, sizeof(bitBufferOut));
SaveState->Write(&bitBufferIn, sizeof(bitBufferIn));
SaveState->Write(&bitsOut, sizeof(bitsOut));
SaveState->Write(&receiving, sizeof(receiving));
SaveState->Write(&addr, sizeof(addr));
SaveState->Write(&busyCycles, sizeof(busyCycles));
SaveState->Write(&locked, sizeof(locked));
}
void C93C46::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("93C46"))
{
ErrorLog("Unable to load EEPROM state. File is corrupted.");
return;
}
SaveState->Read(regs, sizeof(regs));
SaveState->Read(&CS, sizeof(CS));
SaveState->Read(&CLK, sizeof(CLK));
SaveState->Read(&DI, sizeof(DI));
SaveState->Read(&DO, sizeof(DO));
SaveState->Read(&bitBufferOut, sizeof(bitBufferOut));
SaveState->Read(&bitBufferIn, sizeof(bitBufferIn));
SaveState->Read(&bitsOut, sizeof(bitsOut));
SaveState->Read(&receiving, sizeof(receiving));
SaveState->Read(&addr, sizeof(addr));
SaveState->Read(&busyCycles, sizeof(busyCycles));
SaveState->Read(&locked, sizeof(locked));
}
/******************************************************************************
Emulation Functions
******************************************************************************/
// Reverse the bit ordering
static UINT16 ReverseBits16(UINT16 data)
{
UINT16 dataOut = 0, hi, lo;
for (int i = 0; i < 16/2; i++)
{
// Isolate corresponding bits from high and low halves of word
lo = (data>>i)&1;
hi = (data>>(15-i))&1;
// Swap them
dataOut |= (lo<<(15-i));
dataOut |= (hi<<i);
}
return dataOut;
}
void C93C46::Write(unsigned pinCS, unsigned pinCLK, unsigned pinDI)
{
unsigned prevCLK;
//printf("EEPROM: CS=%d CLK=%d DI=%d\n", pinCS, pinCLK, pinDI);
prevCLK = CLK;
// Save current inputs
CS = !!pinCS;
CLK = !!pinCLK;
DI = !!pinDI;
// Active high CS. When it's brought low, reset control logic.
if (CS == 0)
{
bitBufferIn = 0; // this must be cleared each time (only leading 0's can exist prior to commands)
receiving = TRUE; // ready to accept commands
busyCycles = 5; // some applications require the chip to take time while writing
return;
}
// Rising clock edge
if (!prevCLK && CLK)
{
if (receiving == TRUE) // is the chip receiving commands?
{
// Shift in a new bit
bitBufferIn <<= 1;
bitBufferIn |= DI;
// Detect commands
if ((bitBufferIn&0xFFFFFFC0) == 0x180) // READ
{
addr = bitBufferIn&0x3F;
bitBufferOut = ReverseBits16(regs[addr]); // reverse so that D15 is shifted out first
//bitBufferOut <<= 1; // a leading 0 precedes the first word read (causes problems)
bitsOut = 0; // how many bits read out
receiving = FALSE; // transmitting data now
DebugLog("93C46: READ %X\n", addr);
}
else if (bitBufferIn == 0x13) // WEN (write enable)
{
locked = FALSE;
DebugLog("93C46: WEN\n");
}
else if (bitBufferIn == 0x10) // WDS (write disable)
{
locked = TRUE;
DebugLog("93C46: WDS\n");
}
else if ((bitBufferIn&0xFFC00000) == 0x01400000) // WRITE
{
if (!locked)
regs[(bitBufferIn>>16)&0x3F] = bitBufferIn&0xFFFF;
DO = 1; // ready (write completed)
DebugLog("93C46: WRITE %X=%04X (lock=%d)\n", (bitBufferIn>>16)&0x3F, bitBufferIn&0xFFFF, locked);
}
else if ((bitBufferIn&0xFFF00000) == 0x01100000) // WRALL (write all)
{
if (!locked)
{
for (int i = 0; i < 64; i++)
regs[i] = bitBufferIn&0xFFFF;
}
DO = 1;
DebugLog("93C46: WRALL %04X (lock=%d)\n", bitBufferIn&0xFFFF, locked);
}
else if ((bitBufferIn&0xFFFFFFC0) == 0x1C0) // ERASE
{
if (!locked)
regs[bitBufferIn&0x3F] = 0xFFFF;
DO = 1;
DebugLog("93C46: ERASE %X (lock=%d)\n", bitBufferIn&0x3F, locked);
}
else if ((bitBufferIn&0xFFFFFFF0) == 0x120) // ERALL (erase all)
{
if (!locked)
{
for (int i = 0; i < 64; i++)
regs[i] = 0xFFFF;
DebugLog("93C46: ERALL (lock=%d)\n", locked);
}
DO = 1;
}
}
else // the chip is reading out data (transmitting)
{
// Shift out to DO pin
DO = bitBufferOut&1;
bitBufferOut >>= 1;
++bitsOut;
// If we've shifted out an entire 16-bit word, load up the next address (no preceding 0)
if (bitsOut == 16)
{
addr = (addr+1)&0x3F;
bitBufferOut = ReverseBits16(regs[addr]);
bitsOut = 0;
DebugLog("93C46: Next word loaded: %X\n", addr);
}
}
}
}
unsigned C93C46::Read(void)
{
// When not transmitting, DO indicates whether busy or not
if (receiving)
{
if (busyCycles > 0) // simulate programming delay
{
--busyCycles;
return 0; // busy
}
else
return 1; // ready to accept new command
}
// Transmit data
return DO;
}
void C93C46::Clear(void)
{
memset(regs, 0xFF, sizeof(regs));
}
void C93C46::Reset(void)
{
receiving = TRUE;
locked = TRUE;
bitBufferIn = 0;
bitBufferOut = 0;
addr = 0;
busyCycles = 0;
CS = 0;
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void C93C46::Init(void)
{
// this function really only exists for consistency with other device classes
}
C93C46::C93C46(void)
{
memset(regs, 0xFF, sizeof(regs));
DebugLog("Built 93C46 EEPROM\n");
}
C93C46::~C93C46(void)
{
DebugLog("Destroyed 93C46 EEPROM\n");
}

127
Model3/93C46.h Normal file
View file

@ -0,0 +1,127 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* 93C46.h
*
* Header file defining the C93C46 class: 93C46 EEPROM.
*/
#ifndef INCLUDED_93C46_H
#define INCLUDED_93C46_H
/*
* C93C46:
*
* 93C46 serial EEPROM.
*/
class C93C46
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* Clear(void):
*
* Clears the EEPROM contents by writing all 1's.
*/
void Clear(void);
/*
* Write(pinCS, pinCLK, pinDI):
*
* Write to the EEPROM. All inputs must be either 0 or 1 only!
*
* Parameters:
* pinCS Chip select.
* pinCLK Serial data clock.
* pinDI Serial data input.
*/
void Write(unsigned pinCS, unsigned pinCLK, unsigned pinDI);
/*
* Read(void):
*
* Read from the EEPROM.
*
* Returns:
* The serial data output bit (either a 1 or 0).
*/
unsigned Read(void);
/*
* Reset(void):
*
* Resets the device, putting it into a locked state. Does not modify the
* memory.
*/
void Reset(void);
/*
* Init(void):
*
* One-time initialization of the context. Must be called prior to all
* other members.
*/
void Init(void);
/*
* C93C46(void):
* ~C93C46(void):
*
* Constructor and destructor.
*/
C93C46(void);
~C93C46(void);
private:
UINT16 regs[64]; // memory: 64 16-bit registers
unsigned CS, CLK, DI, DO; // pins
UINT32 bitBufferOut; // bits to be shifted out
UINT32 bitBufferIn; // stores bits as they are shifted in
int bitsOut; // how many bits have been shifted out
BOOL receiving; // if true, accepting data, if false, sending data out (read commands)
unsigned addr; // latched address
int busyCycles; // when > 0, counts down delay cycles and indicates busy
BOOL locked; // whether the EEPROM is in a locked state
};
#endif // INCLUDED_93C46_H

81
Model3/Bus.h Normal file
View file

@ -0,0 +1,81 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Bus.h
*
* Header file for the CBus abstract base class.
*/
#ifndef INCLUDED_BUS_H
#define INCLUDED_BUS_H
/*
* CBus:
*
* An abstract base class for an address bus. Defines handlers for 8-, 16-,
* 32-, and 64-bit random access. All accesses are big endian.
*/
class CBus
{
public:
/*
* Read8(addr):
* Read16(addr):
* Read32(addr):
* Read64(addr):
*
* Read handlers.
*
* Parameters:
* addr Address (caller should ensure it is aligned to the correct
* boundary, corresponding to the size).
*
* Returns:
* Data of the appropriate size (8, 16, 32, or 64 bits).
*/
virtual UINT8 Read8(UINT32 addr) = 0;
virtual UINT16 Read16(UINT32 addr) = 0;
virtual UINT32 Read32(UINT32 addr) = 0;
virtual UINT64 Read64(UINT32 addr) = 0;
/*
* Write8(addr, data):
* Write16(addr, data):
* Write32(addr, data):
* Write64(addr, data):
*
* Write handlers.
*
* Parameters:
* addr Address (caller should ensure it is aligned to the correct
* boundary, corresponding to the size).
* data Data to write.
*/
virtual void Write8(UINT32 addr, UINT8 data) = 0;
virtual void Write16(UINT32 addr, UINT16 data) = 0;
virtual void Write32(UINT32 addr, UINT32 data) = 0;
virtual void Write64(UINT32 addr, UINT64 data) = 0;
};
#endif // INCLUDED_BUS_H

126
Model3/IRQ.cpp Normal file
View file

@ -0,0 +1,126 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* IRQ.cpp
*
* Model 3 IRQ controller. Implementation of the CIRQ class.
*
* To-Do List:
* -----------
* - When a proper OO CPU core is added, the CPU object should be hooked to the
* IRQ controller to assert/deassert the PowerPC IRQ line. Right now, we just
* call the PPC core directly, which should not happen in proper OO code.
*/
#include "Supermodel.h"
/******************************************************************************
Save States
******************************************************************************/
void CIRQ::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("IRQ", __FILE__);
SaveState->Write(&irqEnable, sizeof(irqEnable));
SaveState->Write(&irqState, sizeof(irqState));
}
void CIRQ::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("IRQ"))
{
ErrorLog("Unable to load IRQ controller state. Save state file is corrupted.");
return;
}
SaveState->Read(&irqEnable, sizeof(irqEnable));
SaveState->Read(&irqState, sizeof(irqState));
}
/******************************************************************************
Emulation Functions
******************************************************************************/
void CIRQ::Assert(unsigned irqBits)
{
irqState |= irqBits;
if ((irqState&irqEnable)) // low 8 bits are maskable interrupts
//ppc_set_irq_line(0);
ppc_set_irq_line(1);
if ((irqState&(~0xFF))) // any non-maskable interrupts pending?
//ppc_set_irq_line(0);
ppc_set_irq_line(1);
}
//TO-DO: CPU needs to have deassert logic!
void CIRQ::Deassert(unsigned irqBits)
{
irqState &= ~irqBits;
}
void CIRQ::WriteIRQEnable(UINT8 data)
{
irqEnable = (unsigned) data;
}
UINT8 CIRQ::ReadIRQEnable(void)
{
return (UINT8) (irqEnable&0xFF);
}
UINT8 CIRQ::ReadIRQState(void)
{
return (UINT8) (irqState&0xFF);
}
void CIRQ::Reset(void)
{
irqEnable = 0; // disable all
irqState = 0; // no IRQs pending
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void CIRQ::Init(void)
{
// this function really only exists for consistency with other device classes
}
CIRQ::CIRQ(void)
{
DebugLog("Built IRQ controller\n");
}
/*
* CIRQ::~CIRQ(void):
*
* Destructor.
*/
CIRQ::~CIRQ(void)
{
DebugLog("Destroyed IRQ controller\n");
}

148
Model3/IRQ.h Normal file
View file

@ -0,0 +1,148 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* IRQ.h
*
* Header file defining the CIRQ class: Model 3 IRQ controller.
*/
#ifndef INCLUDED_IRQ_H
#define INCLUDED_IRQ_H
/*
* CIRQ:
*
* Model 3 IRQ controller.
*/
class CIRQ
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* Assert(irqBits):
*
* Assert IRQ(s). Also asserts the CPU IRQ line (unless no IRQs are
* actually asserted). The IRQ controller only tracks 8 state bits but for
* non-maskable interrupts, higher bits (bits 9 and above) can be used.
* They will be tracked internally but not visible when external hardware
* tries reading the IRQ state.
*
* Parameters:
* irqBits IRQ bits corresponding to the IRQ state register (1
* indicates assertion, 0 does nothing).
*/
void Assert(unsigned irqBits);
/*
* Deassert(irqBits):
*
* Deasserts the specified IRQs and, if there are no pending IRQs left,
* deasserts the CPU IRQ line.
*
* Parameters:
* irqBits IRQ bits corresponding to the IRQ state register (1
* indicates deassertion, 0 does nothing).
*/
void Deassert(unsigned irqBits);
/*
* ReadIRQEnable(void):
*
* Returns:
* The 8-bit IRQ enable register.
*/
UINT8 ReadIRQEnable(void);
/*
* WriteIRQEnable(data):
*
* Write to the IRQ enable register. A 1 indicates the IRQ is enabled. Only
* the first 8 IRQs can be masked.
*
* Parameters:
* data IRQ enable bits (8 bits).
*/
void WriteIRQEnable(UINT8 data);
/*
* ReadIRQState(void):
*
* Accesses the IRQ state register. Set bits indicate pending IRQs. Only
* the low 8 bits are returned. Any IRQs occupying bits above this are
* effectively invisible and non-maskable.
*
* Returns:
* The 8-bit IRQ state register.
*/
UINT8 ReadIRQState(void);
/*
* Reset(void):
*
* Resets the IRQ controller. All IRQs are disabled.
*/
void Reset(void);
/*
* Init(void):
*
* One-time initialization of the context. Must be called prior to all
* other members.
*/
void Init(void);
/*
* CIRQ(void):
* ~CIRQ(void):
*
* Constructor and destructor.
*/
CIRQ(void);
~CIRQ(void);
private:
unsigned irqEnable; // 8 bits, 1=enabled, 0=disabled
unsigned irqState; // bits correspond to irqEnable, 1=pending, 0=not pending
};
#endif // INCLUDED_IRQ_H

357
Model3/MPC10x.cpp Normal file
View file

@ -0,0 +1,357 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* MPC10x.cpp
*
* Implementation of the CMPC10x class: Motorola MPC105 and MPC106 PCI/bridge
* controllers. For now, only a single PCI bus can be attached and all requests
* are forwarded to it.
*
* This is a minimal implementation designed with the Model 3 in mind. It does
* not properly emulate the device and there are numerous inaccuracies in this
* code.
*
* Important Problems To Be Aware Of
* ---------------------------------
*
* The handling of accesses smaller than 32 bits is funky and probably very
* incorrect, particularly for non-32-bit-aligned register numbers. This
* problem extends to the entire PCI emulation.
*
* Little endian mode is not supported (the internal registers, however, seem
* to be stored in little endian format). The registers are implemented as byte
* read/writeable but this is not accurate. In fact, some are read only, and
* some can only be accessed as 16-bit or 32-bit words.
*
* This code assumes we are running on a little endian machine and that the
* PowerPC is a big endian machine. Therefore, bytes can be written to the
* register space directly but multi-byte words must be flipped before being
* interpreted internally.
*
* External configuration registers are not implemented.
*
* Multiple PCI buses are not yet supported (everything is assumed to be on
* bus 0).
*
* ... And lots more!
*
* References
* ----------
* 1. "MPC106 PCI/Bridge/Memory Controller User's Manual" (MPC106UM/D Rev.1)
*
* To-Do List
* ----------
* - The whole PCI system desperately needs a re-write to deal with different
* bit-width accesses in a cleaner manner.
* - Fix endian confusion. Should assume everything written is in the correct
* endian, pushing the responsibility onto the caller.
*/
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Save States
******************************************************************************/
void CMPC10x::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("MPC10x", __FILE__);
SaveState->Write(regs, sizeof(regs));
SaveState->Write(&pciBus, sizeof(pciBus));
SaveState->Write(&pciDevice, sizeof(pciDevice));
SaveState->Write(&pciFunction, sizeof(pciFunction));
SaveState->Write(&pciReg, sizeof(pciReg));
}
void CMPC10x::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("MPC10x"))
{
ErrorLog("Unable to load MPC%X state. Save state file is corrupted.", model);
return;
}
SaveState->Read(regs, sizeof(regs));
SaveState->Read(&pciBus, sizeof(pciBus));
SaveState->Read(&pciDevice, sizeof(pciDevice));
SaveState->Read(&pciFunction, sizeof(pciFunction));
SaveState->Read(&pciReg, sizeof(pciReg));
}
/******************************************************************************
Emulation Functions
******************************************************************************/
/*
* CMPC10x::WritePCIConfigAddress(data):
*
* Writes to the PCI configuration space address (CONFIG_ADDR) register, which
* selects a PCI device and configuration space register.
*/
void CMPC10x::WritePCIConfigAddress(UINT32 data)
{
UINT32 d = FLIPENDIAN32(data);
pciBus = (d>>16)&0xFF;
pciDevice = (d>>11)&0x1F;
pciFunction = (d>>8)&7;
pciReg = d&0xFF; // NOTE: for actual PCI devices (device>0), register must be shifted right by 2 and clamped to 0x3F
// The manual is unclear as to whether device 0 (MPC10x) register #s are clamped to 0x3F or not. Pay attention to this!
#ifdef DEBUG
if (pciDevice == 0)
{
DebugLog("MPC10x: Device 0 configuration access: %08X\n", d);
if ((d&3))
ErrorLog("MPC10x: Device 0 configuration address with low bits set: %08X\n", d);
}
//DebugLog("MPC10x: Bus=%X, Device=%X, Function=%X, Reg=%X\n", pciBus, pciDevice, pciFunction, pciReg);
#endif
if (pciBus != 0)
{
//printf("Multiple PCI buses detected!\n");
DebugLog("Multiple PCI buses detected!\n");
}
}
/*
* CMPC10x::ReadPCIConfigData(bits, offset):
*
* Reads from the PCI configuration space data (CONFIG_DATA) register, which in
* turn calls upon the selected PCI device to return the data.
*/
UINT32 CMPC10x::ReadPCIConfigData(unsigned bits, unsigned offset)
{
// Handle self-access first
if (pciDevice == 0)
{
// Alignment check
#ifdef DEBUG
if (((bits==16)&&(offset&1)) || ((bits==32)&&(offset&3)))
ErrorLog("Misaligned MPC10x read request (bits=%d,reg=%X,offset=%d)\n", bits, pciReg, offset);
#endif
switch (bits)
{
case 8:
return regs[pciReg+offset];
case 16:
return (regs[pciReg+offset+0]<<8) | regs[pciReg+offset+1];
case 32:
return (regs[pciReg+offset+0]<<24) |
(regs[pciReg+offset+1]<<16) |
(regs[pciReg+offset+2]<<8) |
regs[pciReg+offset+3];
default:
ErrorLog("MPC10x internal error: invalid access size (%d-bits)", bits);
break;
}
}
// All other PCI devices passed to PCI bus
return PCIBus->ReadConfigSpace(pciDevice, (pciReg>>2)&0x3C, bits, offset);
}
/*
* CMPC10x::WritePCIConfigData(bits, offset, data):
*
* Writes to the PCI configuration space data (CONFIG_DATA) register, which in
* turn passes the data to the selected PCI device.
*/
void CMPC10x::WritePCIConfigData(unsigned bits, unsigned offset, UINT32 data)
{
// Handle self-access first
if (pciDevice == 0)
{
// Alignment check
#ifdef DEBUG
if (((bits==16)&&(offset&1)) || ((bits==32)&&(offset&3)))
ErrorLog("Misaligned MPC10x read request (bits=%d,reg=%X,offset=%d)\n", bits, pciReg, offset);
#endif
switch (bits)
{
case 8:
regs[pciReg+offset] = data&0xFF;
break;
case 16:
regs[pciReg+offset+0] = (data>>8)&0xFF;
regs[pciReg+offset+1] = data&0xFF;
break;
case 32:
regs[pciReg+offset+0] = (data>>24)&0xFF;
regs[pciReg+offset+1] = (data>>16)&0xFF;
regs[pciReg+offset+2] = (data>>8)&0xFF;
regs[pciReg+offset+3] = data&0xFF;
break;
default:
ErrorLog("MPC10x internal error: invalid access size (%d-bits)", bits);
break;
}
return;
}
PCIBus->WriteConfigSpace(pciDevice, (pciReg>>2)&0x3C, bits, offset, data);
}
/*
* CMPC10x::WriteRegister(reg, data):
*
* Writes to the MPC10x register space. Accesses one byte at a time so it
* should be endian-neutral (the caller ends up being responsible for this).
*/
void CMPC10x::WriteRegister(unsigned reg, UINT8 data)
{
regs[reg&0xFF] = data;
if ((reg&0xFF)==0xA8)
{
if ((data&0x20))
ErrorLog("MPC10x little endian mode not yet implemented!");
}
}
/*
* CMPC10x::Reset(void):
*
* Resets the device.
*/
void CMPC10x::Reset(void)
{
memset(regs, 0, sizeof(regs));
// Data is actually stored in little endian format, so we can write directly here
*(UINT16 *) &regs[0x00] = 0x1057; // vendor ID (Motorola)
*(UINT16 *) &regs[0x02] = (model==0x106)?0x0002:0x0001; // device ID (MPC105 or MPC106)
if (model == 0x106) // MPC106
{
*(UINT32 *) &regs[0x04] = 0x00800006; // PCI command and PCI status
*(UINT32 *) &regs[0x08] = 0x00060000; // class code and revision ID
*(UINT32 *) &regs[0x0C] = 0x00000800; // cache line size
*(UINT32 *) &regs[0x70] = 0x00CD0000; // output driver control
*(UINT32 *) &regs[0xA8] = 0x0010FF00; // processor interface config. 1
*(UINT32 *) &regs[0xAC] = 0x060C000C; // processor interface config. 2
*(UINT32 *) &regs[0xB8] = 0x04000000; // TO-DO: CHECK MANUAL
*(UINT32 *) &regs[0xC0] = 0x00000100; // error enabling 1
*(UINT32 *) &regs[0xE0] = 0x00420FFF; // emulation support configuration 1
*(UINT32 *) &regs[0xE8] = 0x00200000; // emulation support configuration 2
*(UINT32 *) &regs[0xF0] = 0x0000FF02; // memory control config. 1
*(UINT32 *) &regs[0xF4] = 0x00030000; // memory control config. 2
*(UINT32 *) &regs[0xFC] = 0x00000010; // memory control config. 4
}
else // MPC105
{
*(UINT32 *) &regs[0x04] = 0x00800006; // PCI command and PCI status
*(UINT32 *) &regs[0x08] = 0x00060000; // class code and revision ID
*(UINT32 *) &regs[0xA8] = 0x0010FF00; // processor interface config. 1
*(UINT32 *) &regs[0xAC] = 0x060C000C; // processor interface config. 2
*(UINT32 *) &regs[0xB8] = 0x04000000; // TO-DO: CHECK MANUAL
*(UINT32 *) &regs[0xF0] = 0x0000FF02; // memory control config. 1
*(UINT32 *) &regs[0xF4] = 0x00030000; // memory control config. 2
*(UINT32 *) &regs[0xFC] = 0x00000010; // memory control config. 4
// To-do: any more??
}
pciBus = 0;
pciDevice = 0;
pciFunction = 0;
pciReg = 0;
DebugLog("MPC%X reset\n", model);
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
/*
* CMPC10x:AttachPCIBus(BusObjectPtr):
*
* Attaches a PCI bus object which will handle all PCI register requests.
*/
void CMPC10x::AttachPCIBus(CPCIBus *BusObjectPtr)
{
PCIBus = BusObjectPtr;
DebugLog("MPC10x connected to a PCI bus\n");
}
/*
* CMPC10x::SetModel(modelNum):
*
* Sets the device behavior to either MPC105 or MPC106.
*/
void CMPC10x::SetModel(int modelNum)
{
model = modelNum;
if ((modelNum!=0x105) && (modelNum!=0x106))
{
ErrorLog("%s:%s: Invalid MPC10x model number (%X).", __FILE__, __LINE__, modelNum);
model = 0x105;
}
DebugLog("MPC10x set to MPC%X\n", model);
}
/*
* CMPC10x::Init():
*
* This must be called first and only once during the lifetime of the class.
*/
void CMPC10x::Init(void)
{
// this function really only exists for consistency with other device classes
}
/*
* CMPC10x::CMPC10x(void):
*
* Constructor.
*/
CMPC10x::CMPC10x(void)
{
PCIBus = NULL;
model = 0x105; // default to MPC105
pciBus = 0;
pciDevice = 0;
pciFunction = 0;
pciReg = 0;
DebugLog("Built MPC10x\n");
}
/*
* CMPC10x::~CMPC10x(void):
*
* Destructor.
*/
CMPC10x::~CMPC10x(void)
{
PCIBus = NULL;
DebugLog("Destroyed MPC10x\n");
}

178
Model3/MPC10x.h Normal file
View file

@ -0,0 +1,178 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* MPC10x.h
*
* Header file defining the CMPC10x class (Motorola MPC105 and MPC106).
*/
#ifndef INCLUDED_MPC10X_H
#define INCLUDED_MPC10X_H
/*
* CMPC10x:
*
* MPC105 or MPC106 PCI/bridge/memory controller device.
*/
class CMPC10x
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* WritePCIConfigAddress(data):
*
* Writes to the PCI configuration space address (CONFIG_ADDR) register,
* which selects a PCI device and register. For now, only 32-bit accesses
* are supported.
*
* Parameters:
* data Data to write.
*/
void WritePCIConfigAddress(UINT32 data);
/*
* ReadPCIConfigData(bits, offset):
*
* Reads from the PCI configuration space data (CONFIG_DATA) register,
* which in turn calls upon the selected PCI device to return the data.
*
* Parameters:
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within CONFIG_DATA, aligned to bit width of
* access. For bytes, can be 0, 1, 2, or 3, but for 16-bit
* words can only be 0 or 2. The offset is just the
* CONFIG_DATA address ANDed with 0, 2, or 3 depending on the
* bit width (32, 16, or 8, respectively).
*
* Returns:
* Data from the PCI conifguration space of the presently selected
* device. Bit width will be the same as specified by 'bits'.
*/
UINT32 ReadPCIConfigData(unsigned bits, unsigned offset);
/*
* WritePCIConfigData(bits, offset, data):
*
* Writes data (CONFIG_DATA) to the presently selected PCI device and
* register.
*
* Parameters:
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within CONFIG_DATA, aligned to bit width of
* access.
* data Data to write.
*/
void WritePCIConfigData(unsigned bits, unsigned offset, UINT32 data);
/*
* WriteRegister(reg, data):
*
* Writes to the MPC10x's internal registers.
*
* Parameters:
* reg Register (0-255).
* data Data to write.
*/
void WriteRegister(unsigned reg, UINT8 data);
/*
* Reset(void):
*
* Resets the device.
*/
void Reset(void);
/*
* AttachPCIBus(BusObjectPtr):
*
* Attaches a PCI bus object which will handle all PCI register accesses.
* For now, only one bus is supported. The MPCI10x will automatically
* handle any requests for device 0 (itself) without passing them on to the
* PCI bus.
*
* Parameters:
* BusObjectPtr A pointer to the PCI bus object.
*/
void AttachPCIBus(CPCIBus *BusObjectPtr);
/*
* SetModel(modelNum):
*
* Selects either MPC105 or MPC106 behavior. This will also reset the
* device state by calling Reset(). This should be called prior to any
* other emulation functions and after Init().
*
* Parameters:
* modelNum Either 0x105 for MPC105 or 0x106 for MPC106. Defaults
* to MPC105 if unrecognized.
*/
void SetModel(int modelNum);
/*
* Init(void):
*
* One-time initialization of the context. Must be called prior to all
* other members. By default, initializes to MPC105.
*/
void Init(void);
/*
* CMPC10x(void):
* ~CMPC10x(void):
*
* Constructor and destructor for MPC10x class.
*/
CMPC10x(void);
~CMPC10x(void);
private:
int model; // MPC105 = 0x105 (default), MPC106 = 0x106
CPCIBus *PCIBus; // the PCI bus to which this MPC10x is attached
UINT8 regs[256]; // internal registers
unsigned pciBus; // PCI bus component of PCI address setting (8 bits)
unsigned pciDevice; // PCI device component (5 bits)
unsigned pciFunction; // PCI function component (3 bits)
unsigned pciReg; // PCI register component (7 bits)
};
#endif // INCLUDED_MPC10X_H

2363
Model3/Model3.cpp Normal file

File diff suppressed because it is too large Load diff

318
Model3/Model3.h Normal file
View file

@ -0,0 +1,318 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Model3.h
*
* Header file defining the CModel3 and CModel3Inputs classes.
*/
#ifndef INCLUDED_MODEL3_H
#define INCLUDED_MODEL3_H
#include "Inputs/Inputs.h"
/*
* CModel3:
*
* A complete Model 3 system.
*
* Inherits CBus in order to pass the address space handlers to devices that
* may need them (CPU, DMA, etc.)
*
* NOTE: Currently NOT re-entrant due to a non-OOP PowerPC core. Do NOT create
* create more than one CModel3 object!
*/
class CModel3: public CBus, public CPCIDevice
{
public:
/*
* ReadPCIConfigSpace(device, reg, bits, offset):
*
* Handles unknown PCI devices. See CPCIDevice definition for more details.
*
* Parameters:
* device Device number.
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).;
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
*
* Returns:
* Register data.
*/
UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width);
/*
* WritePCIConfigSpace(device, reg, bits, offset, data):
*
* Handles unknown PCI devices. See CPCIDevice definition for more details.
*
* Parameters:
* device Device number.
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
* data Data.
*/
void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, UINT32 data);
/*
* Read8(addr):
* Read16(addr):
* Read32(addr):
* Read64(addr):
*
* Read a byte, 16-bit half word, 32-bit word, or 64-bit double word from
* the PowerPC address space. This implements the PowerPC address bus. Note
* that it is big endian, so when accessing from a little endian device,
* the byte order must be manually reversed.
*
* Parameters:
* addr Address to read.
*
* Returns:
* Data at the address.
*/
UINT8 Read8(UINT32 addr);
UINT16 Read16(UINT32 addr);
UINT32 Read32(UINT32 addr);
UINT64 Read64(UINT32 addr);
/*
* Write8(addr, data):
* Write16(addr, data):
* Write32(addr, data):
* Write64(addr, data):
*
* Write a byte, half word, word, or double word to the PowerPC address
* space. Note that everything is stored in big endian form, so when
* accessing with a little endian device, the byte order must be manually
* reversed.
*
* Parameters:
* addr Address to write.
* data Data to write.
*/
void Write8(UINT32 addr, UINT8 data);
void Write16(UINT32 addr, UINT16 data);
void Write32(UINT32 addr, UINT32 data);
void Write64(UINT32 addr, UINT64 data);
/*
* SaveState(SaveState):
*
* Saves an image of the current state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and resumes execution from a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* SaveNVRAM(NVRAM):
*
* Saves an image of the current NVRAM state.
*
* Parameters:
* NVRAM Block file to save NVRAM to.
*/
void SaveNVRAM(CBlockFile *NVRAM);
/*
* LoadNVRAM(NVRAM):
*
* Loads an NVRAM image.
*
* Parameters:
* NVRAM Block file to load NVRAM state from.
*/
void LoadNVRAM(CBlockFile *NVRAM);
/*
* ClearNVRAM(void):
*
* Clears all NVRAM (backup RAM and EEPROM).
*/
void ClearNVRAM(void);
/*
* RunFrame(void):
*
* Runs one frame (assuming 60 Hz video refresh rate).
*/
void RunFrame(void);
/*
* Reset(void):
*
* Resets the system. Does not modify non-volatile memory.
*/
void Reset(void);
/*
* GetGameInfo(void):
*
* Returns:
* A pointer to the presently loaded game's information structure (or
* NULL if no ROM set has yet been loaded).
*/
const struct GameInfo * GetGameInfo(void);
/*
* LoadROMSet(GameList, zipFile):
*
* Loads a complete ROM set from the specified ZIP archive.
*
* Parameters:
* GameList List of all supported games and their ROMs.
* zipFile ZIP file to load from.
*
* Returns:
* OKAY if successful, FAIL otherwise. Prints errors.
*/
BOOL LoadROMSet(const struct GameInfo *GameList, const char *zipFile);
/*
* AttachRenderers(Render2DPtr, Render3DPtr):
*
* Attaches the renderers to the appropriate device objects.
*
* Parameters:
* Render2DPtr Pointer to a tile renderer object.
* Render3DPtr Same as above but for a 3D renderer.
*/
void AttachRenderers(CRender2D *Render2DPtr, CRender3D *Render3DPtr);
/*
* AttachInputs(InputsPtr):
*
* Attaches OSD-managed inputs.
*
* Parameters:
* InputsPtr Pointer to the object containing input states.
*/
void AttachInputs(CInputs *InputsPtr);
/*
* Init(ppcFrequencyParam):
*
* One-time initialization of the context. Must be called prior to all
* other members. Allocates memory and initializes device states.
*
* Parameters:
* ppcFrequencyParam PowerPC frequency in Hz. If less than 1 MHz,
* will be clamped to 1 MHz.
*
* Returns:
* OKAY is successful, otherwise FAILED if a non-recoverable error
* occurred. Prints own error messages.
*/
BOOL Init(unsigned ppcFrequencyParam);
/*
* CModel3(void):
* ~CModel3(void):
*
* Constructor and destructor for Model 3 class. Constructor performs a
* bare-bones initialization of object; does not perform any memory
* allocation or any actions that can fail. The destructor will deallocate
* memory and free resources used by the object (and its child objects).
*/
CModel3(void);
~CModel3(void);
/*
* Private Property.
* Tresspassers will be shot! ;)
*/
private:
// Private member functions
UINT8 ReadInputs(unsigned reg);
void WriteInputs(unsigned reg, UINT8 data);
UINT32 ReadSecurity(unsigned reg);
void WriteSecurity(unsigned reg, UINT32 data);
void SetCROMBank(unsigned idx);
UINT8 ReadSystemRegister(unsigned reg);
void WriteSystemRegister(unsigned reg, UINT8 data);
void Patch(void);
// Game and hardware information
const struct GameInfo *Game;
// Game inputs
CInputs *Inputs;
// Input registers (game controls)
UINT8 inputBank;
UINT8 serialFIFO1, serialFIFO2;
UINT8 gunReg;
int adcChannel;
// Emulated core Model 3 memory regions
UINT8 *memoryPool; // single allocated region for all ROM and system RAM
UINT8 *ram; // 8 MB PowerPC RAM
UINT8 *crom; // 8+128 MB CROM (fixed CROM first, then 64MB of banked CROMs -- Daytona2 might need extra?)
UINT8 *vrom; // 64 MB VROM (video ROM, visible only to Real3D)
UINT8 *soundROM; // 512 KB sound ROM (68K program)
UINT8 *sampleROM; // 8 MB samples (68K)
UINT8 *backupRAM; // 128 KB Backup RAM (battery backed)
UINT8 *securityRAM; // 128 KB Security Board RAM
// Banked CROM
UINT8 *cromBank; // currently mapped in CROM bank
unsigned cromBankReg; // the CROM bank register
// Security device
unsigned securityPtr; // pointer to current offset in security data
// PowerPC
PPC_FETCH_REGION PPCFetchRegions[3];
unsigned ppcFrequency; // clock frequency (Hz)
// Other devices
CIRQ IRQ; // Model 3 IRQ controller
CMPC10x PCIBridge; // MPC10x PCI/bridge/memory controller
CPCIBus PCIBus; // Model 3's PCI bus
C53C810 SCSI; // NCR 53C810 SCSI controller
CRTC72421 RTC; // Epson RTC-72421 real-time clock
C93C46 EEPROM; // 93C46 EEPROM
CTileGen TileGen; // Sega 2D tile generator
CReal3D GPU; // Real3D graphics hardware
CSoundBoard SoundBoard; // sound board
};
#endif // INCLUDED_MODEL3_H

154
Model3/PCI.cpp Normal file
View file

@ -0,0 +1,154 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* PCI.cpp
*
* PCI bus and device emulation. Implementation of the CPCIBus class.
* CPCIDevice is just a base class from which others are derived. Only the
* transfer of PCI register data is emulated. Device functionality is
* handled by device objects (derived from CPCIDevice).
*
* TO-DO List:
* -----------
* - Exception handling for out-of-memory errors in STL? Right now, we assume
* the vectors do not run out of memory because so few PCI devices exist.
*/
#include "Supermodel.h"
/******************************************************************************
Emulation Functions
******************************************************************************/
/*
* CPCIBus::ReadConfigSpace(device, reg, bits offset):
*
* Reads a PCI register belonging to a particular device.
*/
UINT32 CPCIBus::ReadConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
{
unsigned i;
// Alignment check
#ifdef DEBUG
if (((bits==16)&&(offset&1)) || ((bits==32)&&(offset&3)))
ErrorLog("Misaligned PCI read request (device=%d,reg=%X,offset=%d)\n", device, reg, offset);
#endif
// Search device vector for a matching device
for (i = 0; i < DeviceVector.size(); i++)
{
if (DeviceVector[i].device == device)
return DeviceVector[i].DeviceObject->ReadPCIConfigSpace(device, reg, bits, offset);
}
DebugLog("PCI read request for unknown device (device=%d,reg=%X)\n", device, reg);
return 0;
}
/*
* CPCIBus::WriteConfigSpace(device, reg, bits, offset, data):
*
* Writes to the PCI configuration space register belonging to a particular
* device.
*/
void CPCIBus::WriteConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
{
unsigned i;
// Search device vector for a matching device
for (i = 0; i < DeviceVector.size(); i++)
{
if (DeviceVector[i].device == device)
{
DeviceVector[i].DeviceObject->WritePCIConfigSpace(device, reg, bits, offset, data);
return;
}
}
// printf("PCI write request for unknown device (device=%d, reg=%X, data=%X)\n", device, reg, data);
DebugLog("PCI write request for unknown device (device=%d, reg=%X, data=%X)\n", device, reg, data);
}
/*
* CPCIBus::Reset():
*
* Resets the PCI bus emulation only. The devices attached to the bus are NOT
* reset or accessed.
*/
void CPCIBus::Reset(void)
{
// this function really only exists for consistency with other device classes
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
/*
* CPCIBus::AttachDevice(device, DeviceObjectPtr):
*
* Attaches a device to the PCI bus. This means it is added to a list of
* devices to scan when handling PCI configuration space requests.
*/
void CPCIBus::AttachDevice(unsigned device, CPCIDevice *DeviceObjectPtr)
{
struct DeviceObjectLink D;
D.device = device;
D.DeviceObject = DeviceObjectPtr;
DeviceVector.push_back(D);
DebugLog("Attached device %d to PCI bus\n", device);
}
/*
* CPCIBus::Init():
*
* This must be called first and only once during the lifetime of the class.
*/
void CPCIBus::Init(void)
{
DeviceVector.clear();
}
/*
* CPCIBus::CPCIBus(void):
*
* Constructor.
*/
CPCIBus::CPCIBus(void)
{
DebugLog("Built PCI bus\n");
}
/*
* CPCIBus::~CPCIBus(void):
*
* Destructor.
*/
CPCIBus::~CPCIBus(void)
{
DebugLog("Destroyed PCI bus\n");
}

190
Model3/PCI.h Normal file
View file

@ -0,0 +1,190 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* PCI.h
*
* Header file for PCI bus and device emulation. Defines the CPCIDevice and
* CPCIBus classes.
*
* References
* ----------
* 1. "MPC106 PCI/Bridge/Memory Controller User's Manual" (MPC106UM/D Rev.1)
* Sec.7.4.5 describes the PCI configuration space header, which CPCIDevice-
* derived classes are supposed to implement.
*/
#ifndef INCLUDED_PCI_H
#define INCLUDED_PCI_H
#include <vector>
using namespace std;
/*
* CPCIDevice:
*
* An abstract base class for PCI devices. Devices can inherit this class to
* provide PCI configuration space access handlers.
*/
class CPCIDevice
{
public:
/*
* ReadPCIConfigSpace(reg, bits, offset):
*
* Reads a PCI configuration space register. Returns data in big endian
* form (little endian devices must byte reverse it).
*
* Parameters:
* device The device number. This is system-specific and ought to be
* ignored in most cases (devices ought to know what they
* are) but is provided in case multiple devices are handled
* by the same object.
* reg Register number (32-bit offset).
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to bit width of
* access. For bytes, can be 0, 1, 2, or 3, but for 16-bit
* words can only be 0 or 2. Offsets are relative to the base
* of the 32-bit aligned register address, so register must be
* ANDed with ~3 first by caller.
*
* Returns:
* Register data. Bit width will be the same as specified by 'bits'.
*/
virtual UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset) = 0;
/*
* WritePCIConfigSpace(reg, bits, offset, data):
*
* Writes to a PCI configuration space register.
*
* Parameters:
* device Device number.
* reg Register number (32-bit offset).
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to bit width of
* access.
* data Data. Interpreted according to specified bit width.
*/
virtual void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data) = 0;
};
/*
* CPCIBus:
*
* A PCI bus. Dispatches commands to various attached devices. Because PCI
* configuration space access is not a critical or frequently-used function,
* dedicated 8-, 16-, and 32-bit handlers are not provided. Rather, the access
* size is passed to the handler to respond accordingly.
*/
class CPCIBus
{
public:
/*
* ReadConfigSpace(device, reg, bits, offset):
*
* Reads a PCI configuration space register belonging to a particular
* device.
*
* Parameters:
* device PCI device ID.
* reg Register number.
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to bit width of
* access. Offsets are relative to the base of the 32-bit
* aligned register address, so register must be ANDed with ~3
* first by caller.
*
* Returns:
* Register data. Bit width will be the same as specified by 'bits'.
*/
UINT32 ReadConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset);
/*
* WriteConfigSpace(device, reg, bits, offset, data):
*
* Writes a PCI configuration space register.
*
* Parameters:
* device PCI device ID.
* reg Register number.
* bits Width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to bit width of
* access.
* data Data being written. Interpreted according to bit width.
*/
void WriteConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data);
/*
* Reset(void):
*
* Resets the PCI bus emulation. Does not reset or access any devices
* attached to the PCI bus. These must be reset manually.
*/
void Reset(void);
/*
* AttachDevice(device, DeviceObjectPtr):
*
* Attaches a device to the PCI bus. This means it is added to a list of
* devices to scan when handling PCI configuration space requests.
*
* Parameters:
* device PCI ID of the device being attached.
* DeviceObjectPtr Pointer to the device object.
*/
void AttachDevice(unsigned device, CPCIDevice *DeviceObjectPtr);
/*
* Init(void):
*
* One-time initialization of the context. Must be called prior to all
* other members.
*/
void Init(void);
/*
* CPCIBus(void):
* ~CPCIBus(void):
*
* Constructor and destructor.
*/
CPCIBus(void);
~CPCIBus(void);
private:
// Map device IDs to objects
struct DeviceObjectLink
{
unsigned device;
CPCIDevice *DeviceObject;
};
// An array of device objects
vector<struct DeviceObjectLink> DeviceVector;
};
#endif // INCLUDED_PCI_H

117
Model3/RTC72421.cpp Normal file
View file

@ -0,0 +1,117 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* RTC72421.cpp
*
* Epson RTC-72421 implementation.
*
* TO-DO List:
* -----------
* - Rather than returning the host system time, should compute the "virtual"
* time inside the Model 3, which could differ.
* - Writes do nothing yet.
*/
#include <time.h>
#include "Supermodel.h"
/******************************************************************************
Emulation Functions
******************************************************************************/
UINT8 CRTC72421::ReadRegister(unsigned reg)
{
time_t currentTime;
struct tm *Time;
time(&currentTime);
Time = localtime(&currentTime);
switch (reg&0xF)
{
case 0: // 1-second digit
return (Time->tm_sec % 10) & 0xF;
case 1: // 10-seconds digit
return (Time->tm_sec / 10) & 0x7;
case 2: // 1-minute digit
return (Time->tm_min % 10) & 0xF;
case 3: // 10-minute digit
return (Time->tm_min / 10) & 0x7;
case 4: // 1-hour digit
return (Time->tm_hour % 10) & 0xF;
case 5: // 10-hours digit
return (Time->tm_hour / 10) & 0x7;
case 6: // 1-day digit (days in month)
return (Time->tm_mday % 10) & 0xF;
case 7: // 10-days digit
return (Time->tm_mday / 10) & 0x3;
case 8: // 1-month digit
return ((Time->tm_mon + 1) % 10) & 0xF;
case 9: // 10-months digit
return ((Time->tm_mon + 1) / 10) & 0x1;
case 10: // 1-year digit
return (Time->tm_year % 10) & 0xF;
case 11: // 10-years digit
return ((Time->tm_year % 100) / 10) & 0xF;
case 12: // day of the week
return Time->tm_wday & 0x7;
case 13:
break;
case 14:
break;
case 15:
break;
}
return 0;
}
void CRTC72421::WriteRegister(unsigned reg, UINT8 data)
{
// TO-DO: emulate me!
}
void CRTC72421::Reset(void)
{
// nothing to do
}
/******************************************************************************
Initialization and Shutdown
******************************************************************************/
void CRTC72421::Init(void)
{
// this function really only exists for consistency with other device classes
}
CRTC72421::CRTC72421(void)
{
DebugLog("Built RTC-72421\n");
}
CRTC72421::~CRTC72421(void)
{
DebugLog("Destroyed RTC-72421\n");
}

91
Model3/RTC72421.h Normal file
View file

@ -0,0 +1,91 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* RTC72421.h
*
* Header file defining the CRTC72421 class: RTC-72421 real-time clock.
*/
#ifndef INCLUDED_RTC72421_H
#define INCLUDED_RTC72421_H
/*
* CRTC72421:
*
* Epson RTC-72421 real-time clock.
*/
class CRTC72421
{
public:
/*
* ReadRegister(unsigned reg):
*
* Reads data from one of the RTC registers.
*
* Parameters:
* reg Register number (0-15).
*
* Returns:
* Data in the lower nibble.
*/
UINT8 ReadRegister(unsigned reg);
/*
* WriteRegister(unsigned reg, UINT8 data):
*
* Writes data to one of the RTC registers.
*
* Parameters:
* reg Register number (0-15).
* data Data to write.
*/
void WriteRegister(unsigned reg, UINT8 data);
/*
* Reset(void):
*
* Does nothing. Provided for compability with other device objects. The
* RTC is battery-backed and always available.
*/
void Reset(void);
/*
* Init(void):
*
* One-time initialization of the context. Must be called prior to all
* other members.
*/
void Init(void);
/*
* CRTC72421(void):
* ~CRTC72421(void):
*
* Constructor and destructor.
*/
CRTC72421(void);
~CRTC72421(void);
};
#endif // INCLUDED_RTC72421_H

945
Model3/Real3D.cpp Normal file
View file

@ -0,0 +1,945 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Real3D.cpp
*
* The Model 3's Real3D-based graphics hardware. Based on the Real3D Pro-1000
* family of image generators.
*
* PCI IDs
* -------
* It appears that Step 2.0 returns a different PCI ID depending on whether
* the PCI configuration space or DMA register are accessed. For example,
* Virtual On 2 expects 0x178611DB from the PCI configuration header but
* 0x16C311DB from the DMA device.
*
* To-Do List
* ----------
* - For consistency, the status registers should probably be byte reversed (this is a
* little endian device), forcing the Model3 Read32/Write32 handlers to
* manually reverse the data. This keeps with the convention for VRAM.
* - Keep an eye out for games writing non-mipmap textures to the mipmap area.
* The render currently cannot cope with this.
*/
#include <cstring>
#include "Supermodel.h"
// Offsets of memory regions within Real3D memory pool
#define OFFSET_8C 0 // 4 MB, culling RAM low (at 0x8C000000)
#define OFFSET_8E 0x400000 // 1 MB, culling RAM high (at 0x8E000000)
#define OFFSET_98 0x500000 // 4 MB, polygon RAM (at 0x98000000)
#define OFFSET_TEXRAM 0x900000 // 8 MB, texture RAM
#define OFFSET_TEXFIFO 0x1100000 // 1 MB, texture FIFO
#define MEMORY_POOL_SIZE (0x400000+0x100000+0x400000+0x800000+0x100000)
/******************************************************************************
Save States
******************************************************************************/
void CReal3D::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("Real3D", __FILE__);
SaveState->Write(memoryPool, MEMORY_POOL_SIZE);
SaveState->Write(&fifoIdx, sizeof(fifoIdx));
SaveState->Write(&vromTextureAddr, sizeof(vromTextureAddr));
SaveState->Write(&vromTextureHeader, sizeof(vromTextureHeader));
SaveState->Write(&dmaSrc, sizeof(dmaSrc));
SaveState->Write(&dmaDest, sizeof(dmaDest));
SaveState->Write(&dmaLength, sizeof(dmaLength));
SaveState->Write(&dmaData, sizeof(dmaData));
SaveState->Write(&dmaUnknownReg, sizeof(dmaUnknownReg));
SaveState->Write(&dmaStatus, sizeof(dmaStatus));
SaveState->Write(&dmaConfig, sizeof(dmaConfig));
SaveState->Write(&tapCurrentInstruction, sizeof(tapCurrentInstruction));
SaveState->Write(&tapIR, sizeof(tapIR));
SaveState->Write(tapID, sizeof(tapID));
SaveState->Write(&tapIDSize, sizeof(tapIDSize));
SaveState->Write(&tapTDO, sizeof(tapTDO));
SaveState->Write(&tapState, sizeof(tapState));
}
void CReal3D::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("Real3D"))
{
ErrorLog("Unable to load Real3D GPU state. Save state file is corrupted.");
return;
}
SaveState->Read(memoryPool, MEMORY_POOL_SIZE);
Render3D->UploadTextures(0,0,2048,2048);
SaveState->Read(&fifoIdx, sizeof(fifoIdx));
SaveState->Read(&vromTextureAddr, sizeof(vromTextureAddr));
SaveState->Read(&vromTextureHeader, sizeof(vromTextureHeader));
SaveState->Read(&dmaSrc, sizeof(dmaSrc));
SaveState->Read(&dmaDest, sizeof(dmaDest));
SaveState->Read(&dmaLength, sizeof(dmaLength));
SaveState->Read(&dmaData, sizeof(dmaData));
SaveState->Read(&dmaUnknownReg, sizeof(dmaUnknownReg));
SaveState->Read(&dmaStatus, sizeof(dmaStatus));
SaveState->Read(&dmaConfig, sizeof(dmaConfig));
SaveState->Read(&tapCurrentInstruction, sizeof(tapCurrentInstruction));
SaveState->Read(&tapIR, sizeof(tapIR));
SaveState->Read(tapID, sizeof(tapID));
SaveState->Read(&tapIDSize, sizeof(tapIDSize));
SaveState->Read(&tapTDO, sizeof(tapTDO));
SaveState->Read(&tapState, sizeof(tapState));
}
/******************************************************************************
Rendering
******************************************************************************/
void CReal3D::RenderFrame(void)
{
//if (commandPortWritten)
Render3D->RenderFrame();
commandPortWritten = FALSE;
}
void CReal3D::BeginFrame(void)
{
status |= 2; // VBlank bit
Render3D->BeginFrame();
}
void CReal3D::EndFrame(void)
{
status &= ~2;
Render3D->EndFrame();
}
/******************************************************************************
DMA Device
Register 0xC:
-------------
+---+---+---+---+---+---+---+---+
|BUS|???|???|???|???|???|???|IRQ|
+---+---+---+---+---+---+---+---+
BUS: Busy (see von2 0x18A104) if 1.
IRQ: IRQ pending.
******************************************************************************/
void CReal3D::DMACopy(void)
{
DebugLog("Real3D DMA copy (PC=%08X, LR=%08X): %08X -> %08X, %X %s\n", ppc_get_pc(), ppc_get_lr(), dmaSrc, dmaDest, dmaLength*4, (dmaConfig&0x80)?"(byte reversed)":"");
//printf("Real3D DMA copy (PC=%08X, LR=%08X): %08X -> %08X, %X %s\n", ppc_get_pc(), ppc_get_lr(), dmaSrc, dmaDest, dmaLength*4, (dmaConfig&0x80)?"(byte reversed)":"");
if ((dmaConfig&0x80)) // reverse bytes
{
while (dmaLength != 0)
{
UINT32 data = Bus->Read32(dmaSrc);
Bus->Write32(dmaDest, FLIPENDIAN32(data));
dmaSrc += 4;
dmaDest += 4;
--dmaLength;
}
}
else
{
while (dmaLength != 0)
{
Bus->Write32(dmaDest, Bus->Read32(dmaSrc));
dmaSrc += 4;
dmaDest += 4;
--dmaLength;
}
}
}
UINT8 CReal3D::ReadDMARegister8(unsigned reg)
{
switch (reg)
{
case 0xC: // status
return dmaStatus;
case 0xE: // configuration
return dmaConfig;
default:
break;
}
DebugLog("Real3D: ReadDMARegister8: reg=%X\n", reg);
return 0;
}
void CReal3D::WriteDMARegister8(unsigned reg, UINT8 data)
{
switch (reg)
{
case 0xD: // IRQ acknowledge
if ((data&1))
{
dmaStatus &= ~1;
IRQ->Deassert(dmaIRQ);
}
break;
case 0xE: // configuration
dmaConfig = data;
break;
default:
DebugLog("Real3D: WriteDMARegister8: reg=%X, data=%02X\n", reg, data);
break;
}
//DebugLog("Real3D: WriteDMARegister8: reg=%X, data=%02X\n", reg, data);
}
UINT32 CReal3D::ReadDMARegister32(unsigned reg)
{
switch (reg)
{
case 0x14: // command result
return dmaData;
default:
break;
}
DebugLog("Real3D: ReadDMARegister32: reg=%X\n", reg);
return 0;
}
void CReal3D::WriteDMARegister32(unsigned reg, UINT32 data)
{
switch (reg)
{
case 0x00: // DMA source address
dmaSrc = data;
break;
case 0x04: // DMA destination address
dmaDest = data;
break;
case 0x08: // DMA length
dmaLength = data;
DMACopy();
dmaStatus |= 1;
IRQ->Assert(dmaIRQ);
break;
case 0x10: // command register
if ((data&0x20000000))
{
dmaData = 0x16C311DB; // Virtual On 2 expects this from DMA
DebugLog("Real3D: DMA ID command issued (ATTENTION: make sure we're returning the correct value), PC=%08X, LR=%08X\n", ppc_get_pc(), ppc_get_lr());
}
else if ((data&0x80000000))
{
dmaUnknownReg ^= 0xFFFFFFFF;
dmaData = dmaUnknownReg;
}
break;
case 0x14: // ?
dmaData = 0xFFFFFFFF;
break;
default:
DebugLog("Real3D: WriteDMARegister32: reg=%X, data=%08X\n", reg, data);
break;
}
//DebugLog("Real3D: WriteDMARegister32: reg=%X, data=%08X\n", reg, data);
}
/******************************************************************************
JTAG Test Access Port Simulation
What I term as "IDs" here are really boundary scan values.
******************************************************************************/
static const int tapFSM[][2] = // finite state machine, each state can lead to 2 next states
{
{ 1, 0 }, // 0 Test-Logic/Reset
{ 1, 2 }, // 1 Run-Test/Idle
{ 3, 9 }, // 2 Select-DR-Scan
{ 4, 5 }, // 3 Capture-DR
{ 4, 5 }, // 4 Shift-DR
{ 6, 8 }, // 5 Exit1-DR
{ 6, 7 }, // 6 Pause-DR
{ 4, 8 }, // 7 Exit2-DR
{ 1, 2 }, // 8 Update-DR
{ 10, 0 }, // 9 Select-IR-Scan
{ 11, 12 }, // 10 Capture-IR
{ 11, 12 }, // 11 Shift-IR
{ 13, 15 }, // 12 Exit1-IR
{ 13, 14 }, // 13 Pause-IR
{ 11, 15 }, // 14 Exit2-IR
{ 1, 2 } // 15 Update-IR
};
/*
* InsertBit():
*
* Inserts a bit into an arbitrarily long bit field. Bit 0 is assumed to be
* the MSB of the first byte in the buffer.
*/
void CReal3D::InsertBit(UINT8 *buf, unsigned bitNum, unsigned bit)
{
unsigned bitInByte;
bitInByte = 7 - (bitNum & 7);
buf[bitNum / 8] &= ~(1 << bitInByte);
buf[bitNum / 8] |= (bit << bitInByte);
}
/*
* InsertID():
*
* Inserts a 32-bit ID code into the ID bit field.
*/
void CReal3D::InsertID(UINT32 id, unsigned startBit)
{
int i;
for (i = 31; i >= 0; i--)
InsertBit(tapID, startBit++, (id >> i) & 1);
}
/*
* Shift():
*
* Shifts the data buffer right (towards LSB at byte 0) by 1 bit. The size of
* the number of bits must be specified. The bit shifted out of the LSB is
* returned.
*/
unsigned CReal3D::Shift(UINT8 *data, unsigned numBits)
{
unsigned i;
unsigned shiftOut, shiftIn;
// This loop takes care of all the fully-filled bytes
shiftIn = 0;
shiftOut = 0;
for (i = 0; i < numBits / 8; i++)
{
shiftOut = data[i] & 1;
data[i] >>= 1;
data[i] |= (shiftIn << 7);
shiftIn = shiftOut; // carry over to next element's MSB
}
// Take care of the last partial byte (if there is one)
if ((numBits & 7) != 0)
{
shiftOut = (data[i] >> (8 - (numBits & 7))) & 1;
data[i] >>= 1;
data[i] |= (shiftIn << 7);
}
return shiftOut;
}
unsigned CReal3D::ReadTAP(void)
{
return tapTDO;
}
void CReal3D::WriteTAP(unsigned tck, unsigned tms, unsigned tdi, unsigned trst)
{
if (!tck)
return;
// Go to next state
tapState = tapFSM[tapState][tms];
switch (tapState)
{
case 3: // Capture-DR
/*
* Read ASIC IDs.
*
* The ID Sequence is:
* - Jupiter
* - Mercury
* - Venus
* - Earth
* - Mars
* - Mars (again)
*
* Note that different Model 3 steps have different chip
* revisions, hence the different IDs returned below.
*
* On Step 1.5 and 1.0, instruction 0x0C631F8C7FFE is used to retrieve
* the ID codes but Step 2.0 is a little weirder. It seems to use this
* and either the state of the TAP after reset or other instructions
* to read the IDs as well. This can be emulated in one of 2 ways:
* Ignore the instruction and always load up the data or load the
* data on TAP reset and when the instruction is issued.
*/
if (step == 0x10)
{
InsertID(0x116C7057, 1 + 0 * 32);
InsertID(0x216C3057, 1 + 1 * 32);
InsertID(0x116C4057, 1 + 2 * 32);
InsertID(0x216C5057, 1 + 3 * 32);
InsertID(0x116C6057, 1 + 4 * 32 + 1);
InsertID(0x116C6057, 1 + 5 * 32 + 1);
}
else if (step == 0x15)
{
InsertID(0x316C7057, 1 + 0 * 32);
InsertID(0x316C3057, 1 + 1 * 32);
InsertID(0x216C4057, 1 + 2 * 32); // Lost World may to use 0x016C4057
InsertID(0x316C5057, 1 + 3 * 32);
InsertID(0x216C6057, 1 + 4 * 32 + 1);
InsertID(0x216C6057, 1 + 5 * 32 + 1);
}
else if (step >= 0x20)
{
InsertID(0x416C7057, 1 + 0 * 32);
InsertID(0x416C3057, 1 + 1 * 32);
InsertID(0x316C4057, 1 + 2 * 32);
InsertID(0x416C5057, 1 + 3 * 32);
InsertID(0x316C6057, 1 + 4 * 32 + 1);
InsertID(0x316C6057, 1 + 5 * 32 + 1);
}
break;
case 4: // Shift-DR
tapTDO = Shift(tapID, tapIDSize);
break;
case 10: // Capture-IR
// Load lower 2 bits with 01 as per IEEE 1149.1-1990
tapIR = 1;
break;
case 11: // Shift-IR
// Shift IR towards output and load in new data from TDI
tapTDO = tapIR & 1; // shift LSB to output
tapIR >>= 1;
tapIR |= ((UINT64) tdi << 45);
break;
case 15: // Update-IR
/*
* Latch IR (technically, this should occur on the falling edge of
* TCK)
*/
tapIR &= 0x3FFFFFFFFFFFULL;
tapCurrentInstruction = tapIR;
break;
default:
break;
}
}
/******************************************************************************
Texture Uploading and Decoding
******************************************************************************/
// Mipmap coordinates for each reduction level (within a single 2048x1024 page)
static const int mipXBase[11] =
{
1024, // 1024/2
1536, // 512/2
1792, // 256/2
1920, // ...
1984,
2016,
2032,
2040,
2044,
2046,
2047
};
static const int mipYBase[11] =
{
512,
768,
896,
960,
992,
1008,
1016,
1020,
1022,
1023,
0
};
// Mipmap reduction factors
static const int mipDivisor[9] = { 2, 4, 8, 16, 32, 64, 128, 256, 512 };
// Table of texel offsets corresponding to an 8x8 texel texture tile
static const unsigned decode[64] =
{
0, 1, 4, 5, 8, 9,12,13,
2, 3, 6, 7,10,11,14,15,
16,17,20,21,24,25,28,29,
18,19,22,23,26,27,30,31,
32,33,36,37,40,41,44,45,
34,35,38,39,42,43,46,47,
48,49,52,53,56,57,60,61,
50,51,54,55,58,59,62,63
};
void CReal3D::StoreTexture(unsigned xPos, unsigned yPos, unsigned width, unsigned height, UINT16 *texData, unsigned bytesPerTexel)
{
unsigned x, y, xx, yy, destOffset;
if (bytesPerTexel == 2) // 16-bit textures
{
// Outer 2 loops: 8x8 tiles
for (y = yPos; y < (yPos+height); y += 8)
{
for (x = xPos; x < (xPos+width); x += 8)
{
// Inner 2 loops: 8x8 texels for the current tile
destOffset = y*2048+x;
for (yy = 0; yy < 8; yy++)
{
for (xx = 0; xx < 8; xx++)
textureRAM[destOffset++] = texData[decode[(yy*8+xx)^1]];
destOffset += 2048-8; // next line
}
texData += 8*8; // next tile
}
}
}
else // 8-bit textures
{
/*
* 8-bit textures appear to be unpacked into 16-bit words in the
* texture RAM. Oddly, the rows of the decoding table seem to be
* swapped.
*/
// Outer 2 loops: 8x8 tiles
for (y = yPos; y < (yPos+height); y += 8)
{
for (x = xPos; x < (xPos+width); x += 8)
{
// Inner 2 loops: 8x8 texels for the current tile
destOffset = y*2048+x;
for (yy = 0; yy < 8; yy++)
{
for (xx = 0; xx < 8; xx += 2)
{
textureRAM[destOffset++] = texData[decode[(yy^1)*8+((xx+0)^1)]/2]>>8;
textureRAM[destOffset++] = texData[decode[(yy^1)*8+((xx+1)^1)]/2]&0xFF;
}
destOffset += 2048-8;
}
texData += 8*8/2; // next tile
}
}
}
}
// Texture data will be in little endian format
void CReal3D::UploadTexture(UINT32 header, UINT16 *texData)
{
unsigned x, y, page, width, height, bytesPerTexel,
mipYPos, mipWidth, mipHeight, mipNum, mipX, mipY;
// Position: texture RAM is arranged as 2 2048x1024 texel sheets
x = 32*(header&0x3F);
y = 32*((header>>7)&0x1F);
page = (header>>20)&1;
y += page*1024; // treat page as additional Y bit (one 2048x2048 sheet)
// Texture size and bit depth
width = 32<<((header>>14)&7);
height = 32<<((header>>17)&7);
if ((header&0x00800000)) // 16 bits per texel
bytesPerTexel = 2;
else // 8 bits
{
bytesPerTexel = 1;
//printf("8-bit textures!\n");
}
// Mipmaps
mipYPos = 32*((header>>7)&0x1F);
// Process texture data
DebugLog("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
//printf("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
switch ((header>>24)&0x0F)
{
case 0x00: // texture w/ mipmaps
StoreTexture(x, y, width, height, texData, bytesPerTexel);
mipWidth = width;
mipHeight = height;
mipNum = 0;
while((mipHeight>8) && (mipWidth>8))
{
if (bytesPerTexel == 1)
texData += (mipWidth*mipHeight)/2;
else
texData += (mipWidth*mipHeight);
mipWidth /= 2;
mipHeight /= 2;
mipX = mipXBase[mipNum] + (x / mipDivisor[mipNum]);
mipY = mipYBase[mipNum] + (mipYPos / mipDivisor[mipNum]);
if(page)
mipY += 1024;
mipNum++;
StoreTexture(mipX, mipY, mipWidth, mipHeight, (UINT16 *) texData, bytesPerTexel);
}
break;
case 0x01: // texture w/out mipmaps
StoreTexture(x, y, width, height, texData, bytesPerTexel);
break;
case 0x02: // mipmaps only
mipWidth = width;
mipHeight = height;
mipNum = 0;
while((mipHeight>8) && (mipWidth>8))
{
mipWidth /= 2;
mipHeight /= 2;
mipX = mipXBase[mipNum] + (x / mipDivisor[mipNum]);
mipY = mipYBase[mipNum] + (mipYPos / mipDivisor[mipNum]);
if(page)
mipY += 1024;
mipNum++;
StoreTexture(mipX, mipY, mipWidth, mipHeight, texData, bytesPerTexel);
if (bytesPerTexel == 1)
texData += (mipWidth*mipHeight)/2;
else
texData += (mipWidth*mipHeight);
}
break;
case 0x80: // MAME thinks these might be a gamma table
//break;
default: // unknown
printf("unknown texture format %02X\n", header>>24);
break;
}
// Signal to renderer that textures have changed
// TO-DO: mipmaps? What if a game writes non-mipmap textures to mipmap area?
//Render3D->UploadTextures(x,y,width,height);
Render3D->UploadTextures(0,0,2048,2048); // TO-DO: should not have to upload all 2048x2048 texels
}
/******************************************************************************
Basic Emulation Functions, Registers, Memory, and Texture FIFO
******************************************************************************/
void CReal3D::Flush(void)
{
unsigned i, size;
UINT32 header;
commandPortWritten = TRUE;
DebugLog("Real3D 88000000 written @ PC=%08X\n", ppc_get_pc());
// Upload textures (if any)
if (fifoIdx > 0)
{
for (i = 0; i < fifoIdx; )
{
size = 2+textureFIFO[i+0]/2;
size /= 4;
header = textureFIFO[i+1]; // texture information header
UploadTexture(header,(UINT16 *)&textureFIFO[i+2]);
DebugLog("Real3D: Texture upload completed: %X bytes (%X)\n", size*4, textureFIFO[i+0]);
i += size;
}
}
// Reset texture FIFO
fifoIdx = 0;
}
void CReal3D::WriteTextureFIFO(UINT32 data)
{
textureFIFO[fifoIdx++] = data;
if (fifoIdx >= (0x100000/4))
ErrorLog("Real3D texture FIFO maxed out!");
}
void CReal3D::WriteTexturePort(unsigned reg, UINT32 data)
{
//printf("Texture Port: %X=%08X\n", reg, data);
switch (reg)
{
case 0x0: // VROM texture address
case 0xC:
vromTextureAddr = data;
break;
case 0x4: // VROM texture header
case 0x10:
vromTextureHeader = data;
break;
case 0x8: // VROM texture length (also used to trigger uploads)
case 0x14:
UploadTexture(vromTextureHeader,(UINT16 *)&vrom[vromTextureAddr&0xFFFFFF]);
//printf("texture upload: addr=%08X\n", vromTextureAddr);
break;
default:
DebugLog("Real3D texture port write: %X=%08X\n", reg, data);
break;
}
}
void CReal3D::WriteLowCullingRAM(UINT32 addr, UINT32 data)
{
cullingRAMLo[addr/4] = data;
}
void CReal3D::WriteHighCullingRAM(UINT32 addr, UINT32 data)
{
cullingRAMHi[addr/4] = data;
}
void CReal3D::WritePolygonRAM(UINT32 addr, UINT32 data)
{
polyRAM[addr/4] = data;
}
// Registers seem to range from 0x00 to around 0x3C but they are not understood
UINT32 CReal3D::ReadRegister(unsigned reg)
{
DebugLog("Real3D: Read reg %X\n", reg);
if (reg == 0)
return 0xFFFFFFFD|status;
else
return 0xFFFFFFFF;
}
UINT32 CReal3D::ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
{
UINT32 d;
if ((bits==8))
{
DebugLog("Real3D: %d-bit PCI read request for reg=%02X\n", bits, reg);
return 0;
}
// This is a little endian device, must return little endian words
switch (reg)
{
case 0x00: // Device ID and Vendor ID
d = FLIPENDIAN32(pciID);
switch (bits)
{
case 8:
d >>= (3-offset)*8; // offset will be 0-3; select appropriate byte
d &= 0xFF;
break;
case 16:
d >>= (2-offset)*8; // offset will be 0 or 2 only; select either high or low word
d &= 0xFFFF;
break;
default:
break;
}
DebugLog("Real3D: PCI ID read. Returning %X (%d-bits). PC=%08X, LR=%08X\n", d, bits, ppc_get_pc(), ppc_get_lr());
return d;
default:
DebugLog("Real3D: PCI read request for reg=%02X (%d-bit)\n", reg, bits);
break;
}
return 0;
}
void CReal3D::WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
{
DebugLog("Real3D: PCI %d-bit write request for reg=%02X, data=%08X\n", bits, reg, data);
}
void CReal3D::Reset(void)
{
commandPortWritten = FALSE;
fifoIdx = 0;
status = 0;
vromTextureAddr = 0;
vromTextureHeader = 0;
tapState = 0;
tapIDSize = 197;
dmaStatus = 0;
dmaUnknownReg = 0;
memset(memoryPool, 0, MEMORY_POOL_SIZE);
DebugLog("Real3D reset\n");
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void CReal3D::AttachRenderer(CRender3D *Render3DPtr)
{
Render3D = Render3DPtr;
Render3D->AttachMemory(cullingRAMLo,cullingRAMHi,polyRAM,vrom,textureRAM);
Render3D->SetStep(step);
DebugLog("Real3D attached a Render3D object\n");
}
void CReal3D::SetStep(int stepID)
{
step = stepID;
if ((step!=0x10) && (step!=0x15) && (step!=0x20) && (step!=0x21))
{
DebugLog("Real3D: Unrecognized stepping: %d.%d\n", (step>>4)&0xF, step&0xF);
step = 0x10;
}
// Set PCI ID
if (step < 0x20)
pciID = 0x16C311DB; // vendor 0x11DB = Sega
else
pciID = 0x178611DB;
// Pass to renderer
if (Render3D != NULL)
Render3D->SetStep(step);
DebugLog("Real3D set to Step %d.%d\n", (step>>4)&0xF, step&0xF);
}
BOOL CReal3D::Init(const UINT8 *vromPtr, CBus *BusObjectPtr, CIRQ *IRQObjectPtr, unsigned dmaIRQBit)
{
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
// IRQ and bus objects
Bus = BusObjectPtr;
IRQ = IRQObjectPtr;
dmaIRQ = dmaIRQBit;
// Allocate all Real3D RAM regions
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
if (NULL == memoryPool)
return ErrorLog("Insufficient memory for Real3D object (needs %1.1f MB).", memSizeMB);
// Set up pointers
cullingRAMLo = (UINT32 *) &memoryPool[OFFSET_8C];
cullingRAMHi = (UINT32 *) &memoryPool[OFFSET_8E];
polyRAM = (UINT32 *) &memoryPool[OFFSET_98];
textureRAM = (UINT16 *) &memoryPool[OFFSET_TEXRAM];
textureFIFO = (UINT32 *) &memoryPool[OFFSET_TEXFIFO];
// VROM pointer passed to us
vrom = (UINT32 *) vromPtr;
return OKAY;
}
CReal3D::CReal3D(void)
{
Render3D = NULL;
memoryPool = NULL;
cullingRAMLo = NULL;
cullingRAMHi = NULL;
polyRAM = NULL;
textureRAM = NULL;
textureFIFO = NULL;
vrom = NULL;
fifoIdx = 0;
vromTextureAddr = 0;
vromTextureHeader = 0;
tapState = 0;
tapIDSize = 197;
DebugLog("Built Real3D\n");
}
/*
* CReal3D::~CReal3D(void):
*
* Destructor.
*/
CReal3D::~CReal3D(void)
{
// Dump memory
#if 0
FILE *fp;
fp = fopen("8c000000", "wb");
if (NULL != fp)
{
fwrite(cullingRAMLo, sizeof(UINT8), 0x400000, fp);
fclose(fp);
printf("dumped %s\n", "8c000000");
}
else
printf("unable to dump %s\n", "8c000000");
fp = fopen("8e000000", "wb");
if (NULL != fp)
{
fwrite(cullingRAMHi, sizeof(UINT8), 0x100000, fp);
fclose(fp);
printf("dumped %s\n", "8e000000");
}
else
printf("unable to dump %s\n", "8e000000");
fp = fopen("98000000", "wb");
if (NULL != fp)
{
fwrite(polyRAM, sizeof(UINT8), 0x400000, fp);
fclose(fp);
printf("dumped %s\n", "98000000");
}
else
printf("unable to dump %s\n", "98000000");
fp = fopen("texram", "wb");
if (NULL != fp)
{
fwrite(textureRAM, sizeof(UINT8), 0x800000, fp);
fclose(fp);
printf("dumped %s\n", "texram");
}
else
printf("unable to dump %s\n", "texram");
#endif
Render3D = NULL;
if (memoryPool != NULL)
{
delete [] memoryPool;
memoryPool = NULL;
}
cullingRAMLo = NULL;
cullingRAMHi = NULL;
polyRAM = NULL;
textureRAM = NULL;
textureFIFO = NULL;
vrom = NULL;
DebugLog("Destroyed Real3D\n");
}

398
Model3/Real3D.h Normal file
View file

@ -0,0 +1,398 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Real3D.h
*
* Header file defining the CReal3D class: the Model 3's Real3D-based graphics
* hardware.
*/
#ifndef INCLUDED_REAL3D_H
#define INCLUDED_REAL3D_H
/*
* CReal3D:
*
* Model 3 Real3D-based graphics hardware. This class manages the hardware
* state and drives the rendering process (scene database traversal). Actual
* rasterization and matrix transformations are carried out by the graphics
* engine.
*/
class CReal3D: public CPCIDevice
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* RenderFrame(void):
*
* Traverses the scene database and renders a frame. Must be called after
* BeginFrame() but before EndFrame().
*/
void RenderFrame(void);
/*
* BeginFrame(void):
*
* Prepare to render a new frame. Must be called once per frame prior to
* drawing anything.
*/
void BeginFrame(void);
/*
* EndFrame(void):
*
* Signals the end of rendering for this frame. Must be called last during
* the frame.
*/
void EndFrame(void);
/*
* Flush(void):
*
* Triggers the beginning of a new frame. All textures in the texture FIFO
* are uploaded and the FIFO is reset. On the real device, this seems to
* cause a frame to be rendered as well but this is not performed here.
*
* This should be called when the command port is written.
*/
void Flush(void);
/*
* ReadDMARegister8(reg):
* ReadDMARegister32(reg):
*
* Reads from a DMA register. Multi-byte reads are returned as little
* endian and must be flipped if called by a big endian device.
*
* Parameters:
* reg Register number to read from (0-0xFF only).
*
* Returns:
* Data of the requested size, in little endian.
*/
UINT8 ReadDMARegister8(unsigned reg);
UINT32 ReadDMARegister32(unsigned reg);
/*
* WriteDMARegister8(reg, data):
* WriteDMARegister32(reg, data);
*
* Write to a DMA register. Multi-byte writes by big endian devices must be
* byte reversed (this is a little endian device).
*
* Parameters:
* reg Register number to read from (0-0xFF only).
* data Data to write.
*/
void WriteDMARegister8(unsigned reg, UINT8 data);
void WriteDMARegister32(unsigned reg, UINT32 data);
/*
* WriteLowCullingRAM(addr, data):
*
* Writes the low culling RAM region. Because this is a little endian
* device, big endian devices/buses have to take care to manually reverse
* the data before writing.
*
* Parameters:
* addr Word (32-bit) aligned address ranging from 0 to 0x3FFFFC.
* User must ensure address is properly clamped.
* data Data to write.
*/
void WriteLowCullingRAM(UINT32 addr, UINT32 data);
/*
* WriteHighCullingRAM(addr, data):
*
* Writes the high culling RAM region. Because this is a little endian
* device, big endian devices/buses have to take care to manually reverse
* the data before writing.
*
* Parameters:
* addr Word (32-bit) aligned address ranging from 0 to 0xFFFFC.
* User must ensure address is properly clamped.
* data Data to write.
*/
void WriteHighCullingRAM(UINT32 addr, UINT32 data);
/*
* WriteTextureFIFO(data):
*
* Writes to the 1MB texture FIFO. Because this is a little endian device,
* big endian devices/buses have to take care to manually reverse the data
* before writing.
*
* Parameters:
* data Data to write.
*/
void WriteTextureFIFO(UINT32 data);
/*
* WriteTexturePort(reg, data):
*
* Writes to the VROM texture ports. Register 0 is the word-granular VROM
* address of the texture, register 4 is the texture information header,
* and register 8 is the size of the texture in words.
*
* Parameters:
* reg Register number: must be 0, 4, 8, 0xC, 0x10, or 0x14 only.
* data The 32-bit word to write to the register. A write to
* register 8 triggers the upload.
*/
void WriteTexturePort(unsigned reg, UINT32 data);
/*
* WritePolygonRAM(addr, data):
*
* Writes the polygon RAM region. Because this is a little endian device,
* big endian devices/buses have to take care to manually reverse the data
* before writing.
*
* Parameters:
* addr Word (32-bit) aligned address ranging from 0 to 0x3FFFFC.
* User must ensure address is properly clamped.
* data Data to write.
*/
void WritePolygonRAM(UINT32 addr, UINT32 data);
/*
* ReadTAP(void):
*
* Reads the JTAG Test Access Port.
*
* Returns:
* The TDO bit (either 1 or 0).
*/
unsigned ReadTAP(void);
/*
* void WriteTAP(tck, tms, tdi, trst):
*
* Writes to the JTAG TAP. State changes only occur on the rising edge of
* the clock (tck = 1). Each of the inputs is a single bit only and must be
* either 0 or 1, or the code will fail.
*
* Parameters:
* tck Clock.
* tms Test mode select.
* tdi Serial data input. Must be 0 or 1 only!
* trst Reset.
*/
void WriteTAP(unsigned tck, unsigned tms, unsigned tdi, unsigned trst);
/*
* ReadRegister(reg):
*
* Reads one of the status registers.
*
* Parameters:
* reg Register offset (32-bit aligned). From 0x00 to 0x3C.
*
* Returns:
* The 32-bit status register.
*/
UINT32 ReadRegister(unsigned reg);
/*
* ReadPCIConfigSpace(device, reg, bits, offset):
*
* Reads a PCI configuration space register. See CPCIDevice definition for
* more details.
*
* Parameters:
* device Device number (ignored, not needed).
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).;
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
*
* Returns:
* Register data.
*/
UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width);
/*
* WritePCIConfigSpace(device, reg, bits, offset, data):
*
* Writes to a PCI configuration space register. See CPCIDevice definition
* for more details.
*
* Parameters:
* device Device number (ignored, not needed).
* reg Register number.
* bits Bit width of access (8, 16, or 32 only).
* offset Byte offset within register, aligned to the specified bit
* width, and offset from the 32-bit aligned base of the
* register number.
* data Data.
*/
void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, UINT32 data);
/*
* Reset(void):
*
* Resets the Real3D device. Must be called before reading/writing the
* device.
*/
void Reset(void);
/*
* AttachRenderer(render2DPtr):
*
* Attaches a 3D renderer for the Real3D to use. This function will
* immediately pass along the information that a CRender3D object needs to
* work with.
*
* Parameters:
* Render3DPtr Pointer to a 3D renderer object.
*/
void AttachRenderer(CRender3D *Render3DPtr);
/*
* SetStep(stepID):
*
* Sets the Model 3 hardware stepping, which also determines the Real3D
* functionality. The default is Step 1.0. This should be called prior to
* any other emulation functions and after Init().
*
* Parameters:
* stepID 0x10 for Step 1.0, 0x15 for Step 1.5, 0x20 for Step 2.0,
* or 0x21 for Step 2.1. Anything else defaults to 1.0.
*/
void SetStep(int stepID);
/*
* Init(vromPtr, BusObjectPtr, IRQObjectPtr, dmaIRQBit):
*
* One-time initialization of the context. Must be called prior to all
* other members. Connects the Real3D device to its video ROM and allocates
* memory for RAM regions.
*
* Parameters:
* vromPtr A pointer to video ROM (with each 32-bit word in
* its native little endian format).
* BusObjectPtr Pointer to the bus that the 53C810 has control
* over. Used to read/write memory.
* IRQObjectPtr Pointer to the IRQ controller. Used to trigger SCSI
* and DMA interrupts.
* dmaIRQBit IRQ identifier bit to pass along to IRQ controller
* when asserting interrupts.
*
* Returns:
* OKAY if successful otherwise FAIL (not enough memory). Prints own
* errors.
*/
BOOL Init(const UINT8 *vromPtr, CBus *BusObjectPtr, CIRQ *IRQObjectPtr, unsigned dmaIRQBit);
/*
* CReal3D(void):
* ~CReal3D(void):
*
* Constructor and destructor.
*/
CReal3D(void);
~CReal3D(void);
private:
// Private member functions
void DMACopy(void);
void InsertBit(UINT8 *buf, unsigned bitNum, unsigned bit);
void InsertID(UINT32 id, unsigned startBit);
unsigned Shift(UINT8 *data, unsigned numBits);
void StoreTexture(unsigned xPos, unsigned yPos, unsigned width, unsigned height, UINT16 *texData, unsigned bytesPerTexel);
void UploadTexture(UINT32 header, UINT16 *texData);
// Renderer attached to the Real3D
CRender3D *Render3D;
// Data passed from Model 3 object
const UINT32 *vrom; // Video ROM
int step; // hardware stepping (as in GameInfo structure)
UINT32 pciID; // PCI vendor and device ID
// Real3D memory
UINT8 *memoryPool; // all memory allocated here
UINT32 *cullingRAMLo; // 4MB of culling RAM at 8C000000
UINT32 *cullingRAMHi; // 1MB of culling RAM at 8E000000
UINT32 *polyRAM; // 4MB of polygon RAM at 98000000
UINT16 *textureRAM; // 8MB of internal texture RAM
UINT32 *textureFIFO; // 1MB texture FIFO at 0x94000000
unsigned fifoIdx; // index into texture FIFO
UINT32 vromTextureAddr; // VROM texture port address data
UINT32 vromTextureHeader; // VROM texture port header data
// Big endian bus object for DMA memory access
CBus *Bus;
// IRQ handling
CIRQ *IRQ; // IRQ controller
unsigned dmaIRQ; // IRQ bit to use when calling IRQ handler
// DMA device
UINT32 dmaSrc;
UINT32 dmaDest;
UINT32 dmaLength;
UINT32 dmaData;
UINT32 dmaUnknownReg;
UINT8 dmaStatus;
UINT8 dmaConfig;
// Command port
BOOL commandPortWritten;
// Status and command registers
UINT32 status;
// JTAG Test Access Port
UINT64 tapCurrentInstruction; // latched IR (not always equal to IR)
UINT64 tapIR; // instruction register (46 bits)
UINT8 tapID[32]; // ASIC ID code data buffer
unsigned tapIDSize; // size of ID data in bits
unsigned tapTDO; // bit shifted out to TDO
int tapState; // current state
};
#endif // INCLUDED_REAL3D_H

379
Model3/SoundBoard.cpp Normal file
View file

@ -0,0 +1,379 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* SoundBoard.cpp
*
* Model 3 sound board. Implementation of the CSoundBoard class. This class can
* only be instantiated once because it relies on global variables (the non-OOP
* 68K core).
*/
#include "Supermodel.h"
//TEMP: these need to be dynamically allocated in the memory pool
static INT16 leftBuffer[44100/60],rightBuffer[44100/60];
static FILE *soundFP;
/******************************************************************************
Global 68K Access Handlers
The 68K must interface with globally-accessible memory maps and access
handlers. Turbo68K uses the __cdecl calling convention. Note that this is not
explicitly defined for the Turbo68K functions themselves but in case Turbo68K
crashes, this may be the culprit.
******************************************************************************/
// Prototypes for access handlers
static UINT8 __cdecl UnknownRead8(UINT32);
static UINT16 __cdecl UnknownRead16(UINT32);
static UINT32 __cdecl UnknownRead32(UINT32);
static void __cdecl UnknownWrite8(UINT32, UINT8);
static void __cdecl UnknownWrite16(UINT32, UINT16);
static void __cdecl UnknownWrite32(UINT32, UINT32);
// Memory maps for 68K
#ifdef SUPERMODEL_SOUND
struct TURBO68K_FETCHREGION mapFetch[] =
{
{ 0x000000, 0x0FFFFF, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL }, // SCSP2 RAM
{ 0x600000, 0x67FFFF, NULL }, // program ROM
{ -1, -1, NULL }
};
struct TURBO68K_DATAREGION mapRead8[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x600000, 0x67FFFF, NULL, NULL }, // program ROM
{ 0x800000, 0x9FFFFF, NULL, NULL }, // sample ROM (low 2 MB)
{ 0xA00000, 0xDFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0xE00000, 0xFFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_r8 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_r8 },
{ 0x000000, 0xFFFFFF, NULL, UnknownRead8 },
{ -1, -1, NULL, NULL }
};
struct TURBO68K_DATAREGION mapRead16[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x600000, 0x67FFFF, NULL, NULL }, // program ROM
{ 0x800000, 0x9FFFFF, NULL, NULL }, // sample ROM (low 2 MB)
{ 0xA00000, 0xDFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0xE00000, 0xFFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_r16 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_r16 },
{ 0x000000, 0xFFFFFF, NULL, UnknownRead16 },
{ -1, -1, NULL, NULL }
};
struct TURBO68K_DATAREGION mapRead32[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x600000, 0x67FFFF, NULL, NULL }, // program ROM
{ 0x800000, 0x9FFFFF, NULL, NULL }, // sample ROM (low 2 MB)
{ 0xA00000, 0xDFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0xE00000, 0xFFFFFF, NULL, NULL }, // sample ROM (bank)
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_r32 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_r32 },
{ 0x000000, 0xFFFFFF, NULL, UnknownRead32 },
{ -1, -1, NULL, NULL }
};
struct TURBO68K_DATAREGION mapWrite8[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_w8 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_w8 },
{ 0x000000, 0xFFFFFF, NULL, UnknownWrite8 },
{ -1, -1, NULL, NULL }
};
struct TURBO68K_DATAREGION mapWrite16[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_w16 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_w16 },
{ 0x000000, 0xFFFFFF, NULL, UnknownWrite16 },
{ -1, -1, NULL, NULL }
};
struct TURBO68K_DATAREGION mapWrite32[] =
{
{ 0x000000, 0x0FFFFF, NULL, NULL }, // SCSP1 RAM
{ 0x200000, 0x2FFFFF, NULL, NULL }, // SCSP2 RAM
{ 0x100000, 0x10FFFF, NULL, SCSP_Master_w32 },
{ 0x300000, 0x30FFFF, NULL, SCSP_Slave_w32 },
{ 0x000000, 0xFFFFFF, NULL, UnknownWrite32 },
{ -1, -1, NULL, NULL }
};
#endif
static UINT8 __cdecl UnknownRead8(UINT32 addr)
{
printf("68K read from %06X (byte)\n", addr);
return 0;
}
static UINT16 __cdecl UnknownRead16(UINT32 addr)
{
printf("68K read from %06X (word)\n", addr);
return 0;
}
static UINT32 __cdecl UnknownRead32(UINT32 addr)
{
printf("68K read from %06X (longword)\n", addr);
return 0;
}
static void __cdecl UnknownWrite8(UINT32 addr, UINT8 data)
{
printf("68K wrote %06X=%02X\n", addr, data);
}
static void __cdecl UnknownWrite16(UINT32 addr, UINT16 data)
{
printf("68K wrote %06X=%04X\n", addr, data);
}
static void __cdecl UnknownWrite32(UINT32 addr, UINT32 data)
{
printf("68K wrote %06X=%08X\n", addr, data);
}
/******************************************************************************
Emulation Functions
******************************************************************************/
int irqLine = 0;
// SCSP callback for generating IRQs
void SCSP68KIRQCallback(int irqNum)
{
#ifdef SUPERMODEL_SOUND
//printf("IRQ: %d\n", irqNum);
irqLine = irqNum;
//Turbo68KInterrupt(irqNum, TURBO68K_AUTOVECTOR);
#endif
}
// SCSP callback for running the 68K
int SCSP68KRunCallback(int numCycles)
{
#ifdef SUPERMODEL_SOUND
for (int i = 0; i < numCycles; i++)
{
if (irqLine)
Turbo68KInterrupt(irqLine,TURBO68K_AUTOVECTOR);
Turbo68KRun(1);
}
return numCycles;
//Turbo68KRun(numCycles);
//return Turbo68KGetElapsedCycles();
#else
return numCycles;
#endif
}
void CSoundBoard::WriteMIDIPort(UINT8 data)
{
SCSP_MidiIn(data);
}
void CSoundBoard::RunFrame(void)
{
#ifdef SUPERMODEL_SOUND
SCSP_Update();
// Output to binary file
INT16 s;
for (int i = 0; i < 44100/60; i++)
{
s = ((UINT16)leftBuffer[i]>>8) | ((leftBuffer[i]&0xFF)<<8);
fwrite(&s, sizeof(INT16), 1, soundFP); // left channel
s = ((UINT16)rightBuffer[i]>>8) | ((rightBuffer[i]&0xFF)<<8);
fwrite(&s, sizeof(INT16), 1, soundFP); // right channel
}
#endif
}
void CSoundBoard::Reset(void)
{
#ifdef SUPERMODEL_SOUND
memcpy(ram1, soundROM, 16); // copy 68K vector table
Turbo68KReset();
/*
printf("68K PC=%06X\n", Turbo68KReadPC());
for (int i = 0; i < 1000000; i++)
{
//printf("%06X\n", Turbo68KReadPC());
Turbo68KRun(1);
}
*/
#endif
DebugLog("Sound Board Reset\n");
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
// Offsets of memory regions within sound board's pool
#define OFFSET_RAM1 0 // 1 MB SCSP1 RAM
#define OFFSET_RAM2 0x100000 // 1 MB SCSP2 RAM
#define MEMORY_POOL_SIZE (0x100000+0x100000)
BOOL CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr, CIRQ *ppcIRQObjectPtr, unsigned soundIRQBit)
{
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
// Attach IRQ controller
ppcIRQ = ppcIRQObjectPtr;
ppcSoundIRQBit = soundIRQBit;
// Receive sound ROMs
soundROM = soundROMPtr;
sampleROM = sampleROMPtr;
// Allocate all memory for RAM
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
if (NULL == memoryPool)
return ErrorLog("Insufficient memory for sound board (needs %1.1f MB).", memSizeMB);
memset(memoryPool, 0, MEMORY_POOL_SIZE);
// Set up memory pointers
ram1 = &memoryPool[OFFSET_RAM1];
ram2 = &memoryPool[OFFSET_RAM2];
// Initialize 68K core
#ifdef SUPERMODEL_SOUND
mapFetch[0].ptr = mapRead8[0].ptr = mapRead16[0].ptr = mapRead32[0].ptr =
mapWrite8[0].ptr = mapWrite16[0].ptr = mapWrite32[0].ptr = (UINT32)ram1 - mapFetch[0].base;;
mapFetch[1].ptr = mapRead8[1].ptr = mapRead16[1].ptr = mapRead32[1].ptr =
mapWrite8[1].ptr = mapWrite16[1].ptr = mapWrite32[1].ptr = (UINT32)ram2 - mapFetch[1].base;
mapFetch[2].ptr = mapRead8[2].ptr = mapRead16[2].ptr = mapRead32[2].ptr = (UINT32)soundROM - mapFetch[2].base;
mapRead8[3].ptr = mapRead16[3].ptr = mapRead32[3].ptr = (UINT32)&sampleROM[0x000000] - mapRead8[3].base;
mapRead8[4].ptr = mapRead16[4].ptr = mapRead32[4].ptr = (UINT32)&sampleROM[0x200000] - mapRead8[4].base;
mapRead8[5].ptr = mapRead16[5].ptr = mapRead32[5].ptr = (UINT32)&sampleROM[0x600000] - mapRead8[5].base;
Turbo68KInit();
Turbo68KSetFetch(mapFetch, NULL);
Turbo68KSetReadByte(mapRead8, NULL);
Turbo68KSetReadWord(mapRead16, NULL);
Turbo68KSetReadLong(mapRead32, NULL);
Turbo68KSetWriteByte(mapWrite8, NULL);
Turbo68KSetWriteWord(mapWrite16, NULL);
Turbo68KSetWriteLong(mapWrite32, NULL);
#endif
// Initialize SCSPs
SCSP_SetBuffers(leftBuffer, rightBuffer, 44100/60);
SCSP_SetCB(SCSP68KRunCallback, SCSP68KIRQCallback, ppcIRQ, ppcSoundIRQBit);
SCSP_Init(2);
SCSP_SetRAM(0, ram1);
SCSP_SetRAM(1, ram2);
// Binary logging
#ifdef SUPERMODEL_SOUND
soundFP = fopen("sound.bin","wb"); // delete existing file
fclose(soundFP);
soundFP = fopen("sound.bin","ab"); // append mode
#endif
return OKAY;
}
CSoundBoard::CSoundBoard(void)
{
memoryPool = NULL;
ram1 = NULL;
ram2 = NULL;
DebugLog("Built Sound Board\n");
}
static void Reverse16(UINT8 *buf, unsigned size)
{
unsigned i;
UINT8 tmp;
for (i = 0; i < size; i += 2)
{
tmp = buf[i+0];
buf[i+0] = buf[i+1];
buf[i+1] = tmp;
}
}
CSoundBoard::~CSoundBoard(void)
{
#ifdef SUPERMODEL_SOUND
// close binary log file
fclose(soundFP);
//#if 0
FILE *fp;
Reverse16(ram1, 0x100000);
Reverse16(ram2, 0x100000);
fp = fopen("scspRAM1", "wb");
if (NULL != fp)
{
fwrite(ram1, sizeof(UINT8), 0x100000, fp);
fclose(fp);
printf("dumped %s\n", "scspRAM1");
}
fp = fopen("scspRAM2", "wb");
if (NULL != fp)
{
fwrite(ram2, sizeof(UINT8), 0x100000, fp);
fclose(fp);
printf("dumped %s\n", "scspRAM2");
}
//#endif
#endif
SCSP_Deinit();
if (memoryPool != NULL)
{
delete [] memoryPool;
memoryPool = NULL;
}
ram1 = NULL;
ram2 = NULL;
DebugLog("Destroyed Sound Board\n");
}

103
Model3/SoundBoard.h Normal file
View file

@ -0,0 +1,103 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* SoundBoard.h
*
* Header file defining the CSoundBoard class: Model 3 audio subsystem.
*/
#ifndef INCLUDED_SOUNDBOARD_H
#define INCLUDED_SOUNDBOARD_H
/*
* CSoundBoard:
*
* Model 3 sound board (68K CPU + 2 x SCSP).
*/
class CSoundBoard
{
public:
/*
* WriteMIDIPort(data):
*
* Writes to the sound board MIDI port.
*
* Parameters:
* data Byte to write to MIDI port.
*/
void WriteMIDIPort(UINT8 data);
/*
* RunFrame(void):
*
* Runs the sound board for one frame, updating sound in the process.
*/
void RunFrame(void);
/*
* Reset(void):
*
* Resets the sound board.
*/
void Reset(void);
/*
* Init(soundROMPtr, sampleROMPtr):
*
* One-time initialization. Must be called prior to all other members.
*
* Parameters:
* soundROMPtr Pointer to sound ROM (68K program).
* sampleROMPtr Pointer to sample ROM.
* ppcIRQObjectPtr Pointer to PowerPC-side IRQ object.
* soundIRQBit IRQ bit mask to use for sound board PowerPC IRQs.
*
* Returns:
* OKAY if successful, FAIL if unable to allocate memory. Prints own
* error messages.
*/
BOOL Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr, CIRQ *ppcIRQObjectPtr, unsigned soundIRQBit);
/*
* CSoundBoard(void):
* ~CSoundBoard(void):
*
* Constructor and destructor.
*/
CSoundBoard(void);
~CSoundBoard(void);
private:
// PowerPC IRQ controller
CIRQ *ppcIRQ;
unsigned ppcSoundIRQBit;
// Sound board memory
const UINT8 *soundROM; // 68K program ROM (passed in from parent object)
const UINT8 *sampleROM; // 68K sample ROM (passed in from parent object)
UINT8 *memoryPool; // single allocated region for all sound board RAM
UINT8 *ram1, *ram2; // SCSP1 and SCSP2 RAM
};
#endif // INCLUDED_SOUNDBOARD_H

205
Model3/TileGen.cpp Normal file
View file

@ -0,0 +1,205 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* TileGen.cpp
*
* Implementation of the CTileGen class: 2D tile generator.
*
* TO-DO List:
* -----------
* - For consistency, the registers should probably be byte reversed (this is a
* little endian device), forcing the Model3 Read32/Write32 handlers to
* manually reverse the data. This keeps with the convention for VRAM.
*/
#include <string.h>
#include "Supermodel.h"
/******************************************************************************
Save States
******************************************************************************/
void CTileGen::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("Tile Generator", __FILE__);
SaveState->Write(memoryPool, 0x100000+0x20000);
SaveState->Write(regs, sizeof(regs));
}
void CTileGen::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("Tile Generator"))
{
ErrorLog("Unable to load tile generator state. Save state file is corrupted.");
return;
}
// Load memory one word at a time
for (int i = 0; i < (0x100000+0x20000); i += 4)
{
UINT32 data;
SaveState->Read(&data, sizeof(data));
Render2D->WriteVRAM(i, data);
*(UINT32 *) &memoryPool[i] = data;
}
SaveState->Read(regs, sizeof(regs));
}
/******************************************************************************
Rendering
******************************************************************************/
void CTileGen::BeginFrame(void)
{
Render2D->BeginFrame();
/*
printf("08: %X\n", regs[0x08/4]);
printf("0C: %X\n", regs[0x0C/4]);
printf("20: %X\n", regs[0x20/4]);
printf("40: %X\n", regs[0x40/4]);
printf("44: %X\n", regs[0x44/4]);
printf("60: %08X\n", regs[0x60/4]);
printf("64: %08X\n", regs[0x64/4]);
printf("68: %08X\n", regs[0x68/4]);
printf("6C: %08X\n", regs[0x6C/4]);
*/
}
void CTileGen::EndFrame(void)
{
Render2D->EndFrame();
}
/******************************************************************************
Emulation Functions
******************************************************************************/
UINT32 CTileGen::ReadRAM(unsigned addr)
{
return *(UINT32 *) &memoryPool[addr];
}
void CTileGen::WriteRAM(unsigned addr, UINT32 data)
{
Render2D->WriteVRAM(addr,data); // inform renderer of update first
*(UINT32 *) &memoryPool[addr] = data;
}
void CTileGen::WriteRegister(unsigned reg, UINT32 data)
{
reg &= 0xFF;
regs[reg/4] = data;
switch (reg)
{
case 0x10: // IRQ acknowledge
IRQ->Deassert(data&0xFF);
break;
case 0x60:
break;
case 0x64:
break;
case 0x68:
break;
case 0x6C:
break;
default:
DebugLog("Tile Generator reg %02X = %08X\n", reg, data);
//printf("%02X = %08X\n", reg, data);
break;
}
}
void CTileGen::Reset(void)
{
memset(regs, 0, sizeof(regs));
memset(memoryPool, 0, 0x120000);
DebugLog("Tile Generator reset\n");
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void CTileGen::AttachRenderer(CRender2D *Render2DPtr)
{
Render2D = Render2DPtr;
Render2D->AttachVRAM(memoryPool);
Render2D->AttachRegisters(regs);
DebugLog("Tile Generator attached a Render2D object\n");
}
#define MEMORY_POOL_SIZE 0x120000
BOOL CTileGen::Init(CIRQ *IRQObjectPtr)
{
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
// Allocate all memory for ROMs and PPC RAM
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
if (NULL == memoryPool)
return ErrorLog("Insufficient memory for tile generator object (needs %1.1f MB).", memSizeMB);
// Hook up the IRQ controller
IRQ = IRQObjectPtr;
DebugLog("Initialized Tile Generator (allocated %1.1f MB and connected to IRQ controller)\n", memSizeMB);
return OKAY;
}
CTileGen::CTileGen(void)
{
IRQ = NULL;
memoryPool = NULL;
DebugLog("Built Tile Generator\n");
}
CTileGen::~CTileGen(void)
{
// Dump tile generator RAM
#if 0
FILE *fp;
fp = fopen("tileram", "wb");
if (NULL != fp)
{
fwrite(memoryPool, sizeof(UINT8), 0x120000, fp);
fclose(fp);
printf("dumped %s\n", "tileram");
}
else
printf("unable to dump %s\n", "tileram");
#endif
IRQ = NULL;
if (memoryPool != NULL)
{
delete [] memoryPool;
memoryPool = NULL;
}
DebugLog("Destroyed Tile Generator\n");
}

177
Model3/TileGen.h Normal file
View file

@ -0,0 +1,177 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* TileGen.h
*
* Header file defining the CTileGen (tile generator device) class.
*/
#ifndef INCLUDED_TILEGEN_H
#define INCLUDED_TILEGEN_H
/*
* CTileGen:
*
* Tile generator device. The Model 3's tile generator handles not only the 2D
* tile map layers but also seems to control video output and VBL IRQs.
*/
class CTileGen
{
public:
/*
* SaveState(SaveState):
*
* Saves an image of the current device state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Loads and a state image.
*
* Parameters:
* SaveState Block file to load state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* BeginFrame(void):
*
* Prepare to render a new frame. Must be called once per frame prior to
* drawing anything.
*/
void BeginFrame(void);
/*
* EndFrame(void):
*
* Signals the end of rendering for this frame. Must be called last during
* the frame.
*/
void EndFrame(void);
/*
* ReadRAM(addr):
*
* Reads the tile generator's little endian RAM (the word is returned with
* the MSB read from addr+3). If a big endian device is reading (PowerPC),
* the result must be manually flipped.
*
* Parameters:
* addr Address in tile generator RAM. Caller must ensure it is
* clamped to the range 0x000000 to 0x11FFFF because this
* function does not.
*
* Returns:
* A 32-bit word read as little endian from the RAM address.
*/
UINT32 ReadRAM(unsigned addr);
/*
* WriteRAM(addr, data):
*
* Writes to the tile generator's little endian RAM (the word's MSB is
* written to addr+3). If a big endian device is writing, the word must be
* flipped manually before passing it to this function.
*
* Parameters:
* addr Address in tile generator RAM. Caller must ensure it is
* clamped to the range 0x000000 to 0x11FFFF because this
* function does not.
* data The data to write.
*/
void WriteRAM(unsigned addr, UINT32 data);
/*
* WriteRegister(reg, data):
*
* Writes 32 bits of data to a (little endian) register. If a big endian
* device is writing, th word must be flipped.
*
* Parameters:
* reg Aligned (32 bits) register offset (0x00-0xFC).
* data Data to write.
*/
void WriteRegister(unsigned reg, UINT32 data);
/*
* Reset(void):
*
* Resets the device.
*/
void Reset(void);
/*
* AttachRenderer(render2DPtr):
*
* Attaches a 2D renderer for the tile generator to use. This function will
* immediately pass along the information that a CRender2D object needs to
* work with.
*
* Parameters:
* Render2DPtr Pointer to a 2D renderer object.
*/
void AttachRenderer(CRender2D *Render2DPtr);
/*
* Init(IRQObjectPtr):
*
* One-time initialization of the context. Must be called prior to all
* other members. Links the tile generator to an IRQ controller and
* allocates memory for tile generator RAM.
*
* Parameters:
* IRQObjectPtr Pointer to the IRQ controller object.
*
* Returns:
* OKAY is successful, otherwise FAILED if a non-recoverable error
* occurred. Prints own error messages.
*/
BOOL Init(CIRQ *IRQObjectPtr);
/*
* CTileGen(void):
* ~CTileGen(void):
*
* Constructor and destructor.
*/
CTileGen(void);
~CTileGen(void);
private:
CIRQ *IRQ; // IRQ controller the tile generator is attached to
CRender2D *Render2D; // 2D renderer the tile generator is attached to
// Tile generator VRAM
UINT8 *memoryPool; // all memory allocated here
// Registers
UINT32 regs[64];
};
#endif // INCLUDED_TILEGEN_H

1097
OSD/SDL/Main.cpp Normal file

File diff suppressed because it is too large Load diff

386
OSD/SDL/SDLInputSystem.cpp Normal file
View file

@ -0,0 +1,386 @@
#include "SDLInputSystem.h"
#include "Supermodel.h"
#include <vector>
using namespace std;
SDLKeyMapStruct CSDLInputSystem::s_keyMap[] =
{
// General keys
{ "BACKSPACE", SDLK_BACKSPACE },
{ "TAB", SDLK_TAB },
{ "CLEAR", SDLK_CLEAR },
{ "RETURN", SDLK_RETURN },
{ "PAUSE", SDLK_PAUSE },
{ "ESCAPE", SDLK_ESCAPE },
{ "SPACE", SDLK_SPACE },
{ "EXCLAIM", SDLK_EXCLAIM },
{ "DBLQUOTE", SDLK_QUOTEDBL },
{ "HASH", SDLK_HASH },
{ "DOLLAR", SDLK_DOLLAR },
{ "AMPERSAND", SDLK_AMPERSAND },
{ "QUOTE", SDLK_QUOTE },
{ "LEFTPAREN", SDLK_LEFTPAREN },
{ "RIGHTPAREN", SDLK_RIGHTPAREN },
{ "ASTERISK", SDLK_ASTERISK },
{ "PLUS", SDLK_PLUS },
{ "COMMA", SDLK_COMMA },
{ "MINUS", SDLK_MINUS },
{ "PERIOD", SDLK_PERIOD },
{ "SLASH", SDLK_SLASH },
{ "0", SDLK_0 },
{ "1", SDLK_1 },
{ "2", SDLK_2 },
{ "3", SDLK_3 },
{ "4", SDLK_4 },
{ "5", SDLK_5 },
{ "6", SDLK_6 },
{ "7", SDLK_7 },
{ "8", SDLK_8 },
{ "9", SDLK_9 },
{ "COLON", SDLK_COLON },
{ "SEMICOLON", SDLK_SEMICOLON },
{ "LESS", SDLK_LESS },
{ "EQUALS", SDLK_EQUALS },
{ "GREATER", SDLK_GREATER },
{ "QUESTION", SDLK_QUESTION },
{ "AT", SDLK_AT },
{ "LEFTBRACKET", SDLK_LEFTBRACKET },
{ "BACKSLASH", SDLK_BACKSLASH },
{ "RIGHTBRACKET", SDLK_RIGHTBRACKET },
{ "CARET", SDLK_CARET },
{ "UNDERSCORE", SDLK_UNDERSCORE },
{ "BACKQUOTE", SDLK_BACKQUOTE },
{ "A", SDLK_a },
{ "B", SDLK_b },
{ "C", SDLK_c },
{ "D", SDLK_d },
{ "E", SDLK_e },
{ "F", SDLK_f },
{ "G", SDLK_g },
{ "H", SDLK_h },
{ "I", SDLK_i },
{ "J", SDLK_j },
{ "K", SDLK_k },
{ "L", SDLK_l },
{ "M", SDLK_m },
{ "N", SDLK_n },
{ "O", SDLK_o },
{ "P", SDLK_p },
{ "Q", SDLK_q },
{ "R", SDLK_r },
{ "S", SDLK_s },
{ "T", SDLK_t },
{ "U", SDLK_u },
{ "V", SDLK_v },
{ "W", SDLK_w },
{ "X", SDLK_x },
{ "Y", SDLK_y },
{ "Z", SDLK_z },
{ "DEL", SDLK_DELETE },
// Keypad
{ "KEYPAD0", SDLK_KP0 },
{ "KEYPAD1", SDLK_KP1 },
{ "KEYPAD2", SDLK_KP2 },
{ "KEYPAD3", SDLK_KP3 },
{ "KEYPAD4", SDLK_KP4 },
{ "KEYPAD5", SDLK_KP5 },
{ "KEYPAD6", SDLK_KP6 },
{ "KEYPAD7", SDLK_KP7 },
{ "KEYPAD8", SDLK_KP8 },
{ "KEYPAD9", SDLK_KP9 },
{ "KEYPADPERIOD", SDLK_KP_PERIOD },
{ "KEYPADDIVIDE", SDLK_KP_DIVIDE },
{ "KEYPADMULTIPLY", SDLK_KP_MULTIPLY },
{ "KEYPADMINUS", SDLK_KP_MINUS },
{ "KEYPADPLUS", SDLK_KP_PLUS },
{ "KEYPADENTER", SDLK_KP_ENTER },
{ "KEYPADEQUALS", SDLK_KP_EQUALS },
// Arrows + Home/End Pad
{ "UP", SDLK_UP },
{ "DOWN", SDLK_DOWN },
{ "RIGHT", SDLK_RIGHT },
{ "LEFT", SDLK_LEFT },
{ "INSERT", SDLK_INSERT },
{ "HOME", SDLK_HOME },
{ "END", SDLK_END },
{ "PGUP", SDLK_PAGEUP },
{ "PGDN", SDLK_PAGEDOWN },
// Function Key
{ "F1", SDLK_F1 },
{ "F2", SDLK_F2 },
{ "F3", SDLK_F3 },
{ "F4", SDLK_F4 },
{ "F5", SDLK_F5 },
{ "F6", SDLK_F6 },
{ "F7", SDLK_F7 },
{ "F8", SDLK_F8 },
{ "F9", SDLK_F9 },
{ "F10", SDLK_F10 },
{ "F11", SDLK_F11 },
{ "F12", SDLK_F12 },
{ "F13", SDLK_F13 },
{ "F14", SDLK_F14 },
{ "F15", SDLK_F15 },
// Modifier Keys
// Removed Numlock, Capslock and Scrollock as don't seem to be handled well by SDL
//{ "NUMLOCK", SDLK_NUMLOCK },
//{ "CAPSLOCK", SDLK_CAPSLOCK },
//{ "SCROLLLOCK", SDLK_SCROLLOCK },
{ "RIGHTSHIFT", SDLK_RSHIFT },
{ "LEFTSHIFT", SDLK_LSHIFT },
{ "RIGHTCTRL", SDLK_RCTRL },
{ "LEFTCTRL", SDLK_LCTRL },
{ "RIGHTALT", SDLK_RALT },
{ "LEFTALT", SDLK_LALT },
{ "RIGHTMETA", SDLK_RMETA },
{ "LEFTMETA", SDLK_LMETA },
{ "RIGHTWINDOWS", SDLK_RSUPER },
{ "LEFTWINDOWS", SDLK_LSUPER },
{ "ALTGR", SDLK_MODE },
{ "COMPOSE", SDLK_COMPOSE },
// Other
{ "HELP", SDLK_HELP },
{ "PRINT", SDLK_PRINT },
{ "SYSREQ", SDLK_SYSREQ },
{ "BREAK", SDLK_BREAK },
{ "MENU", SDLK_MENU },
{ "POWER", SDLK_POWER },
{ "EURO", SDLK_EURO },
{ "UNDO", SDLK_UNDO }
};
CSDLInputSystem::CSDLInputSystem() : CInputSystem("SDL"), m_keyState(NULL), m_mouseX(0), m_mouseY(0), m_mouseZ(0), m_mouseButtons(0)
{
//
}
CSDLInputSystem::~CSDLInputSystem()
{
CloseJoysticks();
}
void CSDLInputSystem::OpenJoysticks()
{
// Open all available joysticks
int numJoys = SDL_NumJoysticks();
for (int joyNum = 0; joyNum < numJoys; joyNum++)
{
SDL_Joystick *joystick = SDL_JoystickOpen(joyNum);
if (joystick == NULL)
{
ErrorLog("Unable to open joystick device %d with SDL - skipping joystick.\n", joyNum + 1);
continue;
}
// Gather joystick details (num POVs & buttons and which axes are available)
int numAxes = SDL_JoystickNumAxes(joystick);
JoyDetails *joyDetails = new JoyDetails();
joyDetails->hasXAxis = numAxes > 0;
joyDetails->hasYAxis = numAxes > 1;
joyDetails->hasZAxis = numAxes > 2;
joyDetails->hasRXAxis = numAxes > 3;
joyDetails->hasRYAxis = numAxes > 4;
joyDetails->hasRZAxis = numAxes > 5;
joyDetails->numPOVs = SDL_JoystickNumHats(joystick);
joyDetails->numButtons = SDL_JoystickNumButtons(joystick);
m_joysticks.push_back(joystick);
m_joyDetails.push_back(joyDetails);
}
}
void CSDLInputSystem::CloseJoysticks()
{
// Close all previously opened joysticks
for (size_t i = 0; i < m_joysticks.size(); i++)
{
SDL_Joystick *joystick = m_joysticks[i];
JoyDetails *joyDetails = m_joyDetails[i];
SDL_JoystickClose(joystick);
delete joyDetails;
}
m_joysticks.clear();
m_joyDetails.clear();
}
bool CSDLInputSystem::InitializeSystem()
{
// Make sure joystick subsystem is initialized and joystick events are enabled
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0)
{
ErrorLog("Unable to initialize SDL joystick subsystem (%s).\n", SDL_GetError());
return false;
}
SDL_JoystickEventState(SDL_ENABLE);
// Open attached joysticks
OpenJoysticks();
return true;
}
int CSDLInputSystem::GetNumKeyboards()
{
// Return ANY_KEYBOARD as SDL 1.2 cannot handle multiple keyboards
return ANY_KEYBOARD;
}
int CSDLInputSystem::GetNumMice()
{
// Return ANY_MOUSE as SDL 1.2 cannot handle multiple mice
return ANY_MOUSE;
}
int CSDLInputSystem::GetNumJoysticks()
{
// Return number of joysticks found
return m_joysticks.size();
}
int CSDLInputSystem::GetKeyIndex(const char *keyName)
{
for (int i = 0; i < NUM_SDL_KEYS; i++)
{
if (stricmp(keyName, s_keyMap[i].keyName) == 0)
return i;
}
return -1;
}
const char *CSDLInputSystem::GetKeyName(int keyIndex)
{
if (keyIndex < 0 || keyIndex >= NUM_SDL_KEYS)
return NULL;
return s_keyMap[keyIndex].keyName;
}
JoyDetails *CSDLInputSystem::GetJoyDetails(int joyNum)
{
return m_joyDetails[joyNum];
}
bool CSDLInputSystem::IsKeyPressed(int kbdNum, int keyIndex)
{
// Get SDL key for given index and check if currently pressed
SDLKey sdlKey = s_keyMap[keyIndex].sdlKey;
return !!m_keyState[sdlKey];
}
int CSDLInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
{
// Return value for given mouse axis
switch (axisNum)
{
case AXIS_X: return m_mouseX;
case AXIS_Y: return m_mouseY;
case AXIS_Z: return m_mouseZ;
default: return 0;
}
}
int CSDLInputSystem::GetMouseWheelDir(int mseNum)
{
// Return wheel value
return m_mouseWheelDir;
}
bool CSDLInputSystem::IsMouseButPressed(int mseNum, int butNum)
{
// Return value for given mouse button
switch (butNum)
{
case 0: return !!(m_mouseButtons & SDL_BUTTON_LMASK);
case 1: return !!(m_mouseButtons & SDL_BUTTON_MMASK);
case 2: return !!(m_mouseButtons & SDL_BUTTON_RMASK);
case 3: return !!(m_mouseButtons & SDL_BUTTON_X1MASK);
case 4: return !!(m_mouseButtons & SDL_BUTTON_X2MASK);
default: return false;
}
}
int CSDLInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
{
// Get raw joystick axis value for given joystick from SDL (values range from -32768 to 32767)
SDL_Joystick *joystick = m_joysticks[joyNum];
return SDL_JoystickGetAxis(joystick, axisNum);
}
bool CSDLInputSystem::IsJoyPOVInDir(int joyNum, int povNum, int povDir)
{
// Get current joystick POV-hat value for given joystick and POV number from SDL and check if pointing in required direction
SDL_Joystick *joystick = m_joysticks[joyNum];
int hatVal = SDL_JoystickGetHat(joystick, povNum);
switch (povDir)
{
case POV_UP: return !!(hatVal & SDL_HAT_UP);
case POV_DOWN: return !!(hatVal & SDL_HAT_DOWN);
case POV_LEFT: return !!(hatVal & SDL_HAT_LEFT);
case POV_RIGHT: return !!(hatVal & SDL_HAT_RIGHT);
default: return false;
}
return false;
}
bool CSDLInputSystem::IsJoyButPressed(int joyNum, int butNum)
{
// Get current joystick button state for given joystick and button number from SDL
SDL_Joystick *joystick = m_joysticks[joyNum];
return !!SDL_JoystickGetButton(joystick, butNum);
}
void CSDLInputSystem::Wait(int ms)
{
SDL_Delay(ms);
}
void CSDLInputSystem::SetMouseVisibility(bool visible)
{
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
}
bool CSDLInputSystem::Poll()
{
// Reset mouse wheel direction
m_mouseWheelDir = 0;
// Poll for event from SDL
SDL_Event e;
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
return false;
else if (e.type == SDL_MOUSEBUTTONDOWN)
{
// Mouse wheel movements are not returned by SDL_GetMouseState below, so they are handled here instead
// The mouse absolute Z-axis value and mouse wheel direction value are updated appropriately
if (e.button.button == SDL_BUTTON_WHEELUP)
{
m_mouseZ += 5;
m_mouseWheelDir = 1;
}
else if (e.button.button == SDL_BUTTON_WHEELDOWN)
{
m_mouseZ -= 5;
m_mouseWheelDir = -1;
}
}
}
// Get key state from SDL
m_keyState = SDL_GetKeyState(NULL);
// Get mouse state from SDL (except mouse wheel which was handled earlier)
m_mouseButtons = SDL_GetMouseState(&m_mouseX, &m_mouseY);
// Update joystick state (not required as called implicitly by SDL_PollEvent above)
//SDL_JoystickUpdate();
return true;
}

106
OSD/SDL/SDLInputSystem.h Normal file
View file

@ -0,0 +1,106 @@
#ifndef INCLUDED_SDLINPUTSYSTEM_H
#define INCLUDED_SDLINPUTSYSTEM_H
#include "Types.h"
#include "Inputs/InputSource.h"
#include "Inputs/InputSystem.h"
#ifdef SUPERMODEL_OSX
#include <SDL/SDL.h>
#else
#include <SDL.h>
#endif
#include <vector>
using namespace std;
#define NUM_SDL_KEYS (sizeof(s_keyMap) / sizeof(SDLKeyMapStruct))
struct SDLKeyMapStruct
{
const char *keyName;
SDLKey sdlKey;
};
/*
* Input system that uses SDL.
*/
class CSDLInputSystem : public CInputSystem
{
private:
// Lookup table to map key names to SDLKeys
static SDLKeyMapStruct s_keyMap[];
// Vector to keep track of attached joysticks
vector<SDL_Joystick*> m_joysticks;
vector<JoyDetails*> m_joyDetails;
// Current key state obtained from SDL
Uint8 *m_keyState;
// Current mouse state obtained from SDL
int m_mouseX;
int m_mouseY;
int m_mouseZ;
short m_mouseWheelDir;
Uint8 m_mouseButtons;
/*
* Opens all attached joysticks.
*/
void OpenJoysticks();
/*
* Closes all attached joysticks.
*/
void CloseJoysticks();
protected:
/*
* Initializes the SDL input system.
*/
bool InitializeSystem();
int GetNumKeyboards();
int GetNumMice();
int GetNumJoysticks();
int GetKeyIndex(const char *keyName);
const char *GetKeyName(int keyIndex);
JoyDetails *GetJoyDetails(int joyNum);
bool IsKeyPressed(int kbdNum, int keyIndex);
int GetMouseAxisValue(int mseNum, int axisNum);
int GetMouseWheelDir(int mseNum);
bool IsMouseButPressed(int mseNum, int butNum);
int GetJoyAxisValue(int joyNum, int axisNum);
bool IsJoyPOVInDir(int joyNum, int povNum, int povDir);
bool IsJoyButPressed(int joyNum, int butNum);
void Wait(int ms);
void SetMouseVisibility(bool visible);
public:
/*
* Constructs an SDL input system.
*/
CSDLInputSystem();
~CSDLInputSystem();
bool Poll();
};
#endif // INCLUDED_SDLINPUTSYSTEM_H

11
OSD/SDL/SDLMain_tmpl.h Normal file
View file

@ -0,0 +1,11 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#import <Cocoa/Cocoa.h>
@interface SDLMain : NSObject
@end

384
OSD/SDL/SDLMain_tmpl.m Normal file
View file

@ -0,0 +1,384 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#import "SDL/SDL.h"
#import "SDLMain_tmpl.h"
#import <sys/param.h> /* for MAXPATHLEN */
#import <unistd.h>
/* For some reaon, Apple removed setAppleMenu from the headers in 10.4,
but the method still is there and works. To avoid warnings, we declare
it ourselves here. */
@interface NSApplication(SDL_Missing_Methods)
- (void)setAppleMenu:(NSMenu *)menu;
@end
/* Use this flag to determine whether we use SDLMain.nib or not */
#define SDL_USE_NIB_FILE 0
/* Use this flag to determine whether we use CPS (docking) or not */
#define SDL_USE_CPS 0
#ifdef SDL_USE_CPS
/* Portions of CPS.h */
typedef struct CPSProcessSerNum
{
UInt32 lo;
UInt32 hi;
} CPSProcessSerNum;
extern "C" OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn);
extern "C" OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
extern "C" OSErr CPSSetFrontProcess( CPSProcessSerNum *psn);
#endif /* SDL_USE_CPS */
static int gArgc;
static char **gArgv;
static BOOL gFinderLaunch;
static BOOL gCalledAppMainline = FALSE;
static NSString *getApplicationName(void)
{
NSDictionary *dict;
NSString *appName = 0;
/* Determine the application name */
dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle());
if (dict)
appName = [dict objectForKey: @"CFBundleName"];
if (![appName length])
appName = [[NSProcessInfo processInfo] processName];
return appName;
}
#if SDL_USE_NIB_FILE
/* A helper category for NSString */
@interface NSString (ReplaceSubString)
- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString;
@end
#endif
@interface SDLApplication : NSApplication
@end
@implementation SDLApplication
/* Invoked from the Quit menu item */
- (void)terminate:(id)sender
{
/* Post a SDL_QUIT event */
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
@end
/* The main class of the application, the application's delegate */
@implementation SDLMain
/* Set the working directory to the .app's parent directory */
- (void) setupWorkingDirectory:(BOOL)shouldChdir
{
if (shouldChdir)
{
char parentdir[MAXPATHLEN];
CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url);
if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) {
assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */
}
CFRelease(url);
CFRelease(url2);
}
}
#if SDL_USE_NIB_FILE
/* Fix menu to contain the real app name instead of "SDL App" */
- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName
{
NSRange aRange;
NSEnumerator *enumerator;
NSMenuItem *menuItem;
aRange = [[aMenu title] rangeOfString:@"SDL App"];
if (aRange.length != 0)
[aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]];
enumerator = [[aMenu itemArray] objectEnumerator];
while ((menuItem = [enumerator nextObject]))
{
aRange = [[menuItem title] rangeOfString:@"SDL App"];
if (aRange.length != 0)
[menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]];
if ([menuItem hasSubmenu])
[self fixMenu:[menuItem submenu] withAppName:appName];
}
[ aMenu sizeToFit ];
}
#else
static void setApplicationMenu(void)
{
/* warning: this code is very odd */
NSMenu *appleMenu;
NSMenuItem *menuItem;
NSString *title;
NSString *appName;
appName = getApplicationName();
appleMenu = [[NSMenu alloc] initWithTitle:@""];
/* Add menu items */
title = [@"About " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
[appleMenu addItem:[NSMenuItem separatorItem]];
title = [@"Hide " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
[menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
[appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
[appleMenu addItem:[NSMenuItem separatorItem]];
title = [@"Quit " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
/* Put menu into the menubar */
menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
[menuItem setSubmenu:appleMenu];
[[NSApp mainMenu] addItem:menuItem];
/* Tell the application object that this is now the application menu */
[NSApp setAppleMenu:appleMenu];
/* Finally give up our references to the objects */
[appleMenu release];
[menuItem release];
}
/* Create a window menu */
static void setupWindowMenu(void)
{
NSMenu *windowMenu;
NSMenuItem *windowMenuItem;
NSMenuItem *menuItem;
windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
/* "Minimize" item */
menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
[windowMenu addItem:menuItem];
[menuItem release];
/* Put menu into the menubar */
windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
[windowMenuItem setSubmenu:windowMenu];
[[NSApp mainMenu] addItem:windowMenuItem];
/* Tell the application object that this is now the window menu */
[NSApp setWindowsMenu:windowMenu];
/* Finally give up our references to the objects */
[windowMenu release];
[windowMenuItem release];
}
/* Replacement for NSApplicationMain */
static void CustomApplicationMain (int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SDLMain *sdlMain;
/* Ensure the application object is initialised */
[SDLApplication sharedApplication];
#ifdef SDL_USE_CPS
{
CPSProcessSerNum PSN;
/* Tell the dock about us */
if (!CPSGetCurrentProcess(&PSN))
if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
if (!CPSSetFrontProcess(&PSN))
[SDLApplication sharedApplication];
}
#endif /* SDL_USE_CPS */
/* Set up the menubar */
[NSApp setMainMenu:[[NSMenu alloc] init]];
setApplicationMenu();
setupWindowMenu();
/* Create SDLMain and make it the app delegate */
sdlMain = [[SDLMain alloc] init];
[NSApp setDelegate:sdlMain];
/* Start the main event loop */
[NSApp run];
[sdlMain release];
[pool release];
}
#endif
/*
* Catch document open requests...this lets us notice files when the app
* was launched by double-clicking a document, or when a document was
* dragged/dropped on the app's icon. You need to have a
* CFBundleDocumentsType section in your Info.plist to get this message,
* apparently.
*
* Files are added to gArgv, so to the app, they'll look like command line
* arguments. Previously, apps launched from the finder had nothing but
* an argv[0].
*
* This message may be received multiple times to open several docs on launch.
*
* This message is ignored once the app's mainline has been called.
*/
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
const char *temparg;
size_t arglen;
char *arg;
char **newargv;
if (!gFinderLaunch) /* MacOS is passing command line args. */
return FALSE;
if (gCalledAppMainline) /* app has started, ignore this document. */
return FALSE;
temparg = [filename UTF8String];
arglen = SDL_strlen(temparg) + 1;
arg = (char *) SDL_malloc(arglen);
if (arg == NULL)
return FALSE;
newargv = (char **) SDL_realloc(gArgv, sizeof (char *) * (gArgc + 2));
if (newargv == NULL)
{
SDL_free(arg);
return FALSE;
}
gArgv = newargv;
SDL_strlcpy(arg, temparg, arglen);
gArgv[gArgc++] = arg;
gArgv[gArgc] = NULL;
return TRUE;
}
/* Called when the internal event loop has just started running */
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
int status;
/* Set the working directory to the .app's parent directory */
[self setupWorkingDirectory:gFinderLaunch];
#if SDL_USE_NIB_FILE
/* Set the main menu to contain the real app name instead of "SDL App" */
[self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()];
#endif
/* Hand off to main application code */
gCalledAppMainline = TRUE;
status = SDL_main (gArgc, gArgv);
/* We're done, thank you for playing */
exit(status);
}
@end
@implementation NSString (ReplaceSubString)
- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString
{
unsigned int bufferSize;
unsigned int selfLen = [self length];
unsigned int aStringLen = [aString length];
unichar *buffer;
NSRange localRange;
NSString *result;
bufferSize = selfLen + aStringLen - aRange.length;
buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar));
/* Get first part into buffer */
localRange.location = 0;
localRange.length = aRange.location;
[self getCharacters:buffer range:localRange];
/* Get middle part into buffer */
localRange.location = 0;
localRange.length = aStringLen;
[aString getCharacters:(buffer+aRange.location) range:localRange];
/* Get last part into buffer */
localRange.location = aRange.location + aRange.length;
localRange.length = selfLen - localRange.location;
[self getCharacters:(buffer+aRange.location+aStringLen) range:localRange];
/* Build output string */
result = [NSString stringWithCharacters:buffer length:bufferSize];
NSDeallocateMemoryPages(buffer, bufferSize);
return result;
}
@end
#ifdef main
# undef main
#endif
/* Main entry point to executable - should *not* be SDL_main! */
int main (int argc, char **argv)
{
/* Copy the arguments into a global variable */
/* This is passed if we are launched by double-clicking */
if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) {
gArgv = (char **) SDL_malloc(sizeof (char *) * 2);
gArgv[0] = argv[0];
gArgv[1] = NULL;
gArgc = 1;
gFinderLaunch = YES;
} else {
int i;
gArgc = argc;
gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1));
for (i = 0; i <= argc; i++)
gArgv[i] = argv[i];
gFinderLaunch = NO;
}
#if SDL_USE_NIB_FILE
[SDLApplication poseAsClass:[NSApplication class]];
NSApplicationMain (argc, argv);
#else
CustomApplicationMain (argc, argv);
#endif
return 0;
}

51
OSD/SDL/Types.h Normal file
View file

@ -0,0 +1,51 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Types.h
*
* Fundamental data types.
*/
#ifndef INCLUDED_TYPES_H
#define INCLUDED_TYPES_H
// Booleans (must be 0 or 1 only)
#define TRUE 1
#define FALSE 0
#define OKAY FALSE
#define FAIL TRUE
// Types
typedef int BOOL;
typedef unsigned long long UINT64;
typedef signed long long INT64;
typedef unsigned int UINT32;
typedef signed int INT32;
typedef unsigned short int UINT16;
typedef signed short int INT16;
typedef unsigned char UINT8;
typedef signed char INT8;
typedef float FLOAT32;
typedef double FLOAT64;
#endif // INCLUDED_TYPES_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,176 @@
#ifndef INCLUDED_DIRECTINPUTSYSTEM_H
#define INCLUDED_DIRECTINPUTSYSTEM_H
#include "Types.h"
#include "Inputs/InputSource.h"
#include "Inputs/InputSystem.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dinput.h>
#include <vector>
using namespace std;
#define NUM_DI_KEYS (sizeof(s_keyMap) / sizeof(DIKeyMapStruct))
#define MATCHES_USAGE(wUsage, wUsagePage, word) wUsage == LOWORD(word) && wUsagePage == HIWORD(word)
#define X_AXIS_USAGE 0x00010030
#define Y_AXIS_USAGE 0x00010031
#define Z_AXIS_USAGE 0x00010032
#define RX_AXIS_USAGE 0x00010033
#define RY_AXIS_USAGE 0x00010034
#define RZ_AXIS_USAGE 0x00010035
struct DIKeyMapStruct
{
const char *keyName;
int diKey;
};
struct RawMseState
{
LONG x;
LONG y;
LONG z;
LONG wheelDelta;
SHORT wheelDir;
USHORT buttons;
};
struct DIMseState
{
LONG x;
LONG y;
LONG z;
SHORT wheelDir;
BYTE buttons[5];
};
typedef /*WINUSERAPI*/ INT (WINAPI *GetRawInputDeviceListPtr)(OUT PRAWINPUTDEVICELIST pRawInputDeviceList, IN OUT PUINT puiNumDevices, IN UINT cbSize);
typedef /*WINUSERAPI*/ INT (WINAPI *GetRawInputDeviceInfoPtr)(IN HANDLE hDevice, IN UINT uiCommand, OUT LPVOID pData, IN OUT PUINT pcbSize);
typedef /*WINUSERAPI*/ BOOL (WINAPI *RegisterRawInputDevicesPtr)(IN PCRAWINPUTDEVICE pRawInputDevices, IN UINT uiNumDevices, IN UINT cbSize);
typedef /*WINUSERAPI*/ INT (WINAPI *GetRawInputDataPtr)(IN HRAWINPUT hRawInput, IN UINT uiCommand, OUT LPVOID pData, IN OUT PUINT pcbSize, IN UINT cbSizeHeader);
LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK DI8EnumDevicesCallback(LPCDIDEVICEINSTANCE instance, LPVOID context);
BOOL CALLBACK DI8EnumAxesCallback(LPDIDEVICEOBJECTINSTANCE instance, LPVOID context);
/*
* Input system that uses DirectInput and RawInput APIs.
*/
class CDirectInputSystem : public CInputSystem
{
private:
// Lookup table to map key names to DirectInput keycodes and Virtual keycodes
static DIKeyMapStruct s_keyMap[];
bool m_useRawInput;
HWND m_hwnd;
bool m_activated;
GetRawInputDeviceListPtr m_getRIDevListPtr;
GetRawInputDeviceInfoPtr m_getRIDevInfoPtr;
RegisterRawInputDevicesPtr m_regRIDevsPtr;
GetRawInputDataPtr m_getRIDataPtr;
vector<HANDLE> m_rawKeyboards;
vector<BOOL*> m_rawKeyStates;
vector<HANDLE> m_rawMice;
RawMseState m_combRawMseState;
vector<RawMseState*> m_rawMseStates;
LPDIRECTINPUT8 m_di8;
LPDIRECTINPUTDEVICE8 m_di8Keyboard;
LPDIRECTINPUTDEVICE8 m_di8Mouse;
vector<LPDIRECTINPUTDEVICE8> m_di8Joysticks;
vector<JoyDetails*> m_joyDetails;
BYTE m_diKeyState[256];
DIMseState m_diMseState;
vector<LPDIJOYSTATE2> m_diJoyStates;
void OpenKeyboardsAndMice();
void ActivateKeyboardsAndMice();
void PollKeyboardsAndMice();
void CloseKeyboardsAndMice();
void ResetMice();
void ProcessRawInput(HRAWINPUT hInput);
void OpenJoysticks();
void ActivateJoysticks();
void PollJoysticks();
void CloseJoysticks();
protected:
/*
* Initializes the DirectInput input system.
*/
bool InitializeSystem();
int GetNumKeyboards();
int GetNumMice();
int GetNumJoysticks();
int GetKeyIndex(const char *keyName);
const char *GetKeyName(int keyIndex);
JoyDetails *GetJoyDetails(int joyNum);
bool IsKeyPressed(int joyNum, int keyIndex);
int GetMouseAxisValue(int mseNum, int axisNum);
int GetMouseWheelDir(int mseNum);
bool IsMouseButPressed(int mseNum, int butNum);
int GetJoyAxisValue(int joyNum, int axisNum);
bool IsJoyPOVInDir(int joyNum, int povNum, int povDir);
bool IsJoyButPressed(int joyNum, int butNum);
void Wait(int ms);
bool ConfigMouseCentered();
CInputSource *CreateAnyMouseSource(EMousePart msePart);
public:
/*
* Constructs a DirectInput/RawInput input system.
* If useRawInput is true then RawInput is used for keyboard and mice movements (allowing multiple devices). If false
* then DirectInput is used instead (which doesn't allow multiple devices).
* In both cases, DirectInput is used for reading joysticks.
*/
CDirectInputSystem(bool useRawInput);
~CDirectInputSystem();
bool Poll();
void SetMouseVisibility(bool visible);
void ConfigEnd();
};
#endif // INCLUDED_DIRECTINPUTSYSTEM_H

15201
Pkgs/glew.c Normal file

File diff suppressed because it is too large Load diff

15343
Pkgs/glew.h Normal file

File diff suppressed because it is too large Load diff

1537
Pkgs/glxew.h Normal file

File diff suppressed because it is too large Load diff

235
Pkgs/ioapi.c Normal file
View file

@ -0,0 +1,235 @@
/* ioapi.h -- IO base function header for compress/uncompress .zip
part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
Modifications for Zip64 support
Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
For more info read MiniZip_info.txt
*/
#if (defined(_WIN32))
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "ioapi.h"
voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
{
if (pfilefunc->zfile_func64.zopen64_file != NULL)
return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
else
{
return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
}
}
long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
{
if (pfilefunc->zfile_func64.zseek64_file != NULL)
return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
else
{
uLong offsetTruncated = (uLong)offset;
if (offsetTruncated != offset)
return -1;
else
return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
}
}
ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
{
if (pfilefunc->zfile_func64.zseek64_file != NULL)
return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
else
{
uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
if ((tell_uLong) == ((uLong)-1))
return (ZPOS64_T)-1;
else
return tell_uLong;
}
}
void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
{
p_filefunc64_32->zfile_func64.zopen64_file = NULL;
p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
p_filefunc64_32->zfile_func64.ztell64_file = NULL;
p_filefunc64_32->zfile_func64.zseek64_file = NULL;
p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
}
static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
{
FILE* file = NULL;
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else
if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else
if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename!=NULL) && (mode_fopen != NULL))
file = fopen(filename, mode_fopen);
return file;
}
static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
{
FILE* file = NULL;
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else
if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else
if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename!=NULL) && (mode_fopen != NULL))
file = fopen64((const char*)filename, mode_fopen);
return file;
}
static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
{
uLong ret;
ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
return ret;
}
static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
{
uLong ret;
ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
return ret;
}
static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
{
long ret;
ret = ftell((FILE *)stream);
return ret;
}
static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
{
ZPOS64_T ret;
ret = ftello64((FILE *)stream);
return ret;
}
static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin)
{
int fseek_origin=0;
long ret;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR :
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END :
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET :
fseek_origin = SEEK_SET;
break;
default: return -1;
}
ret = 0;
if (fseek((FILE *)stream, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
{
int fseek_origin=0;
long ret;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR :
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END :
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET :
fseek_origin = SEEK_SET;
break;
default: return -1;
}
ret = 0;
if(fseeko64((FILE *)stream, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
{
int ret;
ret = fclose((FILE *)stream);
return ret;
}
static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
{
int ret;
ret = ferror((FILE *)stream);
return ret;
}
void fill_fopen_filefunc (pzlib_filefunc_def)
zlib_filefunc_def* pzlib_filefunc_def;
{
pzlib_filefunc_def->zopen_file = fopen_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell_file = ftell_file_func;
pzlib_filefunc_def->zseek_file = fseek_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}
void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def)
{
pzlib_filefunc_def->zopen64_file = fopen64_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell64_file = ftell64_file_func;
pzlib_filefunc_def->zseek64_file = fseek64_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}

200
Pkgs/ioapi.h Normal file
View file

@ -0,0 +1,200 @@
/* ioapi.h -- IO base function header for compress/uncompress .zip
part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
Modifications for Zip64 support
Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
For more info read MiniZip_info.txt
Changes
Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this)
Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux.
More if/def section may be needed to support other platforms
Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows.
(but you should use iowin32.c for windows instead)
*/
#ifndef _ZLIBIOAPI64_H
#define _ZLIBIOAPI64_H
#if (!defined(_WIN32)) && (!defined(WIN32))
// Linux needs this to support file operation on files larger then 4+GB
// But might need better if/def to select just the platforms that needs them.
#ifndef __USE_FILE_OFFSET64
#define __USE_FILE_OFFSET64
#endif
#ifndef __USE_LARGEFILE64
#define __USE_LARGEFILE64
#endif
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#ifndef _FILE_OFFSET_BIT
#define _FILE_OFFSET_BIT 64
#endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include "zlib.h"
#if defined(USE_FILE32API)
#define fopen64 fopen
#define ftello64 ftell
#define fseeko64 fseek
#else
#ifdef _MSC_VER
#define fopen64 fopen
#if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
#define ftello64 _ftelli64
#define fseeko64 _fseeki64
#else // old MSC
#define ftello64 ftell
#define fseeko64 fseek
#endif
#endif
#endif
/*
#ifndef ZPOS64_T
#ifdef _WIN32
#define ZPOS64_T fpos_t
#else
#include <stdint.h>
#define ZPOS64_T uint64_t
#endif
#endif
*/
#ifdef HAVE_MINIZIP64_CONF_H
#include "mz64conf.h"
#endif
/* a type choosen by DEFINE */
#ifdef HAVE_64BIT_INT_CUSTOM
typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T;
#else
#ifdef HAS_STDINT_H
#include "stdint.h"
typedef uint64_t ZPOS64_T;
#else
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef unsigned __int64 ZPOS64_T;
#else
typedef unsigned long long int ZPOS64_T;
#endif
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define ZLIB_FILEFUNC_SEEK_CUR (1)
#define ZLIB_FILEFUNC_SEEK_END (2)
#define ZLIB_FILEFUNC_SEEK_SET (0)
#define ZLIB_FILEFUNC_MODE_READ (1)
#define ZLIB_FILEFUNC_MODE_WRITE (2)
#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
#define ZLIB_FILEFUNC_MODE_EXISTING (4)
#define ZLIB_FILEFUNC_MODE_CREATE (8)
#ifndef ZCALLBACK
#if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
#define ZCALLBACK CALLBACK
#else
#define ZCALLBACK
#endif
#endif
typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode));
typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size));
typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream));
typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream));
typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin));
/* here is the "old" 32 bits structure structure */
typedef struct zlib_filefunc_def_s
{
open_file_func zopen_file;
read_file_func zread_file;
write_file_func zwrite_file;
tell_file_func ztell_file;
seek_file_func zseek_file;
close_file_func zclose_file;
testerror_file_func zerror_file;
voidpf opaque;
} zlib_filefunc_def;
typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream));
typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode));
typedef struct zlib_filefunc64_def_s
{
open64_file_func zopen64_file;
read_file_func zread_file;
write_file_func zwrite_file;
tell64_file_func ztell64_file;
seek64_file_func zseek64_file;
close_file_func zclose_file;
testerror_file_func zerror_file;
voidpf opaque;
} zlib_filefunc64_def;
void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
/* now internal definition, only for zip.c and unzip.h */
typedef struct zlib_filefunc64_32_def_s
{
zlib_filefunc64_def zfile_func64;
open_file_func zopen32_file;
tell_file_func ztell32_file;
seek_file_func zseek32_file;
} zlib_filefunc64_32_def;
#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
//#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))
//#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))
#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream))
#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream))
voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32);
#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode)))
#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream)))
#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
#ifdef __cplusplus
}
#endif
#endif

2125
Pkgs/unzip.c Normal file

File diff suppressed because it is too large Load diff

437
Pkgs/unzip.h Normal file
View file

@ -0,0 +1,437 @@
/* unzip.h -- IO for uncompress .zip files using zlib
Version 1.1, February 14h, 2010
part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
Modifications of Unzip for Zip64
Copyright (C) 2007-2008 Even Rouault
Modifications for Zip64 support on both zip and unzip
Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
For more info read MiniZip_info.txt
---------------------------------------------------------------------------------
Condition of use and distribution are the same than zlib :
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
---------------------------------------------------------------------------------
Changes
See header of unzip64.c
*/
#ifndef _unz64_H
#define _unz64_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _ZLIB_H
#include "zlib.h"
#endif
#ifndef _ZLIBIOAPI_H
#include "ioapi.h"
#endif
#ifdef HAVE_BZIP2
#include "bzlib.h"
#endif
#define Z_BZIP2ED 12
#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
/* like the STRICT of WIN32, we define a pointer that cannot be converted
from (void*) without cast */
typedef struct TagunzFile__ { int unused; } unzFile__;
typedef unzFile__ *unzFile;
#else
typedef voidp unzFile;
#endif
#define UNZ_OK (0)
#define UNZ_END_OF_LIST_OF_FILE (-100)
#define UNZ_ERRNO (Z_ERRNO)
#define UNZ_EOF (0)
#define UNZ_PARAMERROR (-102)
#define UNZ_BADZIPFILE (-103)
#define UNZ_INTERNALERROR (-104)
#define UNZ_CRCERROR (-105)
/* tm_unz contain date/time info */
typedef struct tm_unz_s
{
uInt tm_sec; /* seconds after the minute - [0,59] */
uInt tm_min; /* minutes after the hour - [0,59] */
uInt tm_hour; /* hours since midnight - [0,23] */
uInt tm_mday; /* day of the month - [1,31] */
uInt tm_mon; /* months since January - [0,11] */
uInt tm_year; /* years - [1980..2044] */
} tm_unz;
/* unz_global_info structure contain global data about the ZIPfile
These data comes from the end of central dir */
typedef struct unz_global_info64_s
{
ZPOS64_T number_entry; /* total number of entries in
the central dir on this disk */
uLong size_comment; /* size of the global comment of the zipfile */
} unz_global_info64;
typedef struct unz_global_info_s
{
uLong number_entry; /* total number of entries in
the central dir on this disk */
uLong size_comment; /* size of the global comment of the zipfile */
} unz_global_info;
/* unz_file_info contain information about a file in the zipfile */
typedef struct unz_file_info64_s
{
uLong version; /* version made by 2 bytes */
uLong version_needed; /* version needed to extract 2 bytes */
uLong flag; /* general purpose bit flag 2 bytes */
uLong compression_method; /* compression method 2 bytes */
uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
uLong crc; /* crc-32 4 bytes */
ZPOS64_T compressed_size; /* compressed size 8 bytes */
ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */
uLong size_filename; /* filename length 2 bytes */
uLong size_file_extra; /* extra field length 2 bytes */
uLong size_file_comment; /* file comment length 2 bytes */
uLong disk_num_start; /* disk number start 2 bytes */
uLong internal_fa; /* internal file attributes 2 bytes */
uLong external_fa; /* external file attributes 4 bytes */
tm_unz tmu_date;
} unz_file_info64;
typedef struct unz_file_info_s
{
uLong version; /* version made by 2 bytes */
uLong version_needed; /* version needed to extract 2 bytes */
uLong flag; /* general purpose bit flag 2 bytes */
uLong compression_method; /* compression method 2 bytes */
uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
uLong crc; /* crc-32 4 bytes */
uLong compressed_size; /* compressed size 4 bytes */
uLong uncompressed_size; /* uncompressed size 4 bytes */
uLong size_filename; /* filename length 2 bytes */
uLong size_file_extra; /* extra field length 2 bytes */
uLong size_file_comment; /* file comment length 2 bytes */
uLong disk_num_start; /* disk number start 2 bytes */
uLong internal_fa; /* internal file attributes 2 bytes */
uLong external_fa; /* external file attributes 4 bytes */
tm_unz tmu_date;
} unz_file_info;
extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1,
const char* fileName2,
int iCaseSensitivity));
/*
Compare two filename (fileName1,fileName2).
If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
or strcasecmp)
If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
(like 1 on Unix, 2 on Windows)
*/
extern unzFile ZEXPORT unzOpen OF((const char *path));
extern unzFile ZEXPORT unzOpen64 OF((const void *path));
/*
Open a Zip file. path contain the full pathname (by example,
on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer
"zlib/zlib113.zip".
If the zipfile cannot be opened (file don't exist or in not valid), the
return value is NULL.
Else, the return value is a unzFile Handle, usable with other function
of this unzip package.
the "64" function take a const void* pointer, because the path is just the
value passed to the open64_file_func callback.
Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path
is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char*
does not describe the reality
*/
extern unzFile ZEXPORT unzOpen2 OF((const char *path,
zlib_filefunc_def* pzlib_filefunc_def));
/*
Open a Zip file, like unzOpen, but provide a set of file low level API
for read/write the zip file (see ioapi.h)
*/
extern unzFile ZEXPORT unzOpen2_64 OF((const void *path,
zlib_filefunc64_def* pzlib_filefunc_def));
/*
Open a Zip file, like unz64Open, but provide a set of file low level API
for read/write the zip file (see ioapi.h)
*/
extern int ZEXPORT unzClose OF((unzFile file));
/*
Close a ZipFile opened with unzipOpen.
If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
return UNZ_OK if there is no problem. */
extern int ZEXPORT unzGetGlobalInfo OF((unzFile file,
unz_global_info *pglobal_info));
extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file,
unz_global_info64 *pglobal_info));
/*
Write info about the ZipFile in the *pglobal_info structure.
No preparation of the structure is needed
return UNZ_OK if there is no problem. */
extern int ZEXPORT unzGetGlobalComment OF((unzFile file,
char *szComment,
uLong uSizeBuf));
/*
Get the global comment string of the ZipFile, in the szComment buffer.
uSizeBuf is the size of the szComment buffer.
return the number of byte copied or an error code <0
*/
/***************************************************************************/
/* Unzip package allow you browse the directory of the zipfile */
extern int ZEXPORT unzGoToFirstFile OF((unzFile file));
/*
Set the current file of the zipfile to the first file.
return UNZ_OK if there is no problem
*/
extern int ZEXPORT unzGoToNextFile OF((unzFile file));
/*
Set the current file of the zipfile to the next file.
return UNZ_OK if there is no problem
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
*/
extern int ZEXPORT unzLocateFile OF((unzFile file,
const char *szFileName,
int iCaseSensitivity));
/*
Try locate the file szFileName in the zipfile.
For the iCaseSensitivity signification, see unzStringFileNameCompare
return value :
UNZ_OK if the file is found. It becomes the current file.
UNZ_END_OF_LIST_OF_FILE if the file is not found
*/
/* ****************************************** */
/* Ryan supplied functions */
/* unz_file_info contain information about a file in the zipfile */
typedef struct unz_file_pos_s
{
uLong pos_in_zip_directory; /* offset in zip file directory */
uLong num_of_file; /* # of file */
} unz_file_pos;
extern int ZEXPORT unzGetFilePos(
unzFile file,
unz_file_pos* file_pos);
extern int ZEXPORT unzGoToFilePos(
unzFile file,
unz_file_pos* file_pos);
typedef struct unz64_file_pos_s
{
ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */
ZPOS64_T num_of_file; /* # of file */
} unz64_file_pos;
extern int ZEXPORT unzGetFilePos64(
unzFile file,
unz64_file_pos* file_pos);
extern int ZEXPORT unzGoToFilePos64(
unzFile file,
const unz64_file_pos* file_pos);
/* ****************************************** */
extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file,
unz_file_info64 *pfile_info,
char *szFileName,
uLong fileNameBufferSize,
void *extraField,
uLong extraFieldBufferSize,
char *szComment,
uLong commentBufferSize));
extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file,
unz_file_info *pfile_info,
char *szFileName,
uLong fileNameBufferSize,
void *extraField,
uLong extraFieldBufferSize,
char *szComment,
uLong commentBufferSize));
/*
Get Info about the current file
if pfile_info!=NULL, the *pfile_info structure will contain somes info about
the current file
if szFileName!=NULL, the filemane string will be copied in szFileName
(fileNameBufferSize is the size of the buffer)
if extraField!=NULL, the extra field information will be copied in extraField
(extraFieldBufferSize is the size of the buffer).
This is the Central-header version of the extra field
if szComment!=NULL, the comment string of the file will be copied in szComment
(commentBufferSize is the size of the buffer)
*/
/** Addition for GDAL : START */
extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file));
/** Addition for GDAL : END */
/***************************************************************************/
/* for reading the content of the current zipfile, you can open it, read data
from it, and close it (you can close it before reading all the file)
*/
extern int ZEXPORT unzOpenCurrentFile OF((unzFile file));
/*
Open for reading data the current file in the zipfile.
If there is no error, the return value is UNZ_OK.
*/
extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file,
const char* password));
/*
Open for reading data the current file in the zipfile.
password is a crypting password
If there is no error, the return value is UNZ_OK.
*/
extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file,
int* method,
int* level,
int raw));
/*
Same than unzOpenCurrentFile, but open for read raw the file (not uncompress)
if raw==1
*method will receive method of compression, *level will receive level of
compression
note : you can set level parameter as NULL (if you did not want known level,
but you CANNOT set method parameter as NULL
*/
extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file,
int* method,
int* level,
int raw,
const char* password));
/*
Same than unzOpenCurrentFile, but open for read raw the file (not uncompress)
if raw==1
*method will receive method of compression, *level will receive level of
compression
note : you can set level parameter as NULL (if you did not want known level,
but you CANNOT set method parameter as NULL
*/
extern int ZEXPORT unzCloseCurrentFile OF((unzFile file));
/*
Close the file in zip opened with unzOpenCurrentFile
Return UNZ_CRCERROR if all the file was read but the CRC is not good
*/
extern int ZEXPORT unzReadCurrentFile OF((unzFile file,
voidp buf,
unsigned len));
/*
Read bytes from the current file (opened by unzOpenCurrentFile)
buf contain buffer where data must be copied
len the size of buf.
return the number of byte copied if somes bytes are copied
return 0 if the end of file was reached
return <0 with error code if there is an error
(UNZ_ERRNO for IO error, or zLib error for uncompress error)
*/
extern z_off_t ZEXPORT unztell OF((unzFile file));
extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file));
/*
Give the current position in uncompressed data
*/
extern int ZEXPORT unzeof OF((unzFile file));
/*
return 1 if the end of file was reached, 0 elsewhere
*/
extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file,
voidp buf,
unsigned len));
/*
Read extra field from the current file (opened by unzOpenCurrentFile)
This is the local-header version of the extra field (sometimes, there is
more info in the local-header version than in the central-header)
if buf==NULL, it return the size of the local extra field
if buf!=NULL, len is the size of the buffer, the extra header is copied in
buf.
the return value is the number of bytes copied in buf, or (if <0)
the error code
*/
/***************************************************************************/
/* Get the current file offset */
extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file);
extern uLong ZEXPORT unzGetOffset (unzFile file);
/* Set the current file offset */
extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos);
extern int ZEXPORT unzSetOffset (unzFile file, uLong pos);
#ifdef __cplusplus
}
#endif
#endif /* _unz64_H */

1287
Pkgs/wglew.h Normal file

File diff suppressed because it is too large Load diff

332
ROMLoad.cpp Normal file
View file

@ -0,0 +1,332 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* ROMLoad.cpp
*
* ROM loading functions.
*/
#include <new>
#include <string.h>
#include "Supermodel.h"
#include "Pkgs/unzip.h"
/*
* CopyRegion(dest, destOffset, destSize, src, srcSize):
*
* Repeatedly mirror (copy) to destination from source until destination is
* filled.
*
* Parameters:
* dest Destination region.
* destOffset Offset within destination to begin mirroring to.
* destSize Size in bytes of destination region.
* src Source region to copy from.
* srcSize Size of region to copy from.
*/
void CopyRegion(UINT8 *dest, unsigned destOffset, unsigned destSize, UINT8 *src, unsigned srcSize)
{
while (destOffset < destSize)
{
// If we'll overrun the destination, trim the copy length
if ((destOffset+srcSize) >= destSize)
srcSize = destSize-destOffset;
// Copy!
memcpy(&dest[destOffset], src, srcSize);
destOffset += srcSize;
}
}
// Search for a ROM within a single game based on its CRC
static BOOL FindROMByCRCInGame(const struct GameInfo **gamePtr, int *romIdxPtr, const struct GameInfo *Game, UINT32 crc)
{
unsigned j;
for (j = 0; Game->ROM[j].region != NULL; j++)
{
if (crc == Game->ROM[j].crc) // found it!
{
*gamePtr = Game;
*romIdxPtr = j;
return OKAY;
}
}
return FAIL;
}
// Search for a ROM in the complete game list based on CRC32 and return its GameInfo and ROMInfo entries
static BOOL FindROMByCRC(const struct GameInfo **gamePtr, int *romIdxPtr, const struct GameInfo *GameList, const struct GameInfo *TryGame, UINT32 crc)
{
unsigned i;
if (TryGame != NULL)
{
if (FindROMByCRCInGame(gamePtr, romIdxPtr, TryGame, crc) == OKAY)
return OKAY;
}
for (i = 0; GameList[i].title != NULL; i++)
{
if (FindROMByCRCInGame(gamePtr, romIdxPtr, &(GameList[i]), crc) == OKAY)
return OKAY;
}
return FAIL;
}
static void ByteSwap(UINT8 *buf, unsigned size)
{
unsigned i;
UINT8 x;
for (i = 0; i < size; i += 2)
{
x = buf[i+0];
buf[i+0] = buf[i+1];
buf[i+1] = x;
}
}
// Load a single ROM file
static BOOL LoadROM(UINT8 *buf, unsigned bufSize, const struct ROMMap *Map, const struct ROMInfo *ROM, unzFile zf, const char *zipFile, BOOL loadAll)
{
char file[2048+1];
int err, bytes;
unz_file_info fileInfo;
unsigned i, j, destIdx, srcIdx;
// Read the file into the buffer
err = unzGetCurrentFileInfo(zf, &fileInfo, file, 2048, NULL, 0, NULL, 0);
if (err != UNZ_OK)
return ErrorLog("Unable to extract a file name from %s.", zipFile);
if (fileInfo.uncompressed_size != ROM->fileSize)
return ErrorLog("%s in %s is not the correct size (must be %d bytes).", file, zipFile, ROM->fileSize);
err = unzOpenCurrentFile(zf);
if (UNZ_OK != err)
return ErrorLog("Unable to read %s from %s.", file, zipFile);
bytes = unzReadCurrentFile(zf, buf, bufSize);
if (bytes != ROM->fileSize)
{
unzCloseCurrentFile(zf);
return ErrorLog("Unable to read %s from %s.", file, zipFile);
}
err = unzCloseCurrentFile(zf);
if (UNZ_CRCERROR == err)
ErrorLog("CRC error reading %s from %s. File may be corrupt.", file, zipFile);
// Byte swap
if (ROM->byteSwap)
ByteSwap(buf, ROM->fileSize);
// Find out how to map the ROM and do it
for (i = 0; Map[i].region != NULL; i++)
{
if (!strcmp(Map[i].region, ROM->region))
{
destIdx = ROM->offset;
for (srcIdx = 0; srcIdx < ROM->fileSize; )
{
for (j = 0; j < ROM->groupSize; j++)
Map[i].ptr[destIdx+j] = buf[srcIdx++];
destIdx += ROM->stride;
}
return OKAY;
}
}
if (loadAll) // need to load all ROMs, so there should be no unmapped regions
return ErrorLog("%s:%d: No mapping for \"%s\".", __FILE__, __LINE__, ROM->region);
else
return OKAY;
}
/*
* LoadROMSetFromZIPFile(Map, GameList, zipFile):
*
* Loads a complete ROM set from a ZIP archive. Automatically detects the game.
* If multiple games exist within the archive, an error will be printed and all
* but the first detected game will be ignored.
*
* Parameters:
* Map A list of pointers to the memory buffers for each ROM
* region.
* GameList List of all supported games and their ROMs.
* zipFile ZIP file to load from.
* loadAll If true, will check to ensure all ROMs were loaded.
* Otherwise, omits this check and loads only specified
* regions.
*
* Returns:
* Pointer to GameInfo struct for loaded game if successful, NULL
* otherwise. Prints errors.
*/
const struct GameInfo * LoadROMSetFromZIPFile(const struct ROMMap *Map, const struct GameInfo *GameList, const char *zipFile, BOOL loadAll)
{
unzFile zf;
unz_file_info fileInfo;
const struct GameInfo *Game = NULL, *CurGame;
int romIdx; // index within Game->ROM
unsigned romsFound[sizeof(Game->ROM)/sizeof(struct ROMInfo)], numROMs;
int err;
unsigned i, n, maxSize;
BOOL multipleGameError = FALSE;
UINT8 *buf;
// Try to open file
zf = unzOpen(zipFile);
if (NULL == zf)
{
ErrorLog("Unable to open %s.", zipFile);
return NULL;
}
// Check ROMs: scan ZIP file for first known ROM and check to ensure all ROMs are present
memset(romsFound, 0, sizeof(romsFound));
err = unzGoToFirstFile(zf);
if (UNZ_OK != err)
{
ErrorLog("Unable to read the contents of %s (code %X)", zipFile, err);
return NULL;
}
for (; err != UNZ_END_OF_LIST_OF_FILE; err = unzGoToNextFile(zf))
{
// Identify the file we're looking at
err = unzGetCurrentFileInfo(zf, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK)
continue;
if (OKAY != FindROMByCRC(&CurGame, &romIdx, GameList, Game, fileInfo.crc))
continue;
if (Game == NULL) // this is the first game we've identified within the ZIP
{
Game = CurGame;
DebugLog("%ROM set identified: %s (%s), %s\n", Game->id, Game->title, zipFile);
}
else
{
if (CurGame != Game)
{
DebugLog("%s also contains: %s (%s)\n", zipFile, CurGame->id, CurGame->title);
if (multipleGameError == FALSE) // only warn about this once
{
ErrorLog("Multiple games were found in %s; loading \"%s\".", zipFile, Game->title);
multipleGameError = TRUE;
}
}
}
// If we have found a ROM for the correct game, mark it
if (Game == CurGame)
romsFound[romIdx] = 1;
}
if (Game == NULL)
{
ErrorLog("%s contains no supported games.", zipFile);
return NULL;
}
// Compute how many ROM files this game has
for (numROMs = 0; Game->ROM[numROMs].region != NULL; numROMs++)
;
// If not all ROMs were present, tell the user
err = OKAY;
for (i = 0; i < numROMs; i++)
{
if (romsFound[i] == 0)
err |= ErrorLog("%s (CRC=%08X) is missing from %s.", Game->ROM[i].file, Game->ROM[i].crc, zipFile);
}
if (err != OKAY)
{
unzClose(zf);
return NULL;
//return FAIL;
}
// Allocate memory for the largest ROM to load
maxSize = 0;
for (i = 0; i < numROMs; i++)
{
if (Game->ROM[i].fileSize > maxSize)
maxSize = Game->ROM[i].fileSize;
}
buf = new(std::nothrow) UINT8[maxSize];
if (NULL == buf)
{
unzClose(zf);
ErrorLog("Insufficient memory to load ROM files (%d bytes).", maxSize);
return NULL;
}
// Load ROMs
memset(romsFound, 0, sizeof(romsFound));
err = unzGoToFirstFile(zf);
if (UNZ_OK != err)
{
ErrorLog("Unable to read the contents of %s (code %X).", zipFile, err);
err = FAIL;
goto Quit;
}
for (; err != UNZ_END_OF_LIST_OF_FILE; err = unzGoToNextFile(zf))
{
err = unzGetCurrentFileInfo(zf, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if (err != UNZ_OK)
continue;
if (OKAY != FindROMByCRC(&CurGame, &romIdx, GameList, Game, fileInfo.crc))
continue;
if (CurGame == Game) // if ROM belongs to correct game
{
if (OKAY == LoadROM(buf, maxSize, Map, &Game->ROM[romIdx], zf, zipFile, loadAll))
romsFound[romIdx] = 1; // success! mark as loaded
}
}
// Ensure all ROMs were loaded
if (loadAll)
{
n = 0;
for (i = 0; i < numROMs; i++)
{
if (romsFound[i])
++n;
else
ErrorLog("Failed to load %s (CRC=%08X) from %s.", Game->ROM[i].file, Game->ROM[i].crc, zipFile);
}
if (n < numROMs)
err = FAIL;
else
err = OKAY;
}
else
err = OKAY;
Quit:
unzClose(zf);
delete [] buf;
return (err == OKAY) ? Game : NULL;
}

118
ROMLoad.h Normal file
View file

@ -0,0 +1,118 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* ROMLoad.h
*
* Header file for ROM loading functions.
*/
#ifndef INCLUDED_ROMLOAD_H
#define INCLUDED_ROMLOAD_H
/******************************************************************************
Data Structures
******************************************************************************/
/*
* ROMInfo:
*
* Describes a single ROM file.
*/
struct ROMInfo
{
// Function
const char *region; // ROM region identifier (used as a key to search ROMMap)
// Information used to identify files
const char *file; // file name
UINT32 crc; // CRC-32 checksum (same as zip format)
unsigned fileSize; // file size in bytes (must be the same as all other ROMs with same region ID)
// Interleaving information
unsigned groupSize; // number of consecutive bytes to fetch each time (groupSize%2 must = 0, must be consistent for region)
unsigned offset; // starting offset within ROM region
unsigned stride; // number of bytes to skip before loading next group of bytes from file (must be >= groupSize)
BOOL byteSwap; // swap every pair of bytes if true
};
/*
* ROMMap:
*
* Describes how to map ROM regions (where to load them). This structure is
* assembled after memory allocation is completed and tells the ROM loading
* functions where to load files by matching region identifiers with memory
* pointers.
*
* Both pointers must be set to NULL to terminate an array of ROM maps.
*/
struct ROMMap
{
const char *region; // ROM region identifier
UINT8 *ptr; // pointer to memory region
};
/******************************************************************************
Functions
******************************************************************************/
/*
* CopyRegion(dest, destOffset, destSize, src, srcSize):
*
* Repeatedly mirror (copy) to destination from source until destination is
* filled.
*
* Parameters:
* dest Destination region.
* destOffset Offset within destination to begin mirroring to.
* destSize Size in bytes of destination region.
* src Source region to copy from.
* srcSize Size of region to copy from.
*/
extern void CopyRegion(UINT8 *dest, unsigned destOffset, unsigned destSize, UINT8 *src, unsigned srcSize);
/*
* LoadROMSetFromZIPFile(Map, GameList, zipFile):
*
* Loads a complete ROM set from a zip archive. Automatically detects the game.
* If multiple games exist within the archive, an error will be printed and all
* but the first detected game will be ignored.
*
* Parameters:
* Map A list of pointers to the memory buffers for each ROM
* region.
* GameList List of all supported games and their ROMs.
* zipFile ZIP file to load from.
* loadAll If true, will check to ensure all ROMs were loaded.
* Otherwise, omits this check and loads only specified
* regions.
*
* Returns:
* Pointer to GameInfo struct for loaded game if successful, NULL
* otherwise. Prints errors.
*/
extern const struct GameInfo * LoadROMSetFromZIPFile(const struct ROMMap *Map, const struct GameInfo *GameList, const char *zipFile,
BOOL loadAll);
#endif // INCLUDED_ROMLOAD_H

2021
Sound/SCSP.cpp Normal file

File diff suppressed because it is too large Load diff

71
Sound/SCSP.h Normal file
View file

@ -0,0 +1,71 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* SCSP.h
*
* Header file defining for SCSP emulation.
*/
#ifndef INCLUDED_SCSP_H
#define INCLUDED_SCSP_H
void SCSP_w8(unsigned int addr,unsigned char val);
void SCSP_w16(unsigned int addr,unsigned short val);
void SCSP_w32(unsigned int addr,unsigned int val);
unsigned char SCSP_r8(unsigned int addr);
unsigned short SCSP_r16(unsigned int addr);
unsigned int SCSP_r32(unsigned int addr);
void SCSP_SetCB(int (*Run68k)(int cycles),void (*Int68k)(int irq), CIRQ *ppcIRQObjectPtr, unsigned soundIRQBit);
void SCSP_Update();
void SCSP_MidiIn(unsigned char);
void SCSP_MidiOutW(unsigned char);
unsigned char SCSP_MidiOutFill();
unsigned char SCSP_MidiInFill();
void SCSP_CpuRunScanline();
unsigned char SCSP_MidiOutR();
void SCSP_Init(int n);
void SCSP_SetRAM(int n,unsigned char *r);
void SCSP_RTECheck();
int SCSP_IRQCB(int);
void SCSP_Master_w8(unsigned int addr,unsigned char val);
void SCSP_Master_w16(unsigned int addr,unsigned short val);
void SCSP_Master_w32(unsigned int addr,unsigned int val);
void SCSP_Slave_w8(unsigned int addr,unsigned char val);
void SCSP_Slave_w16(unsigned int addr,unsigned short val);
void SCSP_Slave_w32(unsigned int addr,unsigned int val);
unsigned char SCSP_Master_r8(unsigned int addr);
unsigned short SCSP_Master_r16(unsigned int addr);
unsigned int SCSP_Master_r32(unsigned int addr);
unsigned char SCSP_Slave_r8(unsigned int addr);
unsigned short SCSP_Slave_r16(unsigned int addr);
unsigned int SCSP_Slave_r32(unsigned int addr);
// Supermodel interface functions
void SCSP_WriteMIDICtrlPort(UINT8 data);
void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, int bufferLength);
void SCSP_Deinit(void);
#endif // INCLUDED_SCSP_H

1101
Sound/SCSPDSP.cpp Normal file

File diff suppressed because it is too large Load diff

59
Sound/SCSPDSP.h Normal file
View file

@ -0,0 +1,59 @@
//#ifndef SCSPDSP_H
#define SCSPDSP_H
//#define DYNDSP
#define DYNOPT 1 //set to 1 to enable optimization of recompiler
//the DSP Context
struct _SCSPDSP
{
//Config
unsigned short *SCSPRAM;
unsigned int RBP; //Ring buf pointer
unsigned int RBL; //Delay ram (Ring buffer) size in words
//context
signed short COEF[64]; //16 bit signed
unsigned short MADRS[32]; //offsets (in words), 16 bit
unsigned short MPRO[128*4]; //128 steps 64 bit
signed int TEMP[128]; //TEMP regs,24 bit signed
signed int MEMS[32]; //MEMS regs,24 bit signed
unsigned int DEC;
//input
signed int MIXS[16]; //MIXS, 24 bit signed
signed short EXTS[2]; //External inputs (CDDA) 16 bit signed
//output
signed short EFREG[16]; //EFREG, 16 bit signed
bool Stopped;
int LastStep;
#ifdef DYNDSP
signed int ACC; //26 bit
signed int SHIFTED; //24 bit
signed int X; //24 bit
signed int Y; //13 bit
signed int B; //26 bit
signed int INPUTS; //24 bit
signed int MEMVAL;
signed int FRC_REG; //13 bit
signed int Y_REG; //24 bit
unsigned int ADDR;
unsigned int ADRS_REG; //13 bit
void (*DoSteps)();
#endif
};
void SCSPDSP_Init(_SCSPDSP *DSP);
void SCSPDSP_SetSample(_SCSPDSP *DSP,signed int sample,int SEL,int MXL);
void SCSPDSP_Step(_SCSPDSP *DSP);
void SCSPDSP_Start(_SCSPDSP *DSP);
//#endif

153
Sound/SCSPLFO.cpp Normal file
View file

@ -0,0 +1,153 @@
// included from SCSP.c, do not compile
//LFOs
#include <math.h>
#include <stdlib.h>
#define LFO_SHIFT 8
struct _LFO
{
unsigned short phase;
DWORD phase_step;
int *table;
int *scale;
};
#define LFIX(v) ((unsigned int) ((float) (1<<LFO_SHIFT)*(v)))
//Convert DB to multiply amplitude
#define DB(v) LFIX(pow(10.0,(float) (v)/20.0))
//Convert cents to step increment
#define CENTS(v) LFIX(pow(2.0,(float) (v)/1200.0))
static int PLFO_TRI[256],PLFO_SQR[256],PLFO_SAW[256],PLFO_NOI[256];
static int ALFO_TRI[256],ALFO_SQR[256],ALFO_SAW[256],ALFO_NOI[256];
static float LFOFreq[32]={0.17f,0.19f,0.23f,0.27f,0.34f,0.39f,0.45f,0.55f,0.68f,0.78f,0.92f,1.10f,1.39f,1.60f,1.87f,2.27f,
2.87f,3.31f,3.92f,4.79f,6.15f,7.18f,8.60f,10.8f,14.4f,17.2f,21.5f,28.7f,43.1f,57.4f,86.1f,172.3f};
static float ASCALE[8]={0.0f,0.4f,0.8f,1.5f,3.0f,6.0f,12.0f,24.0f};
static float PSCALE[8]={0.0f,7.0f,13.5f,27.0f,55.0f,112.0f,230.0f,494.0f};
static int PSCALES[8][256];
static int ASCALES[8][256];
void LFO_Init()
{
int i;
for(i=0;i<256;++i)
{
int a,p;
float TL;
//Saw
a=255-i;
if(i<128)
p=i;
else
p=255-i;
ALFO_SAW[i]=a;
PLFO_SAW[i]=p;
//Square
if(i<128)
{
a=255;
p=127;
}
else
{
a=0;
p=-128;
}
ALFO_SQR[i]=a;
PLFO_SQR[i]=p;
//Tri
if(i<128)
a=255-(i*2);
else
a=(i*2)-256;
if(i<64)
p=i*2;
else if(i<128)
p=255-i*2;
else if(i<192)
p=256-i*2;
else
p=i*2-511;
ALFO_TRI[i]=a;
PLFO_TRI[i]=p;
//noise
//a=lfo_noise[i];
a=rand()&0xff;
p=128-a;
ALFO_NOI[i]=a;
PLFO_NOI[i]=p;
}
for(int s=0;s<8;++s)
{
float limit=PSCALE[s];
for(i=-128;i<128;++i)
{
PSCALES[s][i+128]=CENTS(((limit*((float) i))/128.0));
}
limit=-ASCALE[s];
for(i=0;i<256;++i)
{
ASCALES[s][i]=DB(((limit*(float) i)/256.0));
}
}
}
signed int inline PLFO_Step(_LFO *LFO)
{
int p;
LFO->phase+=LFO->phase_step;
#if LFO_SHIFT!=8
LFO->phase&=(1<<(LFO_SHIFT+8))-1;
#endif
p=LFO->table[LFO->phase>>LFO_SHIFT];
p=LFO->scale[p+128];
return p<<(SHIFT-LFO_SHIFT);
}
signed int inline ALFO_Step(_LFO *LFO)
{
int p;
LFO->phase+=LFO->phase_step;
#if LFO_SHIFT!=8
LFO->phase&=(1<<(LFO_SHIFT+8))-1;
#endif
p=LFO->table[LFO->phase>>LFO_SHIFT];
p=LFO->scale[p];
return p<<(SHIFT-LFO_SHIFT);
}
void LFO_ComputeStep(_LFO *LFO,DWORD LFOF,DWORD LFOWS,DWORD LFOS,int ALFO)
{
float step=(float) LFOFreq[LFOF]*256.0f/(float) srate;
LFO->phase_step=(unsigned int) ((float) (1<<LFO_SHIFT)*step);
if(ALFO)
{
switch(LFOWS)
{
case 0: LFO->table=ALFO_SAW; break;
case 1: LFO->table=ALFO_SQR; break;
case 2: LFO->table=ALFO_TRI; break;
case 3: LFO->table=ALFO_NOI; break;
}
LFO->scale=ASCALES[LFOS];
}
else
{
switch(LFOWS)
{
case 0: LFO->table=PLFO_SAW; break;
case 1: LFO->table=PLFO_SQR; break;
case 2: LFO->table=PLFO_TRI; break;
case 3: LFO->table=PLFO_NOI; break;
}
LFO->scale=PSCALES[LFOS];
}
}

188
Supermodel.h Normal file
View file

@ -0,0 +1,188 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Supermodel.h
*
* Program-wide header file.
*/
#ifndef INCLUDED_SUPERMODEL_H
#define INCLUDED_SUPERMODEL_H
/******************************************************************************
Program-Wide Definitions
******************************************************************************/
#define SUPERMODEL_VERSION "0.2-WIP" // version string
/******************************************************************************
OS-Dependent (OSD) Items
Everything here must be provided by the OSD layer. Include files should be
located in the OSD directories for each port.
******************************************************************************/
// stricmp() is non-standard, apparently...
#ifdef _MSC_VER // MS VisualC++
#define stricmp _stricmp
#else // assume GCC
#define stricmp strcasecmp
#endif
/*
* Fundamental Data Types:
*
* BOOL Boolean (w/ TRUE = FAIL = 1, OKAY = FALSE = 0).
* UINT64 Unsigned 64-bit integer.
* INT64 Signed 64-bit integer.
* UINT32 Unsigned 32-bit integer.
* INT32 Signed 32-bit integer.
* UINT16 Unsigned 16-bit integer.
* INT16 Signed 16-bit integer.
* UINT8 Unsigned 8-bit integer.
* INT8 Signed 8-bit integer.
* FLOAT32 Single-precision, 32-bit floating point number.
* FLOAT64 Double-precision, 64-bit floating point number.
*/
#include "Types.h"
/*
* Error and Debug Logging
*/
/*
* DebugLog(fmt, ...):
*
* Prints debugging information. The OSD layer may choose to print this to a
* log file, the screen, neither, or both. Newlines and other formatting codes
* must be explicitly included.
*
* Parameters:
* fmt A format string (the same as printf()).
* ... Variable number of arguments, as required by format string.
*/
extern void DebugLog(const char *fmt, ...);
/*
* ErrorLog(fmt, ...):
*
* Prints error information. Errors need not require program termination and
* may simply be informative warnings to the user. Newlines should not be
* included in the format string -- they are automatically added at the end of
* a line.
*
* Parameters:
* fmt A format string (the same as printf()).
* ... Variable number of arguments, as required by format string.
*
* Returns:
* Must always return FAIL.
*/
extern BOOL ErrorLog(const char *fmt, ...);
/*
* InfoLog(fmt, ...);
*
* Prints information to the error log file but does not print to stderr. This
* is useful for logging non-error information. Newlines are automatically
* appended.
*
* Parameters:
* fmt Format string (same as printf()).
* ... Variable number of arguments as required by format string.
*/
extern void InfoLog(const char *fmt, ...);
/******************************************************************************
Header Files
All primary header files for modules used throughout Supermodel are included
here, except for external packages and APIs.
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Games.h"
#include "ROMLoad.h"
#include "INIFile.h"
#include "BlockFile.h"
#include "Graphics/Render2D.h"
#include "Graphics/Render3D.h"
#include "Graphics/Shader.h"
#include "CPU/PowerPC/ppc.h"
#include "CPU/PowerPC/PPCDisasm.h"
#ifdef SUPERMODEL_SOUND
#include "CPU/68K/Turbo68K.h"
#endif
#include "Model3/Bus.h"
#include "Model3/IRQ.h"
#include "Model3/PCI.h"
#include "Model3/53C810.h"
#include "Model3/MPC10x.h"
#include "Model3/RTC72421.h"
#include "Model3/93C46.h"
#include "Model3/TileGen.h"
#include "Model3/Real3D.h"
#include "Sound/SCSP.h"
#include "Model3/SoundBoard.h"
#include "Model3/Model3.h"
#include "Inputs/Input.h"
#include "Inputs/Inputs.h"
#include "Inputs/InputSource.h"
#include "Inputs/InputSystem.h"
#include "Inputs/InputTypes.h"
#include "Inputs/MultiInputSource.h"
/******************************************************************************
Helpful Macros and Inlines
******************************************************************************/
/*
* FLIPENDIAN16(data):
* FLIPENDIAN32(data):
*
* Flips the endianness of the data (reverses bytes).
*
* Parameters:
* data Word or half-word to flip.
*
* Returns:
* Flipped word.
*/
static inline UINT16 FLIPENDIAN16(UINT16 d)
{
return(((d >> 8) & 0x00FF) |
((d << 8) & 0xFF00));
}
static inline UINT32 FLIPENDIAN32(UINT32 d)
{
return(((d >> 24) & 0x000000FF) |
((d >> 8) & 0x0000FF00) |
((d << 8) & 0x00FF0000) |
((d << 24) & 0xFF000000));
}
#endif // INCLUDED_SUPERMODEL_H