quicknes import @ d2901940e566841c7c35dd92ad7556fb59c753a4 from https://github.com/kode54/QuickNES_Core.git
only changes: add one pair of missing parens (https://github.com/kode54/QuickNES_Core/issues/3) and commented out HAVE_ZLIB_H also includes new vs10 solution which may or may not build
This commit is contained in:
parent
913760289d
commit
ad0faa2a42
|
@ -0,0 +1,77 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Binary_Extractor.h"
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// TODO: could close file once data has been read into memory
|
||||
|
||||
static File_Extractor* new_binary()
|
||||
{
|
||||
return BLARGG_NEW Binary_Extractor;
|
||||
}
|
||||
|
||||
fex_type_t_ const fex_bin_type [1] = {{
|
||||
"",
|
||||
&new_binary,
|
||||
"file",
|
||||
NULL
|
||||
}};
|
||||
|
||||
Binary_Extractor::Binary_Extractor() :
|
||||
File_Extractor( fex_bin_type )
|
||||
{ }
|
||||
|
||||
Binary_Extractor::~Binary_Extractor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Binary_Extractor::open_path_v()
|
||||
{
|
||||
set_name( arc_path() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Binary_Extractor::open_v()
|
||||
{
|
||||
set_name( arc_path() );
|
||||
set_info( arc().remain(), 0, 0 );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Binary_Extractor::close_v()
|
||||
{ }
|
||||
|
||||
blargg_err_t Binary_Extractor::next_v()
|
||||
{
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Binary_Extractor::rewind_v()
|
||||
{
|
||||
return open_path_v();
|
||||
}
|
||||
|
||||
blargg_err_t Binary_Extractor::stat_v()
|
||||
{
|
||||
RETURN_ERR( open_arc_file() );
|
||||
RETURN_ERR( arc().seek( 0 ) );
|
||||
return open_v();
|
||||
}
|
||||
|
||||
blargg_err_t Binary_Extractor::extract_v( void* p, int n )
|
||||
{
|
||||
return arc().read( p, n );
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Presents a single file as an "archive" of just that file.
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BINARY_EXTRACTOR_H
|
||||
#define BINARY_EXTRACTOR_H
|
||||
|
||||
#include "File_Extractor.h"
|
||||
|
||||
class Binary_Extractor : public File_Extractor {
|
||||
public:
|
||||
Binary_Extractor();
|
||||
virtual ~Binary_Extractor();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t open_path_v();
|
||||
virtual blargg_err_t open_v();
|
||||
virtual void close_v();
|
||||
|
||||
virtual blargg_err_t next_v();
|
||||
virtual blargg_err_t rewind_v();
|
||||
|
||||
virtual blargg_err_t stat_v();
|
||||
virtual blargg_err_t extract_v( void*, int );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,774 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Data_Reader.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// Data_Reader
|
||||
|
||||
blargg_err_t Data_Reader::read( void* p, int n )
|
||||
{
|
||||
assert( n >= 0 );
|
||||
|
||||
if ( n < 0 )
|
||||
return blargg_err_caller;
|
||||
|
||||
if ( n <= 0 )
|
||||
return blargg_ok;
|
||||
|
||||
if ( n > remain() )
|
||||
return blargg_err_file_eof;
|
||||
|
||||
blargg_err_t err = read_v( p, n );
|
||||
if ( !err )
|
||||
remain_ -= n;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Data_Reader::read_avail( void* p, int* n_ )
|
||||
{
|
||||
assert( *n_ >= 0 );
|
||||
|
||||
int n = min( (BOOST::uint64_t)(*n_), remain() );
|
||||
*n_ = 0;
|
||||
|
||||
if ( n < 0 )
|
||||
return blargg_err_caller;
|
||||
|
||||
if ( n <= 0 )
|
||||
return blargg_ok;
|
||||
|
||||
blargg_err_t err = read_v( p, n );
|
||||
if ( !err )
|
||||
{
|
||||
remain_ -= n;
|
||||
*n_ = n;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Data_Reader::read_avail( void* p, long* n )
|
||||
{
|
||||
int i = STATIC_CAST(int, *n);
|
||||
blargg_err_t err = read_avail( p, &i );
|
||||
*n = i;
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Data_Reader::skip_v( int count )
|
||||
{
|
||||
char buf [512];
|
||||
while ( count )
|
||||
{
|
||||
int n = min( count, (int) sizeof buf );
|
||||
count -= n;
|
||||
RETURN_ERR( read_v( buf, n ) );
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Data_Reader::skip( int n )
|
||||
{
|
||||
assert( n >= 0 );
|
||||
|
||||
if ( n < 0 )
|
||||
return blargg_err_caller;
|
||||
|
||||
if ( n <= 0 )
|
||||
return blargg_ok;
|
||||
|
||||
if ( n > remain() )
|
||||
return blargg_err_file_eof;
|
||||
|
||||
blargg_err_t err = skip_v( n );
|
||||
if ( !err )
|
||||
remain_ -= n;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
// File_Reader
|
||||
|
||||
blargg_err_t File_Reader::seek( BOOST::uint64_t n )
|
||||
{
|
||||
assert( n >= 0 );
|
||||
|
||||
if ( n < 0 )
|
||||
return blargg_err_caller;
|
||||
|
||||
if ( n == tell() )
|
||||
return blargg_ok;
|
||||
|
||||
if ( n > size() )
|
||||
return blargg_err_file_eof;
|
||||
|
||||
blargg_err_t err = seek_v( n );
|
||||
if ( !err )
|
||||
set_tell( n );
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Reader::skip_v( BOOST::uint64_t n )
|
||||
{
|
||||
return seek_v( tell() + n );
|
||||
}
|
||||
|
||||
|
||||
// Subset_Reader
|
||||
|
||||
Subset_Reader::Subset_Reader( Data_Reader* dr, BOOST::uint64_t size ) :
|
||||
in( dr )
|
||||
{
|
||||
set_remain( min( size, dr->remain() ) );
|
||||
}
|
||||
|
||||
blargg_err_t Subset_Reader::read_v( void* p, int s )
|
||||
{
|
||||
return in->read( p, s );
|
||||
}
|
||||
|
||||
|
||||
// Remaining_Reader
|
||||
|
||||
Remaining_Reader::Remaining_Reader( void const* h, int size, Data_Reader* r ) :
|
||||
in( r )
|
||||
{
|
||||
header = h;
|
||||
header_remain = size;
|
||||
|
||||
set_remain( size + r->remain() );
|
||||
}
|
||||
|
||||
blargg_err_t Remaining_Reader::read_v( void* out, int count )
|
||||
{
|
||||
int first = min( count, header_remain );
|
||||
if ( first )
|
||||
{
|
||||
memcpy( out, header, first );
|
||||
header = STATIC_CAST(char const*, header) + first;
|
||||
header_remain -= first;
|
||||
}
|
||||
|
||||
return in->read( STATIC_CAST(char*, out) + first, count - first );
|
||||
}
|
||||
|
||||
|
||||
// Mem_File_Reader
|
||||
|
||||
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
|
||||
begin( STATIC_CAST(const char*, p) )
|
||||
{
|
||||
set_size( s );
|
||||
}
|
||||
|
||||
blargg_err_t Mem_File_Reader::read_v( void* p, int s )
|
||||
{
|
||||
memcpy( p, begin + tell(), s );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Mem_File_Reader::seek_v( int )
|
||||
{
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
|
||||
// Callback_Reader
|
||||
|
||||
Callback_Reader::Callback_Reader( callback_t c, BOOST::uint64_t s, void* d ) :
|
||||
callback( c ),
|
||||
user_data( d )
|
||||
{
|
||||
set_remain( s );
|
||||
}
|
||||
|
||||
blargg_err_t Callback_Reader::read_v( void* out, int count )
|
||||
{
|
||||
return callback( user_data, out, count );
|
||||
}
|
||||
|
||||
|
||||
// Callback_File_Reader
|
||||
|
||||
Callback_File_Reader::Callback_File_Reader( callback_t c, BOOST::uint64_t s, void* d ) :
|
||||
callback( c ),
|
||||
user_data( d )
|
||||
{
|
||||
set_size( s );
|
||||
}
|
||||
|
||||
blargg_err_t Callback_File_Reader::read_v( void* out, int count )
|
||||
{
|
||||
return callback( user_data, out, count, tell() );
|
||||
}
|
||||
|
||||
blargg_err_t Callback_File_Reader::seek_v( int )
|
||||
{
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static const BOOST::uint8_t mask_tab[6]={0x80,0xE0,0xF0,0xF8,0xFC,0xFE};
|
||||
|
||||
static const BOOST::uint8_t val_tab[6]={0,0xC0,0xE0,0xF0,0xF8,0xFC};
|
||||
|
||||
size_t utf8_char_len_from_header( char p_c )
|
||||
{
|
||||
BOOST::uint8_t c = (BOOST::uint8_t)p_c;
|
||||
|
||||
size_t cnt = 0;
|
||||
for(;;)
|
||||
{
|
||||
if ( ( p_c & mask_tab[cnt] ) == val_tab[cnt] ) break;
|
||||
if ( ++cnt >= 6 ) return 0;
|
||||
}
|
||||
|
||||
return cnt + 1;
|
||||
}
|
||||
|
||||
size_t utf8_decode_char( const char *p_utf8, unsigned & wide, size_t mmax )
|
||||
{
|
||||
const BOOST::uint8_t * utf8 = ( const BOOST::uint8_t* )p_utf8;
|
||||
|
||||
if ( mmax == 0 )
|
||||
{
|
||||
wide = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( utf8[0] < 0x80 )
|
||||
{
|
||||
wide = utf8[0];
|
||||
return utf8[0]>0 ? 1 : 0;
|
||||
}
|
||||
if ( mmax > 6 ) mmax = 6;
|
||||
wide = 0;
|
||||
|
||||
unsigned res=0;
|
||||
unsigned n;
|
||||
unsigned cnt=0;
|
||||
for(;;)
|
||||
{
|
||||
if ( ( *utf8 & mask_tab[cnt] ) == val_tab[cnt] ) break;
|
||||
if ( ++cnt >= mmax ) return 0;
|
||||
}
|
||||
cnt++;
|
||||
|
||||
if ( cnt==2 && !( *utf8 & 0x1E ) ) return 0;
|
||||
|
||||
if ( cnt == 1 )
|
||||
res = *utf8;
|
||||
else
|
||||
res = ( 0xFF >> ( cnt + 1 ) ) & *utf8;
|
||||
|
||||
for ( n = 1; n < cnt; n++ )
|
||||
{
|
||||
if ( ( utf8[n] & 0xC0 ) != 0x80 )
|
||||
return 0;
|
||||
if ( !res && n == 2 && !( ( utf8[n] & 0x7F ) >> ( 7 - cnt ) ) )
|
||||
return 0;
|
||||
|
||||
res = ( res << 6 ) | ( utf8[n] & 0x3F );
|
||||
}
|
||||
|
||||
wide = res;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
size_t utf8_encode_char( unsigned wide, char * target )
|
||||
{
|
||||
size_t count;
|
||||
|
||||
if ( wide < 0x80 )
|
||||
count = 1;
|
||||
else if ( wide < 0x800 )
|
||||
count = 2;
|
||||
else if ( wide < 0x10000 )
|
||||
count = 3;
|
||||
else if ( wide < 0x200000 )
|
||||
count = 4;
|
||||
else if ( wide < 0x4000000 )
|
||||
count = 5;
|
||||
else if ( wide <= 0x7FFFFFFF )
|
||||
count = 6;
|
||||
else
|
||||
return 0;
|
||||
|
||||
if ( target == 0 )
|
||||
return count;
|
||||
|
||||
switch ( count )
|
||||
{
|
||||
case 6:
|
||||
target[5] = 0x80 | ( wide & 0x3F );
|
||||
wide = wide >> 6;
|
||||
wide |= 0x4000000;
|
||||
case 5:
|
||||
target[4] = 0x80 | ( wide & 0x3F );
|
||||
wide = wide >> 6;
|
||||
wide |= 0x200000;
|
||||
case 4:
|
||||
target[3] = 0x80 | ( wide & 0x3F );
|
||||
wide = wide >> 6;
|
||||
wide |= 0x10000;
|
||||
case 3:
|
||||
target[2] = 0x80 | ( wide & 0x3F );
|
||||
wide = wide >> 6;
|
||||
wide |= 0x800;
|
||||
case 2:
|
||||
target[1] = 0x80 | ( wide & 0x3F );
|
||||
wide = wide >> 6;
|
||||
wide |= 0xC0;
|
||||
case 1:
|
||||
target[0] = wide;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t utf16_encode_char( unsigned cur_wchar, blargg_wchar_t * out )
|
||||
{
|
||||
if ( cur_wchar < 0x10000 )
|
||||
{
|
||||
if ( out ) *out = (blargg_wchar_t) cur_wchar; return 1;
|
||||
}
|
||||
else if ( cur_wchar < ( 1 << 20 ) )
|
||||
{
|
||||
unsigned c = cur_wchar - 0x10000;
|
||||
//MSDN:
|
||||
//The first (high) surrogate is a 16-bit code value in the range U+D800 to U+DBFF. The second (low) surrogate is a 16-bit code value in the range U+DC00 to U+DFFF. Using surrogates, Unicode can support over one million characters. For more details about surrogates, refer to The Unicode Standard, version 2.0.
|
||||
if ( out )
|
||||
{
|
||||
out[0] = ( blargg_wchar_t )( 0xD800 | ( 0x3FF & ( c >> 10 ) ) );
|
||||
out[1] = ( blargg_wchar_t )( 0xDC00 | ( 0x3FF & c ) ) ;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( out ) *out = '?'; return 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t utf16_decode_char( const blargg_wchar_t * p_source, unsigned * p_out, size_t p_source_length )
|
||||
{
|
||||
if ( p_source_length == 0 ) return 0;
|
||||
else if ( p_source_length == 1 )
|
||||
{
|
||||
*p_out = p_source[0];
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t retval = 0;
|
||||
unsigned decoded = p_source[0];
|
||||
if ( decoded != 0 )
|
||||
{
|
||||
retval = 1;
|
||||
if ( ( decoded & 0xFC00 ) == 0xD800 )
|
||||
{
|
||||
unsigned low = p_source[1];
|
||||
if ( ( low & 0xFC00 ) == 0xDC00 )
|
||||
{
|
||||
decoded = 0x10000 + ( ( ( decoded & 0x3FF ) << 10 ) | ( low & 0x3FF ) );
|
||||
retval = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
*p_out = decoded;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts wide-character path to UTF-8. Free result with free(). Only supported on Windows.
|
||||
char* blargg_to_utf8( const blargg_wchar_t* wpath )
|
||||
{
|
||||
if ( wpath == NULL )
|
||||
return NULL;
|
||||
|
||||
size_t needed = 0;
|
||||
size_t mmax = blargg_wcslen( wpath );
|
||||
if ( mmax <= 0 )
|
||||
return NULL;
|
||||
|
||||
size_t ptr = 0;
|
||||
while ( ptr < mmax )
|
||||
{
|
||||
unsigned wide = 0;
|
||||
size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr );
|
||||
if ( !char_len ) break;
|
||||
ptr += char_len;
|
||||
needed += utf8_encode_char( wide, 0 );
|
||||
}
|
||||
if ( needed <= 0 )
|
||||
return NULL;
|
||||
|
||||
char* path = (char*) calloc( needed + 1, 1 );
|
||||
if ( path == NULL )
|
||||
return NULL;
|
||||
|
||||
ptr = 0;
|
||||
size_t actual = 0;
|
||||
while ( ptr < mmax && actual < needed )
|
||||
{
|
||||
unsigned wide = 0;
|
||||
size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr );
|
||||
if ( !char_len ) break;
|
||||
ptr += char_len;
|
||||
actual += utf8_encode_char( wide, path + actual );
|
||||
}
|
||||
|
||||
if ( actual == 0 )
|
||||
{
|
||||
free( path );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert( actual == needed );
|
||||
return path;
|
||||
}
|
||||
|
||||
// Converts UTF-8 path to wide-character. Free result with free() Only supported on Windows.
|
||||
blargg_wchar_t* blargg_to_wide( const char* path )
|
||||
{
|
||||
if ( path == NULL )
|
||||
return NULL;
|
||||
|
||||
size_t mmax = strlen( path );
|
||||
if ( mmax <= 0 )
|
||||
return NULL;
|
||||
|
||||
size_t needed = 0;
|
||||
size_t ptr = 0;
|
||||
while ( ptr < mmax )
|
||||
{
|
||||
unsigned wide = 0;
|
||||
size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr );
|
||||
if ( !char_len ) break;
|
||||
ptr += char_len;
|
||||
needed += utf16_encode_char( wide, 0 );
|
||||
}
|
||||
if ( needed <= 0 )
|
||||
return NULL;
|
||||
|
||||
blargg_wchar_t* wpath = (blargg_wchar_t*) calloc( needed + 1, sizeof *wpath );
|
||||
if ( wpath == NULL )
|
||||
return NULL;
|
||||
|
||||
ptr = 0;
|
||||
size_t actual = 0;
|
||||
while ( ptr < mmax && actual < needed )
|
||||
{
|
||||
unsigned wide = 0;
|
||||
size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr );
|
||||
if ( !char_len ) break;
|
||||
ptr += char_len;
|
||||
actual += utf16_encode_char( wide, wpath + actual );
|
||||
}
|
||||
if ( actual == 0 )
|
||||
{
|
||||
free( wpath );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert( actual == needed );
|
||||
return wpath;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static FILE* blargg_fopen( const char path [], const char mode [] )
|
||||
{
|
||||
FILE* file = NULL;
|
||||
blargg_wchar_t* wmode = NULL;
|
||||
blargg_wchar_t* wpath = NULL;
|
||||
|
||||
wpath = blargg_to_wide( path );
|
||||
if ( wpath )
|
||||
{
|
||||
wmode = blargg_to_wide( mode );
|
||||
if ( wmode )
|
||||
file = _wfopen( wpath, wmode );
|
||||
}
|
||||
|
||||
// Save and restore errno in case free() clears it
|
||||
int saved_errno = errno;
|
||||
free( wmode );
|
||||
free( wpath );
|
||||
errno = saved_errno;
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline FILE* blargg_fopen( const char path [], const char mode [] )
|
||||
{
|
||||
return fopen( path, mode );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Std_File_Reader
|
||||
|
||||
Std_File_Reader::Std_File_Reader()
|
||||
{
|
||||
file_ = NULL;
|
||||
}
|
||||
|
||||
Std_File_Reader::~Std_File_Reader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
static blargg_err_t blargg_fopen( FILE** out, const char path [] )
|
||||
{
|
||||
errno = 0;
|
||||
*out = blargg_fopen( path, "rb" );
|
||||
if ( !*out )
|
||||
{
|
||||
#ifdef ENOENT
|
||||
if ( errno == ENOENT )
|
||||
return blargg_err_file_missing;
|
||||
#endif
|
||||
#ifdef ENOMEM
|
||||
if ( errno == ENOMEM )
|
||||
return blargg_err_memory;
|
||||
#endif
|
||||
return blargg_err_file_read;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static blargg_err_t blargg_fsize( FILE* f, long* out )
|
||||
{
|
||||
if ( fseek( f, 0, SEEK_END ) )
|
||||
return blargg_err_file_io;
|
||||
|
||||
*out = ftell( f );
|
||||
if ( *out < 0 )
|
||||
return blargg_err_file_io;
|
||||
|
||||
if ( fseek( f, 0, SEEK_SET ) )
|
||||
return blargg_err_file_io;
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Std_File_Reader::open( const char path [] )
|
||||
{
|
||||
close();
|
||||
|
||||
FILE* f;
|
||||
RETURN_ERR( blargg_fopen( &f, path ) );
|
||||
|
||||
long s;
|
||||
blargg_err_t err = blargg_fsize( f, &s );
|
||||
if ( err )
|
||||
{
|
||||
fclose( f );
|
||||
return err;
|
||||
}
|
||||
|
||||
file_ = f;
|
||||
set_size( s );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Std_File_Reader::make_unbuffered()
|
||||
{
|
||||
if ( setvbuf( STATIC_CAST(FILE*, file_), NULL, _IONBF, 0 ) )
|
||||
check( false ); // shouldn't fail, but OK if it does
|
||||
}
|
||||
|
||||
blargg_err_t Std_File_Reader::read_v( void* p, int s )
|
||||
{
|
||||
if ( (size_t) s != fread( p, 1, s, STATIC_CAST(FILE*, file_) ) )
|
||||
{
|
||||
// Data_Reader's wrapper should prevent EOF
|
||||
check( !feof( STATIC_CAST(FILE*, file_) ) );
|
||||
|
||||
return blargg_err_file_io;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
#ifdef __CELLOS_LV2__
|
||||
int
|
||||
fseeko(FILE *stream, off_t pos, int whence)
|
||||
{
|
||||
return fseek(stream, (long)pos, whence);
|
||||
}
|
||||
#endif
|
||||
|
||||
blargg_err_t Std_File_Reader::seek_v( BOOST::uint64_t n )
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if ( fseek( STATIC_CAST(FILE*, file_), n, SEEK_SET ) )
|
||||
#else
|
||||
if ( fseeko( STATIC_CAST(FILE*, file_), n, SEEK_SET ) )
|
||||
#endif
|
||||
{
|
||||
// Data_Reader's wrapper should prevent EOF
|
||||
check( !feof( STATIC_CAST(FILE*, file_) ) );
|
||||
|
||||
return blargg_err_file_io;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Std_File_Reader::close()
|
||||
{
|
||||
if ( file_ )
|
||||
{
|
||||
fclose( STATIC_CAST(FILE*, file_) );
|
||||
file_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gzip_File_Reader
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
#ifdef HAVE_ZLIB_H
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
static const char* get_gzip_eof( const char path [], long* eof )
|
||||
{
|
||||
FILE* file;
|
||||
RETURN_ERR( blargg_fopen( &file, path ) );
|
||||
|
||||
int const h_size = 4;
|
||||
unsigned char h [h_size];
|
||||
|
||||
// read four bytes to ensure that we can seek to -4 later
|
||||
if ( fread( h, 1, h_size, file ) != (size_t) h_size || h[0] != 0x1F || h[1] != 0x8B )
|
||||
{
|
||||
// Not gzipped
|
||||
if ( ferror( file ) )
|
||||
return blargg_err_file_io;
|
||||
|
||||
if ( fseek( file, 0, SEEK_END ) )
|
||||
return blargg_err_file_io;
|
||||
|
||||
*eof = ftell( file );
|
||||
if ( *eof < 0 )
|
||||
return blargg_err_file_io;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gzipped; get uncompressed size from end
|
||||
if ( fseek( file, -h_size, SEEK_END ) )
|
||||
return blargg_err_file_io;
|
||||
|
||||
if ( fread( h, 1, h_size, file ) != (size_t) h_size )
|
||||
return blargg_err_file_io;
|
||||
|
||||
*eof = get_le32( h );
|
||||
}
|
||||
|
||||
if ( fclose( file ) )
|
||||
check( false );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
Gzip_File_Reader::Gzip_File_Reader()
|
||||
{
|
||||
file_ = NULL;
|
||||
}
|
||||
|
||||
Gzip_File_Reader::~Gzip_File_Reader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_File_Reader::open( const char path [] )
|
||||
{
|
||||
close();
|
||||
|
||||
long s;
|
||||
RETURN_ERR( get_gzip_eof( path, &s ) );
|
||||
|
||||
file_ = gzopen( path, "rb" );
|
||||
if ( !file_ )
|
||||
return blargg_err_file_read;
|
||||
|
||||
set_size( s );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static blargg_err_t convert_gz_error( gzFile file )
|
||||
{
|
||||
int err;
|
||||
gzerror( file, &err );
|
||||
|
||||
switch ( err )
|
||||
{
|
||||
case Z_STREAM_ERROR: break;
|
||||
case Z_DATA_ERROR: return blargg_err_file_corrupt;
|
||||
case Z_MEM_ERROR: return blargg_err_memory;
|
||||
case Z_BUF_ERROR: break;
|
||||
}
|
||||
return blargg_err_internal;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_File_Reader::read_v( void* p, int s )
|
||||
{
|
||||
int result = gzread( (gzFile) file_, p, s );
|
||||
if ( result != s )
|
||||
{
|
||||
if ( result < 0 )
|
||||
return convert_gz_error( (gzFile) file_ );
|
||||
|
||||
return blargg_err_file_corrupt;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_File_Reader::seek_v( int n )
|
||||
{
|
||||
if ( gzseek( (gzFile) file_, n, SEEK_SET ) < 0 )
|
||||
return convert_gz_error( (gzFile) file_ );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Gzip_File_Reader::close()
|
||||
{
|
||||
if ( file_ )
|
||||
{
|
||||
if ( gzclose( (gzFile) file_ ) )
|
||||
check( false );
|
||||
file_ = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,265 @@
|
|||
// Lightweight interface for reading data from byte stream
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef DATA_READER_H
|
||||
#define DATA_READER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
/* Some functions accept a long instead of int for convenience where caller has
|
||||
a long due to some other interface, and would otherwise have to get a warning,
|
||||
or cast it (and verify that it wasn't outside the range of an int).
|
||||
|
||||
To really support huge (>2GB) files, long isn't a solution, since there's no
|
||||
guarantee it's more than 32 bits. We'd need to use long long (if available), or
|
||||
something compiler-specific, and change all places file sizes or offsets are
|
||||
used. */
|
||||
|
||||
// Supports reading and finding out how many bytes are remaining
|
||||
class Data_Reader {
|
||||
public:
|
||||
|
||||
// Reads min(*n,remain()) bytes and sets *n to this number, thus trying to read more
|
||||
// tham remain() bytes doesn't result in error, just *n being set to remain().
|
||||
blargg_err_t read_avail( void* p, int* n );
|
||||
blargg_err_t read_avail( void* p, long* n );
|
||||
|
||||
// Reads exactly n bytes, or returns error if they couldn't ALL be read.
|
||||
// Reading past end of file results in blargg_err_file_eof.
|
||||
blargg_err_t read( void* p, int n );
|
||||
|
||||
// Number of bytes remaining until end of file
|
||||
BOOST::uint64_t remain() const { return remain_; }
|
||||
|
||||
// Reads and discards n bytes. Skipping past end of file results in blargg_err_file_eof.
|
||||
blargg_err_t skip( int n );
|
||||
|
||||
virtual ~Data_Reader() { }
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Data_Reader( const Data_Reader& );
|
||||
Data_Reader& operator = ( const Data_Reader& );
|
||||
|
||||
// Derived interface
|
||||
protected:
|
||||
Data_Reader() : remain_( 0 ) { }
|
||||
|
||||
// Sets remain
|
||||
void set_remain( BOOST::uint64_t n ) { assert( n >= 0 ); remain_ = n; }
|
||||
|
||||
// Do same as read(). Guaranteed that 0 < n <= remain(). Value of remain() is updated
|
||||
// AFTER this call succeeds, not before. set_remain() should NOT be called from this.
|
||||
virtual blargg_err_t read_v( void*, int n ) BLARGG_PURE( { (void)n; return blargg_ok; } )
|
||||
|
||||
// Do same as skip(). Guaranteed that 0 < n <= remain(). Default just reads data
|
||||
// and discards it. Value of remain() is updated AFTER this call succeeds, not
|
||||
// before. set_remain() should NOT be called from this.
|
||||
virtual blargg_err_t skip_v( int n );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
private:
|
||||
BOOST::uint64_t remain_;
|
||||
};
|
||||
|
||||
|
||||
// Supports seeking in addition to Data_Reader operations
|
||||
class File_Reader : public Data_Reader {
|
||||
public:
|
||||
|
||||
// Size of file
|
||||
BOOST::uint64_t size() const { return size_; }
|
||||
|
||||
// Current position in file
|
||||
BOOST::uint64_t tell() const { return size_ - remain(); }
|
||||
|
||||
// Goes to new position
|
||||
blargg_err_t seek( BOOST::uint64_t );
|
||||
|
||||
// Derived interface
|
||||
protected:
|
||||
// Sets size and resets position
|
||||
void set_size( BOOST::uint64_t n ) { size_ = n; Data_Reader::set_remain( n ); }
|
||||
void set_size( int n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); }
|
||||
void set_size( long n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); }
|
||||
|
||||
// Sets reported position
|
||||
void set_tell( BOOST::uint64_t i ) { assert( 0 <= i && i <= size_ ); Data_Reader::set_remain( size_ - i ); }
|
||||
|
||||
// Do same as seek(). Guaranteed that 0 <= n <= size(). Value of tell() is updated
|
||||
// AFTER this call succeeds, not before. set_* functions should NOT be called from this.
|
||||
virtual blargg_err_t seek_v( BOOST::uint64_t n ) BLARGG_PURE( { (void)n; return blargg_ok; } )
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
File_Reader() : size_( 0 ) { }
|
||||
|
||||
virtual blargg_err_t skip_v( BOOST::uint64_t );
|
||||
|
||||
private:
|
||||
BOOST::uint64_t size_;
|
||||
|
||||
void set_remain(); // avoid accidental use of set_remain
|
||||
};
|
||||
|
||||
|
||||
// Reads from file on disk
|
||||
class Std_File_Reader : public File_Reader {
|
||||
public:
|
||||
|
||||
// Opens file
|
||||
blargg_err_t open( const char path [] );
|
||||
|
||||
// Closes file if one was open
|
||||
void close();
|
||||
|
||||
// Switches to unbuffered mode. Useful if buffering is already being
|
||||
// done at a higher level.
|
||||
void make_unbuffered();
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Std_File_Reader();
|
||||
virtual ~Std_File_Reader();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
virtual blargg_err_t seek_v( BOOST::uint64_t );
|
||||
|
||||
private:
|
||||
void* file_;
|
||||
};
|
||||
|
||||
|
||||
// Treats range of memory as a file
|
||||
class Mem_File_Reader : public File_Reader {
|
||||
public:
|
||||
|
||||
Mem_File_Reader( const void* begin, long size );
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
virtual blargg_err_t seek_v( int );
|
||||
|
||||
private:
|
||||
const char* const begin;
|
||||
};
|
||||
|
||||
|
||||
// Allows only count bytes to be read from reader passed
|
||||
class Subset_Reader : public Data_Reader {
|
||||
public:
|
||||
|
||||
Subset_Reader( Data_Reader*, BOOST::uint64_t count );
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
|
||||
private:
|
||||
Data_Reader* const in;
|
||||
};
|
||||
|
||||
|
||||
// Joins already-read header and remaining data into original file.
|
||||
// Meant for cases where you've already read header and don't want
|
||||
// to seek and re-read data (for efficiency).
|
||||
class Remaining_Reader : public Data_Reader {
|
||||
public:
|
||||
|
||||
Remaining_Reader( void const* header, int header_size, Data_Reader* );
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
|
||||
private:
|
||||
Data_Reader* const in;
|
||||
void const* header;
|
||||
int header_remain;
|
||||
};
|
||||
|
||||
|
||||
// Invokes callback function to read data
|
||||
extern "C" { // necessary to be usable from C
|
||||
typedef const char* (*callback_reader_func_t)(
|
||||
void* user_data, // Same value passed to constructor
|
||||
void* out, // Buffer to place data into
|
||||
int count // Number of bytes to read
|
||||
);
|
||||
}
|
||||
class Callback_Reader : public Data_Reader {
|
||||
public:
|
||||
typedef callback_reader_func_t callback_t;
|
||||
Callback_Reader( callback_t, BOOST::uint64_t size, void* user_data );
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
|
||||
private:
|
||||
callback_t const callback;
|
||||
void* const user_data;
|
||||
};
|
||||
|
||||
|
||||
// Invokes callback function to read data
|
||||
extern "C" { // necessary to be usable from C
|
||||
typedef const char* (*callback_file_reader_func_t)(
|
||||
void* user_data, // Same value passed to constructor
|
||||
void* out, // Buffer to place data into
|
||||
int count, // Number of bytes to read
|
||||
BOOST::uint64_t pos // Position in file to read from
|
||||
);
|
||||
}
|
||||
class Callback_File_Reader : public File_Reader {
|
||||
public:
|
||||
typedef callback_file_reader_func_t callback_t;
|
||||
Callback_File_Reader( callback_t, BOOST::uint64_t size, void* user_data );
|
||||
|
||||
// Implementation
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
virtual blargg_err_t seek_v( int );
|
||||
|
||||
private:
|
||||
callback_t const callback;
|
||||
void* const user_data;
|
||||
};
|
||||
|
||||
|
||||
#ifdef HAVE_ZLIB_H
|
||||
|
||||
// Reads file compressed with gzip (or uncompressed)
|
||||
class Gzip_File_Reader : public File_Reader {
|
||||
public:
|
||||
|
||||
// Opens possibly gzipped file
|
||||
blargg_err_t open( const char path [] );
|
||||
|
||||
// Closes file if one was open
|
||||
void close();
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Gzip_File_Reader();
|
||||
~Gzip_File_Reader();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
virtual blargg_err_t seek_v( int );
|
||||
|
||||
private:
|
||||
// void* so "zlib.h" doesn't have to be included here
|
||||
void* file_;
|
||||
};
|
||||
#endif
|
||||
|
||||
char* blargg_to_utf8( const blargg_wchar_t* );
|
||||
blargg_wchar_t* blargg_to_wide( const char* );
|
||||
|
||||
#endif
|
|
@ -0,0 +1,341 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "File_Extractor.h"
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
File_Extractor::fex_t( fex_type_t t ) :
|
||||
type_( t )
|
||||
{
|
||||
own_file_ = NULL;
|
||||
|
||||
close_();
|
||||
}
|
||||
|
||||
// Open
|
||||
|
||||
blargg_err_t File_Extractor::set_path( const char* path )
|
||||
{
|
||||
if ( !path )
|
||||
path = "";
|
||||
|
||||
RETURN_ERR( path_.resize( strlen( path ) + 1 ) );
|
||||
memcpy( path_.begin(), path, path_.size() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::open( const char path [] )
|
||||
{
|
||||
close();
|
||||
|
||||
RETURN_ERR( set_path( path ) );
|
||||
|
||||
blargg_err_t err = open_path_v();
|
||||
if ( err )
|
||||
close();
|
||||
else
|
||||
opened_ = true;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::open_path_v()
|
||||
{
|
||||
RETURN_ERR( open_arc_file() );
|
||||
|
||||
return open_v();
|
||||
}
|
||||
|
||||
inline
|
||||
static void make_unbuffered( Std_File_Reader* r )
|
||||
{
|
||||
r->make_unbuffered();
|
||||
}
|
||||
|
||||
inline
|
||||
static void make_unbuffered( void* )
|
||||
{ }
|
||||
|
||||
blargg_err_t File_Extractor::open_arc_file( bool unbuffered )
|
||||
{
|
||||
if ( reader_ )
|
||||
return blargg_ok;
|
||||
|
||||
FEX_FILE_READER* in = BLARGG_NEW FEX_FILE_READER;
|
||||
CHECK_ALLOC( in );
|
||||
|
||||
blargg_err_t err = in->open( arc_path() );
|
||||
if ( err )
|
||||
{
|
||||
delete in;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader_ = in;
|
||||
own_file();
|
||||
if ( unbuffered )
|
||||
make_unbuffered( in );
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::open( File_Reader* input, const char* path )
|
||||
{
|
||||
close();
|
||||
|
||||
RETURN_ERR( set_path( path ) );
|
||||
|
||||
RETURN_ERR( input->seek( 0 ) );
|
||||
|
||||
reader_ = input;
|
||||
blargg_err_t err = open_v();
|
||||
if ( err )
|
||||
close();
|
||||
else
|
||||
opened_ = true;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Close
|
||||
|
||||
void File_Extractor::close()
|
||||
{
|
||||
close_v();
|
||||
close_();
|
||||
}
|
||||
|
||||
void File_Extractor::close_()
|
||||
{
|
||||
delete own_file_;
|
||||
own_file_ = NULL;
|
||||
|
||||
tell_ = 0;
|
||||
reader_ = NULL;
|
||||
opened_ = false;
|
||||
|
||||
path_.clear();
|
||||
clear_file();
|
||||
}
|
||||
|
||||
File_Extractor::~fex_t()
|
||||
{
|
||||
check( !opened() ); // fails if derived destructor didn't call close()
|
||||
|
||||
delete own_file_;
|
||||
}
|
||||
|
||||
// Scanning
|
||||
|
||||
void File_Extractor::clear_file()
|
||||
{
|
||||
name_ = NULL;
|
||||
wname_ = NULL;
|
||||
done_ = true;
|
||||
stat_called = false;
|
||||
data_ptr_ = NULL;
|
||||
|
||||
set_info( 0 );
|
||||
own_data_.clear();
|
||||
clear_file_v();
|
||||
}
|
||||
|
||||
void File_Extractor::set_name( const char new_name [], const blargg_wchar_t* new_wname )
|
||||
{
|
||||
name_ = new_name;
|
||||
wname_ = new_wname;
|
||||
done_ = false;
|
||||
}
|
||||
|
||||
void File_Extractor::set_info( BOOST::uint64_t new_size, unsigned date, unsigned crc )
|
||||
{
|
||||
size_ = new_size;
|
||||
date_ = (date != 0xFFFFFFFF ? date : 0);
|
||||
crc32_ = crc;
|
||||
set_remain( new_size );
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::next_()
|
||||
{
|
||||
tell_++;
|
||||
clear_file();
|
||||
|
||||
blargg_err_t err = next_v();
|
||||
if ( err )
|
||||
clear_file();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::next()
|
||||
{
|
||||
assert( !done() );
|
||||
return next_();
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::rewind()
|
||||
{
|
||||
assert( opened() );
|
||||
|
||||
tell_ = 0;
|
||||
clear_file();
|
||||
|
||||
blargg_err_t err = rewind_v();
|
||||
if ( err )
|
||||
clear_file();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::stat()
|
||||
{
|
||||
assert( !done() );
|
||||
|
||||
if ( !stat_called )
|
||||
{
|
||||
RETURN_ERR( stat_v() );
|
||||
stat_called = true;
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
// Tell/seek
|
||||
|
||||
int const pos_offset = 1;
|
||||
|
||||
fex_pos_t File_Extractor::tell_arc() const
|
||||
{
|
||||
assert( opened() );
|
||||
|
||||
fex_pos_t pos = tell_arc_v();
|
||||
assert( pos >= 0 );
|
||||
|
||||
return pos + pos_offset;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::seek_arc( fex_pos_t pos )
|
||||
{
|
||||
assert( opened() );
|
||||
assert( pos != 0 );
|
||||
|
||||
clear_file();
|
||||
|
||||
blargg_err_t err = seek_arc_v( pos - pos_offset );
|
||||
if ( err )
|
||||
clear_file();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
fex_pos_t File_Extractor::tell_arc_v() const
|
||||
{
|
||||
return tell_;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::seek_arc_v( fex_pos_t pos )
|
||||
{
|
||||
// >= because seeking to current file should always reset read pointer etc.
|
||||
if ( tell_ >= pos )
|
||||
RETURN_ERR( rewind() );
|
||||
|
||||
while ( tell_ < pos )
|
||||
{
|
||||
RETURN_ERR( next_() );
|
||||
|
||||
if ( done() )
|
||||
{
|
||||
assert( false );
|
||||
return blargg_err_caller;
|
||||
}
|
||||
}
|
||||
|
||||
assert( tell_ == pos );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
// Extraction
|
||||
|
||||
blargg_err_t File_Extractor::rewind_file()
|
||||
{
|
||||
RETURN_ERR( stat() );
|
||||
|
||||
if ( tell() > 0 )
|
||||
{
|
||||
if ( data_ptr_ )
|
||||
{
|
||||
set_remain( size() );
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_ERR( seek_arc( tell_arc() ) );
|
||||
RETURN_ERR( stat() );
|
||||
}
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::data( const void** data_out )
|
||||
{
|
||||
assert( !done() );
|
||||
|
||||
*data_out = NULL;
|
||||
if ( !data_ptr_ )
|
||||
{
|
||||
BOOST::uint64_t old_tell = tell();
|
||||
|
||||
RETURN_ERR( rewind_file() );
|
||||
|
||||
void const* ptr;
|
||||
RETURN_ERR( data_v( &ptr ) );
|
||||
data_ptr_ = ptr;
|
||||
|
||||
// Now that data is in memory, we can seek by simply setting remain
|
||||
set_remain( size() - old_tell );
|
||||
}
|
||||
|
||||
*data_out = data_ptr_;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::data_v( void const** out )
|
||||
{
|
||||
RETURN_ERR( own_data_.resize( size() ) );
|
||||
*out = own_data_.begin();
|
||||
|
||||
blargg_err_t err = extract_v( own_data_.begin(), own_data_.size() );
|
||||
if ( err )
|
||||
own_data_.clear();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::extract_v( void* out, int count )
|
||||
{
|
||||
void const* p;
|
||||
RETURN_ERR( data( &p ) );
|
||||
memcpy( out, STATIC_CAST(char const*,p) + (size() - remain()), count );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t File_Extractor::read_v( void* out, int count )
|
||||
{
|
||||
if ( data_ptr_ )
|
||||
return File_Extractor::extract_v( out, count );
|
||||
|
||||
return extract_v( out, count );
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
// Compressed file archive interface
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef FILE_EXTRACTOR_H
|
||||
#define FILE_EXTRACTOR_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Data_Reader.h"
|
||||
#include "fex.h"
|
||||
|
||||
struct fex_t : private Data_Reader {
|
||||
public:
|
||||
virtual ~fex_t();
|
||||
|
||||
// Open/close
|
||||
|
||||
// Opens archive from custom data source. Keeps pointer until close().
|
||||
blargg_err_t open( File_Reader* input, const char* path = NULL );
|
||||
|
||||
// Takes ownership of File_Reader* passed to open(), so that close()
|
||||
// will delete it.
|
||||
void own_file() { own_file_ = reader_; }
|
||||
|
||||
// See fex.h
|
||||
blargg_err_t open( const char path [] );
|
||||
fex_type_t type() const { return type_; }
|
||||
void close();
|
||||
|
||||
// Scanning
|
||||
|
||||
// See fex.h
|
||||
bool done() const { return done_; }
|
||||
blargg_err_t next();
|
||||
blargg_err_t rewind();
|
||||
fex_pos_t tell_arc() const;
|
||||
blargg_err_t seek_arc( fex_pos_t );
|
||||
|
||||
// Info
|
||||
|
||||
// See fex.h
|
||||
const char* name() const { return name_; }
|
||||
const blargg_wchar_t* wname() const { return wname_; }
|
||||
blargg_err_t stat();
|
||||
BOOST::uint64_t size() const { assert( stat_called ); return size_; }
|
||||
unsigned int dos_date() const { return date_; }
|
||||
unsigned int crc32() const { return crc32_; }
|
||||
|
||||
// Extraction
|
||||
|
||||
// Data_Reader to current file's data, so standard Data_Reader interface can
|
||||
// be used, rather than having to treat archives specially. stat() must have
|
||||
// been called.
|
||||
Data_Reader& reader() { assert( stat_called ); return *this; }
|
||||
|
||||
// See fex.h
|
||||
blargg_err_t data( const void** data_out );
|
||||
BOOST::uint64_t tell() const { return size_ - remain(); }
|
||||
|
||||
// Derived interface
|
||||
protected:
|
||||
|
||||
// Sets type of object
|
||||
fex_t( fex_type_t );
|
||||
|
||||
// Path to archive file, or "" if none supplied
|
||||
const char* arc_path() const { return path_.begin(); }
|
||||
|
||||
// Opens archive file if it's not already. If unbuffered is true, opens file
|
||||
// without any buffering.
|
||||
blargg_err_t open_arc_file( bool unbuffered = false );
|
||||
|
||||
// Archive file
|
||||
File_Reader& arc() const { return *reader_; }
|
||||
|
||||
// Sets current file name
|
||||
void set_name( const char name [], const blargg_wchar_t* wname = NULL );
|
||||
|
||||
// Sets current file information
|
||||
void set_info( BOOST::uint64_t size, unsigned date = 0, unsigned crc = 0 );
|
||||
|
||||
// User overrides
|
||||
|
||||
// Overrides must do indicated task. Non-pure functions have reasonable default
|
||||
// implementation. Overrides should avoid calling public functions like
|
||||
// next() and rewind().
|
||||
|
||||
// Open archive using file_path(). OK to delay actual file opening until later.
|
||||
// Default just calls open_arc_file(), then open_v().
|
||||
virtual blargg_err_t open_path_v();
|
||||
|
||||
// Open archive using file() for source data. If unsupported, return error.
|
||||
virtual blargg_err_t open_v() BLARGG_PURE( ; )
|
||||
|
||||
// Go to next file in archive and call set_name() and optionally set_info()
|
||||
virtual blargg_err_t next_v() BLARGG_PURE( ; )
|
||||
|
||||
// Go back to first file in archive
|
||||
virtual blargg_err_t rewind_v() BLARGG_PURE( ; )
|
||||
|
||||
// Close archive. Called even if open_path_v() or open_v() return unsuccessfully.
|
||||
virtual void close_v() BLARGG_PURE( ; )
|
||||
|
||||
// Clear any fields related to current file
|
||||
virtual void clear_file_v() { }
|
||||
|
||||
// Call set_info() if not already called by next_v()
|
||||
virtual blargg_err_t stat_v() { return blargg_ok; }
|
||||
|
||||
// Return value that allows later return to this file. Result must be >= 0.
|
||||
virtual fex_pos_t tell_arc_v() const;
|
||||
|
||||
// Return to previously saved position
|
||||
virtual blargg_err_t seek_arc_v( fex_pos_t );
|
||||
|
||||
// One or both of the following must be overridden
|
||||
|
||||
// Provide pointer to data for current file in archive
|
||||
virtual blargg_err_t data_v( const void** out );
|
||||
|
||||
// Extract next n bytes
|
||||
virtual blargg_err_t extract_v( void* out, int n );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
private:
|
||||
fex_type_t const type_;
|
||||
|
||||
// Archive file
|
||||
blargg_vector<char> path_;
|
||||
File_Reader* reader_;
|
||||
File_Reader* own_file_;
|
||||
bool opened_;
|
||||
|
||||
// Position in archive
|
||||
fex_pos_t tell_; // only used by default implementation of tell/seek
|
||||
bool done_;
|
||||
|
||||
// Info for current file in archive
|
||||
const char* name_;
|
||||
const blargg_wchar_t* wname_;
|
||||
unsigned date_;
|
||||
unsigned crc32_;
|
||||
BOOST::uint64_t size_;
|
||||
bool stat_called;
|
||||
|
||||
// Current file contents
|
||||
void const* data_ptr_; // NULL if not read into memory
|
||||
blargg_vector<char> own_data_;
|
||||
|
||||
bool opened() const { return opened_; }
|
||||
void clear_file();
|
||||
void close_();
|
||||
blargg_err_t set_path( const char* path );
|
||||
blargg_err_t rewind_file();
|
||||
blargg_err_t next_();
|
||||
|
||||
// Data_Reader overrides
|
||||
// TODO: override skip_v?
|
||||
virtual blargg_err_t read_v( void* out, int n );
|
||||
};
|
||||
|
||||
struct fex_type_t_
|
||||
{
|
||||
const char* extension;
|
||||
File_Extractor* (*new_fex)();
|
||||
const char* name;
|
||||
blargg_err_t (*init)(); // Called by fex_init(). Can be NULL.
|
||||
};
|
||||
|
||||
extern const fex_type_t_
|
||||
fex_7z_type [1],
|
||||
fex_gz_type [1],
|
||||
fex_rar_type [1],
|
||||
fex_zip_type [1],
|
||||
fex_bin_type [1];
|
||||
|
||||
inline blargg_err_t File_Extractor::open_v() { return blargg_ok; }
|
||||
inline blargg_err_t File_Extractor::next_v() { return blargg_ok; }
|
||||
inline blargg_err_t File_Extractor::rewind_v() { return blargg_ok; }
|
||||
inline void File_Extractor::close_v() { }
|
||||
|
||||
// Default to Std_File_Reader for archive access
|
||||
#ifndef FEX_FILE_READER
|
||||
#define FEX_FILE_READER Std_File_Reader
|
||||
#elif defined (FEX_FILE_READER_INCLUDE)
|
||||
#include FEX_FILE_READER_INCLUDE
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,98 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Gzip_Extractor.h"
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// TODO: could close file once data has been read into memory
|
||||
|
||||
static blargg_err_t init_gzip_file()
|
||||
{
|
||||
get_crc_table(); // initialize zlib's CRC-32 tables
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static File_Extractor* new_gzip()
|
||||
{
|
||||
return BLARGG_NEW Gzip_Extractor;
|
||||
}
|
||||
|
||||
fex_type_t_ const fex_gz_type [1] = {{
|
||||
".gz",
|
||||
&new_gzip,
|
||||
"gzipped file",
|
||||
&init_gzip_file
|
||||
}};
|
||||
|
||||
Gzip_Extractor::Gzip_Extractor() :
|
||||
File_Extractor( fex_gz_type )
|
||||
{ }
|
||||
|
||||
Gzip_Extractor::~Gzip_Extractor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::open_path_v()
|
||||
{
|
||||
// skip opening file
|
||||
return open_v();
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::stat_v()
|
||||
{
|
||||
RETURN_ERR( open_arc_file( true ) );
|
||||
if ( !gr.opened() || gr.tell() != 0 )
|
||||
RETURN_ERR( gr.open( &arc() ) );
|
||||
|
||||
set_info( gr.remain(), 0, gr.crc32() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::open_v()
|
||||
{
|
||||
// Remove .gz suffix
|
||||
size_t len = strlen( arc_path() );
|
||||
if ( fex_has_extension( arc_path(), ".gz" ) )
|
||||
len -= 3;
|
||||
|
||||
RETURN_ERR( name.resize( len + 1 ) );
|
||||
memcpy( name.begin(), arc_path(), name.size() );
|
||||
name [name.size() - 1] = '\0';
|
||||
|
||||
set_name( name.begin() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Gzip_Extractor::close_v()
|
||||
{
|
||||
name.clear();
|
||||
gr.close();
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::next_v()
|
||||
{
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::rewind_v()
|
||||
{
|
||||
set_name( name.begin() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Extractor::extract_v( void* p, int n )
|
||||
{
|
||||
return gr.read( p, n );
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Presents a gzipped file as an "archive" of just that file.
|
||||
// Also handles non-gzipped files.
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef GZIP_EXTRACTOR_H
|
||||
#define GZIP_EXTRACTOR_H
|
||||
|
||||
#include "File_Extractor.h"
|
||||
#include "Gzip_Reader.h"
|
||||
|
||||
class Gzip_Extractor : public File_Extractor {
|
||||
public:
|
||||
Gzip_Extractor();
|
||||
virtual ~Gzip_Extractor();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t open_path_v();
|
||||
virtual blargg_err_t open_v();
|
||||
virtual void close_v();
|
||||
|
||||
virtual blargg_err_t next_v();
|
||||
virtual blargg_err_t rewind_v();
|
||||
|
||||
virtual blargg_err_t stat_v();
|
||||
virtual blargg_err_t extract_v( void*, int );
|
||||
|
||||
private:
|
||||
Gzip_Reader gr;
|
||||
blargg_vector<char> name;
|
||||
|
||||
void set_info_();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,85 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Gzip_Reader.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Gzip_Reader::Gzip_Reader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
Gzip_Reader::~Gzip_Reader()
|
||||
{ }
|
||||
|
||||
static blargg_err_t gzip_reader_read( void* file, void* out, int* count )
|
||||
{
|
||||
return STATIC_CAST(File_Reader*,file)->read_avail( out, count );
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Reader::calc_size()
|
||||
{
|
||||
size_ = in->size();
|
||||
crc32_ = 0;
|
||||
if ( inflater.deflated() )
|
||||
{
|
||||
byte trailer [8];
|
||||
int old_pos = in->tell();
|
||||
RETURN_ERR( in->seek( size_ - sizeof trailer ) );
|
||||
RETURN_ERR( in->read( trailer, sizeof trailer ) );
|
||||
RETURN_ERR( in->seek( old_pos ) );
|
||||
crc32_ = get_le32( trailer + 0 );
|
||||
|
||||
unsigned n = get_le32( trailer + 4 );
|
||||
if ( n > INT_MAX )
|
||||
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "gzip larger than 2GB" );
|
||||
|
||||
size_ = n;
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Reader::open( File_Reader* new_in )
|
||||
{
|
||||
close();
|
||||
|
||||
in = new_in;
|
||||
RETURN_ERR( in->seek( 0 ) );
|
||||
RETURN_ERR( inflater.begin( gzip_reader_read, new_in ) );
|
||||
RETURN_ERR( inflater.set_mode( inflater.mode_auto ) );
|
||||
RETURN_ERR( calc_size() );
|
||||
set_remain( size_ );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Gzip_Reader::close()
|
||||
{
|
||||
in = NULL;
|
||||
inflater.end();
|
||||
}
|
||||
|
||||
blargg_err_t Gzip_Reader::read_v( void* out, int count )
|
||||
{
|
||||
assert( in );
|
||||
int actual = count;
|
||||
RETURN_ERR( inflater.read( out, &actual ) );
|
||||
|
||||
if ( actual != count )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Transparently decompresses gzip files, as well as uncompressed
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef GZIP_READER_H
|
||||
#define GZIP_READER_H
|
||||
|
||||
#include "Data_Reader.h"
|
||||
#include "Zlib_Inflater.h"
|
||||
|
||||
class Gzip_Reader : public Data_Reader {
|
||||
public:
|
||||
// Keeps pointer to reader until close(). If
|
||||
blargg_err_t open( File_Reader* );
|
||||
|
||||
// True if file is open
|
||||
bool opened() const { return in != NULL; }
|
||||
|
||||
// Frees memory
|
||||
void close();
|
||||
|
||||
// True if file is compressed
|
||||
bool deflated() const { return inflater.deflated(); }
|
||||
|
||||
// CRC-32 of data, of 0 if unavailable
|
||||
unsigned int crc32() const { return crc32_; }
|
||||
|
||||
// Number of bytes read since opening
|
||||
BOOST::uint64_t tell() const { return size_ - remain(); }
|
||||
|
||||
public:
|
||||
Gzip_Reader();
|
||||
virtual ~Gzip_Reader();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t read_v( void*, int );
|
||||
|
||||
private:
|
||||
File_Reader* in;
|
||||
unsigned crc32_;
|
||||
int size_;
|
||||
Zlib_Inflater inflater;
|
||||
|
||||
blargg_err_t calc_size();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,197 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
#if FEX_ENABLE_RAR
|
||||
|
||||
#include "Rar_Extractor.h"
|
||||
|
||||
/* Copyright (C) 2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
static blargg_err_t init_rar()
|
||||
{
|
||||
unrar_init();
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static File_Extractor* new_rar()
|
||||
{
|
||||
return BLARGG_NEW Rar_Extractor;
|
||||
}
|
||||
|
||||
fex_type_t_ const fex_rar_type [1] = {{
|
||||
".rar",
|
||||
&new_rar,
|
||||
"RAR archive",
|
||||
&init_rar
|
||||
}};
|
||||
|
||||
blargg_err_t Rar_Extractor::convert_err( unrar_err_t err )
|
||||
{
|
||||
blargg_err_t reader_err = reader.err;
|
||||
reader.err = blargg_ok;
|
||||
if ( reader_err )
|
||||
check( err == unrar_next_err );
|
||||
|
||||
switch ( err )
|
||||
{
|
||||
case unrar_ok: return blargg_ok;
|
||||
case unrar_err_memory: return blargg_err_memory;
|
||||
case unrar_err_open: return blargg_err_file_read;
|
||||
case unrar_err_not_arc: return blargg_err_file_type;
|
||||
case unrar_err_corrupt: return blargg_err_file_corrupt;
|
||||
case unrar_err_io: return blargg_err_file_io;
|
||||
case unrar_err_arc_eof: return blargg_err_internal;
|
||||
case unrar_err_encrypted: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR encryption not supported" );
|
||||
case unrar_err_segmented: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR segmentation not supported" );
|
||||
case unrar_err_huge: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Huge RAR files not supported" );
|
||||
case unrar_err_old_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Old RAR compression not supported" );
|
||||
case unrar_err_new_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR uses unknown newer compression" );
|
||||
case unrar_next_err: break;
|
||||
default:
|
||||
check( false ); // unhandled RAR error
|
||||
}
|
||||
|
||||
if ( reader_err )
|
||||
return reader_err;
|
||||
|
||||
check( false );
|
||||
return BLARGG_ERR( BLARGG_ERR_INTERNAL, "RAR archive" );
|
||||
}
|
||||
|
||||
static inline unrar_err_t handle_err( Rar_Extractor::read_callback_t* h, blargg_err_t err )
|
||||
{
|
||||
if ( !err )
|
||||
return unrar_ok;
|
||||
|
||||
h->err = err;
|
||||
return unrar_next_err;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
static unrar_err_t my_unrar_read( void* data, void* out, int* count, unrar_pos_t pos )
|
||||
{
|
||||
// TODO: 64-bit file support
|
||||
|
||||
Rar_Extractor::read_callback_t* h = STATIC_CAST(Rar_Extractor::read_callback_t*,data);
|
||||
if ( h->pos != pos )
|
||||
{
|
||||
blargg_err_t err = h->in->seek( pos );
|
||||
if ( err )
|
||||
return handle_err( h, err );
|
||||
|
||||
h->pos = pos;
|
||||
}
|
||||
|
||||
blargg_err_t err = h->in->read_avail( out, count );
|
||||
if ( err )
|
||||
return handle_err( h, err );
|
||||
|
||||
h->pos += *count;
|
||||
|
||||
return unrar_ok;
|
||||
}
|
||||
}
|
||||
|
||||
Rar_Extractor::Rar_Extractor() :
|
||||
File_Extractor( fex_rar_type )
|
||||
{
|
||||
unrar = NULL;
|
||||
}
|
||||
|
||||
Rar_Extractor::~Rar_Extractor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::open_v()
|
||||
{
|
||||
reader.pos = 0;
|
||||
reader.in = &arc();
|
||||
reader.err = blargg_ok;
|
||||
|
||||
RETURN_ERR( arc().seek( 0 ) );
|
||||
RETURN_ERR( convert_err( unrar_open_custom( &unrar, &my_unrar_read, &reader ) ) );
|
||||
return skip_unextractables();
|
||||
}
|
||||
|
||||
void Rar_Extractor::close_v()
|
||||
{
|
||||
unrar_close( unrar );
|
||||
|
||||
unrar = NULL;
|
||||
reader.in = NULL;
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::skip_unextractables()
|
||||
{
|
||||
while ( !unrar_done( unrar ) && unrar_try_extract( unrar ) )
|
||||
RETURN_ERR( next_raw() );
|
||||
|
||||
if ( !unrar_done( unrar ) )
|
||||
{
|
||||
unrar_info_t const* info = unrar_info( unrar );
|
||||
|
||||
set_name( info->name, (info->name_w && *info->name_w) ? info->name_w : NULL );
|
||||
set_info( info->size, info->dos_date, (info->is_crc32 ? info->crc : 0) );
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::next_raw()
|
||||
{
|
||||
return convert_err( unrar_next( unrar ) );
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::next_v()
|
||||
{
|
||||
RETURN_ERR( next_raw() );
|
||||
return skip_unextractables();
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::rewind_v()
|
||||
{
|
||||
RETURN_ERR( convert_err( unrar_rewind( unrar ) ) );
|
||||
return skip_unextractables();
|
||||
}
|
||||
|
||||
fex_pos_t Rar_Extractor::tell_arc_v() const
|
||||
{
|
||||
return unrar_tell( unrar );
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::seek_arc_v( fex_pos_t pos )
|
||||
{
|
||||
RETURN_ERR( convert_err( unrar_seek( unrar, pos ) ) );
|
||||
return skip_unextractables();
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::data_v( void const** out )
|
||||
{
|
||||
return convert_err( unrar_extract_mem( unrar, out ) );
|
||||
}
|
||||
|
||||
blargg_err_t Rar_Extractor::extract_v( void* out, int count )
|
||||
{
|
||||
// We can read entire file directly into user buffer
|
||||
if ( count == size() )
|
||||
return convert_err( unrar_extract( unrar, out, count ) );
|
||||
|
||||
// This will call data_v() and copy from that buffer for us
|
||||
return File_Extractor::extract_v( out, count );
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,43 @@
|
|||
// RAR archive extractor
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef RAR_EXTRACTOR_H
|
||||
#define RAR_EXTRACTOR_H
|
||||
|
||||
#include "File_Extractor.h"
|
||||
#include "unrar/unrar.h"
|
||||
|
||||
class Rar_Extractor : public File_Extractor {
|
||||
public:
|
||||
Rar_Extractor();
|
||||
virtual ~Rar_Extractor();
|
||||
|
||||
struct read_callback_t
|
||||
{
|
||||
const char* err;
|
||||
BOOST::uint64_t pos;
|
||||
File_Reader* in;
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t open_v();
|
||||
virtual void close_v();
|
||||
|
||||
virtual blargg_err_t next_v();
|
||||
virtual blargg_err_t rewind_v();
|
||||
virtual fex_pos_t tell_arc_v() const;
|
||||
virtual blargg_err_t seek_arc_v( fex_pos_t );
|
||||
|
||||
virtual blargg_err_t data_v( void const** );
|
||||
virtual blargg_err_t extract_v( void*, int );
|
||||
|
||||
private:
|
||||
unrar_t* unrar;
|
||||
read_callback_t reader;
|
||||
|
||||
blargg_err_t convert_err( unrar_err_t );
|
||||
blargg_err_t skip_unextractables();
|
||||
blargg_err_t next_raw();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,290 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Zip7_Extractor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "7z_C/7z.h"
|
||||
#include "7z_C/7zAlloc.h"
|
||||
#include "7z_C/7zCrc.h"
|
||||
}
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
static ISzAlloc zip7_alloc = { SzAlloc, SzFree };
|
||||
static ISzAlloc zip7_alloc_temp = { SzAllocTemp, SzFreeTemp };
|
||||
|
||||
struct Zip7_Extractor_Impl :
|
||||
ISeekInStream
|
||||
{
|
||||
CLookToRead look;
|
||||
CSzArEx db;
|
||||
|
||||
// SzExtract state
|
||||
UInt32 block_index;
|
||||
Byte* buf;
|
||||
size_t buf_size;
|
||||
|
||||
File_Reader* in;
|
||||
const char* in_err;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// 7-zip callbacks pass an ISeekInStream* for data, so we must cast it
|
||||
// back to ISeekInStream* FIRST, then cast to our Impl structure
|
||||
|
||||
static SRes zip7_read_( void* vstream, void* out, size_t* size )
|
||||
{
|
||||
assert( out && size );
|
||||
ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream);
|
||||
Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream);
|
||||
|
||||
long lsize = *size;
|
||||
blargg_err_t err = impl->in->read_avail( out, &lsize );
|
||||
if ( err )
|
||||
{
|
||||
*size = 0;
|
||||
impl->in_err = err;
|
||||
return SZ_ERROR_READ;
|
||||
}
|
||||
|
||||
*size = lsize;
|
||||
return SZ_OK;
|
||||
}
|
||||
|
||||
static SRes zip7_seek_( void* vstream, Int64* pos, ESzSeek mode )
|
||||
{
|
||||
ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream);
|
||||
Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream);
|
||||
|
||||
if ( mode == SZ_SEEK_CUR )
|
||||
{
|
||||
assert( *pos == 0 ); // only used to find the archive start position
|
||||
*pos = impl->in->tell();
|
||||
return SZ_OK;
|
||||
}
|
||||
|
||||
if ( mode == SZ_SEEK_END )
|
||||
{
|
||||
assert( *pos == 0 ); // only used to find file length
|
||||
*pos = impl->in->size();
|
||||
return SZ_OK;
|
||||
}
|
||||
|
||||
assert( mode == SZ_SEEK_SET );
|
||||
blargg_err_t err = impl->in->seek( *pos );
|
||||
if ( err )
|
||||
{
|
||||
// don't set in_err in this case, since it might be benign
|
||||
if ( err == blargg_err_file_eof )
|
||||
return SZ_ERROR_INPUT_EOF;
|
||||
|
||||
impl->in_err = err;
|
||||
return SZ_ERROR_READ;
|
||||
}
|
||||
|
||||
return SZ_OK;
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::zip7_err( int err )
|
||||
{
|
||||
// TODO: ignore in_err in some cases? unsure about which error to use
|
||||
blargg_err_t in_err = impl->in_err;
|
||||
impl->in_err = NULL;
|
||||
if ( in_err )
|
||||
{
|
||||
check( err != SZ_OK );
|
||||
return in_err;
|
||||
}
|
||||
|
||||
switch ( err )
|
||||
{
|
||||
case SZ_OK: return blargg_ok;
|
||||
case SZ_ERROR_MEM: return blargg_err_memory;
|
||||
case SZ_ERROR_READ: return blargg_err_file_io;
|
||||
case SZ_ERROR_CRC:
|
||||
case SZ_ERROR_DATA:
|
||||
case SZ_ERROR_INPUT_EOF:
|
||||
case SZ_ERROR_ARCHIVE: return blargg_err_file_corrupt;
|
||||
case SZ_ERROR_UNSUPPORTED: return blargg_err_file_feature;
|
||||
case SZ_ERROR_NO_ARCHIVE: return blargg_err_file_type;
|
||||
}
|
||||
|
||||
return blargg_err_generic;
|
||||
}
|
||||
|
||||
static blargg_err_t init_7z()
|
||||
{
|
||||
static bool inited;
|
||||
if ( !inited )
|
||||
{
|
||||
inited = true;
|
||||
CrcGenerateTable();
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static File_Extractor* new_7z()
|
||||
{
|
||||
return BLARGG_NEW Zip7_Extractor;
|
||||
}
|
||||
|
||||
fex_type_t_ const fex_7z_type [1] = {{
|
||||
".7z",
|
||||
&new_7z,
|
||||
"7-zip archive",
|
||||
&init_7z
|
||||
}};
|
||||
|
||||
Zip7_Extractor::Zip7_Extractor() :
|
||||
File_Extractor( fex_7z_type )
|
||||
{
|
||||
impl = NULL;
|
||||
}
|
||||
|
||||
Zip7_Extractor::~Zip7_Extractor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::open_v()
|
||||
{
|
||||
RETURN_ERR( init_7z() );
|
||||
|
||||
if ( !impl )
|
||||
{
|
||||
impl = (Zip7_Extractor_Impl*) malloc( sizeof *impl );
|
||||
CHECK_ALLOC( impl );
|
||||
}
|
||||
|
||||
impl->in = &arc();
|
||||
impl->block_index = (UInt32) -1;
|
||||
impl->buf = NULL;
|
||||
impl->buf_size = 0;
|
||||
|
||||
LookToRead_CreateVTable( &impl->look, false );
|
||||
impl->ISeekInStream::Read = zip7_read_;
|
||||
impl->ISeekInStream::Seek = zip7_seek_;
|
||||
impl->look.realStream = impl;
|
||||
LookToRead_Init( &impl->look );
|
||||
|
||||
SzArEx_Init( &impl->db );
|
||||
|
||||
impl->in_err = NULL;
|
||||
RETURN_ERR( zip7_err( SzArEx_Open( &impl->db, &impl->look.s,
|
||||
&zip7_alloc, &zip7_alloc_temp ) ) );
|
||||
|
||||
return seek_arc_v( 0 );
|
||||
}
|
||||
|
||||
void Zip7_Extractor::close_v()
|
||||
{
|
||||
if ( impl )
|
||||
{
|
||||
if ( impl->in )
|
||||
{
|
||||
impl->in = NULL;
|
||||
SzArEx_Free( &impl->db, &zip7_alloc );
|
||||
}
|
||||
IAlloc_Free( &zip7_alloc, impl->buf );
|
||||
free( impl );
|
||||
impl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::next_v()
|
||||
{
|
||||
while ( ++index < (int) impl->db.db.NumFiles )
|
||||
{
|
||||
CSzFileItem const& item = impl->db.db.Files [index];
|
||||
if ( !item.IsDir )
|
||||
{
|
||||
unsigned long date = 0;
|
||||
if ( item.MTimeDefined )
|
||||
{
|
||||
const UInt64 epoch = ((UInt64)0x019db1de << 32) + 0xd53e8000;
|
||||
/* 0x019db1ded53e8000ULL: 1970-01-01 00:00:00 (UTC) */
|
||||
struct tm tm;
|
||||
|
||||
UInt64 time = ((UInt64)item.MTime.High << 32) + item.MTime.Low - epoch;
|
||||
time /= 1000000;
|
||||
|
||||
time_t _time = time;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
localtime_s( &tm, &_time );
|
||||
#else
|
||||
localtime_r( &_time, &tm );
|
||||
#endif
|
||||
|
||||
date = ( tm.tm_sec >> 1 ) & 0x1F |
|
||||
(( tm.tm_min & 0x3F ) << 5 ) |
|
||||
(( tm.tm_hour & 0x1F ) << 11 ) |
|
||||
(( tm.tm_mday & 0x1F ) << 16 ) |
|
||||
(( ( tm.tm_mon + 1 ) & 0x0F ) << 21 ) |
|
||||
(( ( tm.tm_year - 80 ) & 0x7F ) << 25 );
|
||||
}
|
||||
|
||||
size_t name_length = SzArEx_GetFileNameUtf16( &impl->db, index, 0 );
|
||||
name16.resize( name_length );
|
||||
SzArEx_GetFileNameUtf16( &impl->db, index, ( UInt16 * ) name16.begin() );
|
||||
char * temp = blargg_to_utf8( name16.begin() );
|
||||
if ( !temp ) temp = "";
|
||||
size_t utf8_length = strlen( temp );
|
||||
name8.resize( utf8_length + 1 );
|
||||
memcpy( name8.begin(), temp, utf8_length + 1 );
|
||||
free( temp );
|
||||
set_name( name8.begin(), name16.begin() );
|
||||
set_info( item.Size, 0, (item.CrcDefined ? item.Crc : 0) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::rewind_v()
|
||||
{
|
||||
return seek_arc_v( 0 );
|
||||
}
|
||||
|
||||
fex_pos_t Zip7_Extractor::tell_arc_v() const
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::seek_arc_v( fex_pos_t pos )
|
||||
{
|
||||
assert( 0 <= pos && pos <= (int) impl->db.db.NumFiles );
|
||||
|
||||
index = pos - 1;
|
||||
return next_v();
|
||||
}
|
||||
|
||||
blargg_err_t Zip7_Extractor::data_v( void const** out )
|
||||
{
|
||||
impl->in_err = NULL;
|
||||
size_t offset = 0;
|
||||
size_t count = 0;
|
||||
RETURN_ERR( zip7_err( SzArEx_Extract( &impl->db, &impl->look.s, index,
|
||||
&impl->block_index, &impl->buf, &impl->buf_size,
|
||||
&offset, &count, &zip7_alloc, &zip7_alloc_temp ) ) );
|
||||
assert( count == (size_t) size() );
|
||||
|
||||
*out = impl->buf + offset;
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 7-zip archive extractor
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef ZIP7_EXTRACTOR_H
|
||||
#define ZIP7_EXTRACTOR_H
|
||||
|
||||
#include "File_Extractor.h"
|
||||
|
||||
struct Zip7_Extractor_Impl;
|
||||
|
||||
class Zip7_Extractor : public File_Extractor {
|
||||
public:
|
||||
Zip7_Extractor();
|
||||
virtual ~Zip7_Extractor();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t open_v();
|
||||
virtual void close_v();
|
||||
|
||||
virtual blargg_err_t next_v();
|
||||
virtual blargg_err_t rewind_v();
|
||||
virtual fex_pos_t tell_arc_v() const;
|
||||
virtual blargg_err_t seek_arc_v( fex_pos_t );
|
||||
|
||||
virtual blargg_err_t data_v( void const** out );
|
||||
|
||||
private:
|
||||
Zip7_Extractor_Impl* impl;
|
||||
int index;
|
||||
blargg_vector<char> name8;
|
||||
blargg_vector<blargg_wchar_t> name16;
|
||||
|
||||
blargg_err_t zip7_err( int err );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,390 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Zip_Extractor.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
/* To avoid copying filename string from catalog, I terminate it by modifying
|
||||
catalog data. This potentially requires moving the first byte of the type
|
||||
of the next entry elsewhere; I move it to the first byte of made_by. Kind
|
||||
of hacky, but I'd rather not have to allocate memory for a copy of it. */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
/* Reads this much from end of file when first opening. Only this much is
|
||||
searched for the end catalog entry. If whole catalog is within this data,
|
||||
nothing more needs to be read on open. */
|
||||
int const end_read_size = 8 * 1024;
|
||||
|
||||
/* Reads are are made using file offset that's a multiple of this,
|
||||
increasing performance. */
|
||||
int const disk_block_size = 4 * 1024;
|
||||
|
||||
// Read buffer used for extracting file data
|
||||
int const read_buf_size = 16 * 1024;
|
||||
|
||||
struct header_t
|
||||
{
|
||||
char type [4];
|
||||
byte vers [2];
|
||||
byte flags [2];
|
||||
byte method [2];
|
||||
byte date [4];
|
||||
byte crc [4];
|
||||
byte raw_size [4];
|
||||
byte size [4];
|
||||
byte filename_len [2];
|
||||
byte extra_len [2];
|
||||
char filename [2]; // [filename_len]
|
||||
//char extra [extra_len];
|
||||
};
|
||||
int const header_size = 30;
|
||||
|
||||
struct entry_t
|
||||
{
|
||||
char type [4];
|
||||
byte made_by [2];
|
||||
byte vers [2];
|
||||
byte flags [2];
|
||||
byte method [2];
|
||||
byte date [4];
|
||||
byte crc [4];
|
||||
byte raw_size [4];
|
||||
byte size [4];
|
||||
byte filename_len [2];
|
||||
byte extra_len [2];
|
||||
byte comment_len [2];
|
||||
byte disk [2];
|
||||
byte int_attrib [2];
|
||||
byte ext_attrib [4];
|
||||
byte file_offset [4];
|
||||
char filename [2]; // [filename_len]
|
||||
//char extra [extra_len];
|
||||
//char comment [comment_len];
|
||||
};
|
||||
int const entry_size = 46;
|
||||
|
||||
struct end_entry_t
|
||||
{
|
||||
char type [4];
|
||||
byte disk [2];
|
||||
byte first_disk [2];
|
||||
byte disk_entry_count [2];
|
||||
byte entry_count [2];
|
||||
byte dir_size [4];
|
||||
byte dir_offset [4];
|
||||
byte comment_len [2];
|
||||
char comment [2]; // [comment_len]
|
||||
};
|
||||
int const end_entry_size = 22;
|
||||
|
||||
static blargg_err_t init_zip()
|
||||
{
|
||||
get_crc_table(); // initialize zlib's CRC-32 tables
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static File_Extractor* new_zip()
|
||||
{
|
||||
return BLARGG_NEW Zip_Extractor;
|
||||
}
|
||||
|
||||
fex_type_t_ const fex_zip_type [1] = {{
|
||||
".zip",
|
||||
&new_zip,
|
||||
"ZIP archive",
|
||||
&init_zip
|
||||
}};
|
||||
|
||||
Zip_Extractor::Zip_Extractor() :
|
||||
File_Extractor( fex_zip_type )
|
||||
{
|
||||
Zip_Extractor::clear_file_v();
|
||||
|
||||
// If these fail, structures had extra padding inserted by compiler
|
||||
assert( offsetof (header_t,filename) == header_size );
|
||||
assert( offsetof (entry_t,filename) == entry_size );
|
||||
assert( offsetof (end_entry_t,comment) == end_entry_size );
|
||||
}
|
||||
|
||||
Zip_Extractor::~Zip_Extractor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::open_path_v()
|
||||
{
|
||||
RETURN_ERR( open_arc_file( true ) );
|
||||
return File_Extractor::open_path_v();
|
||||
}
|
||||
|
||||
inline
|
||||
void Zip_Extractor::reorder_entry_header( int offset )
|
||||
{
|
||||
catalog [offset + 0] = 0;
|
||||
catalog [offset + 4] = 'P';
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::open_v()
|
||||
{
|
||||
if ( arc().size() < end_entry_size )
|
||||
return blargg_err_file_type;
|
||||
|
||||
// Read final end_read_size bytes of file
|
||||
BOOST::uint64_t file_pos = max( (BOOST::uint64_t) 0, arc().size() - end_read_size );
|
||||
file_pos -= file_pos % disk_block_size;
|
||||
RETURN_ERR( catalog.resize( arc().size() - file_pos ) );
|
||||
RETURN_ERR( arc().seek( file_pos ) );
|
||||
RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) );
|
||||
|
||||
// Find end-of-catalog entry
|
||||
BOOST::uint64_t end_pos = catalog.size() - end_entry_size;
|
||||
while ( end_pos >= 0 && memcmp( &catalog [end_pos], "PK\5\6", 4 ) )
|
||||
end_pos--;
|
||||
if ( end_pos < 0 )
|
||||
return blargg_err_file_type;
|
||||
end_entry_t const& end_entry = (end_entry_t&) catalog [end_pos];
|
||||
end_pos += file_pos;
|
||||
|
||||
// some idiotic zip compressors add data to end of zip without setting comment len
|
||||
// check( arc().size() == end_pos + end_entry_size + get_le16( end_entry.comment_len ) );
|
||||
|
||||
// Find file offset of beginning of catalog
|
||||
catalog_begin = get_le32( end_entry.dir_offset );
|
||||
int catalog_size = end_pos - catalog_begin;
|
||||
if ( catalog_size < 0 )
|
||||
return blargg_err_file_corrupt;
|
||||
catalog_size += end_entry_size;
|
||||
|
||||
// See if catalog is entirely contained in bytes already read
|
||||
BOOST::uint64_t begin_offset = catalog_begin - file_pos;
|
||||
if ( begin_offset >= 0 )
|
||||
memmove( catalog.begin(), &catalog [begin_offset], catalog_size );
|
||||
|
||||
RETURN_ERR( catalog.resize( catalog_size ) );
|
||||
if ( begin_offset < 0 )
|
||||
{
|
||||
// Catalog begins before bytes read, so it needs to be read
|
||||
RETURN_ERR( arc().seek( catalog_begin ) );
|
||||
RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) );
|
||||
}
|
||||
|
||||
// First entry in catalog should be a file or end of archive
|
||||
if ( memcmp( catalog.begin(), "PK\1\2", 4 ) && memcmp( catalog.begin(), "PK\5\6", 4 ) )
|
||||
return blargg_err_file_type;
|
||||
|
||||
reorder_entry_header( 0 );
|
||||
return rewind_v();
|
||||
}
|
||||
|
||||
void Zip_Extractor::close_v()
|
||||
{
|
||||
catalog.clear();
|
||||
}
|
||||
|
||||
// Scanning
|
||||
|
||||
inline
|
||||
static bool is_normal_file( entry_t const& e, unsigned len )
|
||||
{
|
||||
int last_char = (len ? e.filename [len - 1] : '/');
|
||||
bool is_dir = (last_char == '/' || last_char == '\\');
|
||||
if ( is_dir && get_le32( e.size ) == 0 )
|
||||
return false;
|
||||
check( !is_dir );
|
||||
|
||||
// Mac OS X puts meta-information in separate files with normal extensions,
|
||||
// so they must be filtered out or caller will mistake them for normal files.
|
||||
if ( e.made_by[1] == 3 )
|
||||
{
|
||||
const char* dir = strrchr( e.filename, '/' );
|
||||
if ( dir )
|
||||
dir++;
|
||||
else
|
||||
dir = e.filename;
|
||||
|
||||
if ( *dir == '.' )
|
||||
return false;
|
||||
|
||||
if ( !strcmp( dir, "Icon\x0D" ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::update_info( bool advance_first )
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
entry_t& e = (entry_t&) catalog [catalog_pos];
|
||||
|
||||
if ( memcmp( e.type, "\0K\1\2P", 5 ) && memcmp( e.type, "PK\1\2", 4 ) )
|
||||
{
|
||||
check( !memcmp( e.type, "\0K\5\6P", 5 ) );
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned len = get_le16( e.filename_len );
|
||||
int next_offset = catalog_pos + entry_size + len + get_le16( e.extra_len ) +
|
||||
get_le16( e.comment_len );
|
||||
if ( (unsigned) next_offset > catalog.size() - end_entry_size )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
if ( catalog [next_offset] == 'P' )
|
||||
reorder_entry_header( next_offset );
|
||||
|
||||
if ( !advance_first )
|
||||
{
|
||||
e.filename [len] = 0; // terminate name
|
||||
|
||||
if ( is_normal_file( e, len ) )
|
||||
{
|
||||
set_name( e.filename );
|
||||
set_info( get_le32( e.size ), get_le32( e.date ), get_le32( e.crc ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
catalog_pos = next_offset;
|
||||
advance_first = false;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::next_v()
|
||||
{
|
||||
return update_info( true );
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::rewind_v()
|
||||
{
|
||||
return seek_arc_v( 0 );
|
||||
}
|
||||
|
||||
fex_pos_t Zip_Extractor::tell_arc_v() const
|
||||
{
|
||||
return catalog_pos;
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::seek_arc_v( fex_pos_t pos )
|
||||
{
|
||||
assert( 0 <= pos && (size_t) pos <= catalog.size() - end_entry_size );
|
||||
|
||||
catalog_pos = pos;
|
||||
return update_info( false );
|
||||
}
|
||||
|
||||
// Reading
|
||||
|
||||
void Zip_Extractor::clear_file_v()
|
||||
{
|
||||
buf.end();
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::inflater_read( void* data, void* out, int* count )
|
||||
{
|
||||
Zip_Extractor& self = *STATIC_CAST(Zip_Extractor*,data);
|
||||
|
||||
if ( *count > self.raw_remain )
|
||||
*count = self.raw_remain;
|
||||
|
||||
self.raw_remain -= *count;
|
||||
|
||||
return self.arc().read( out, *count );
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::fill_buf( int offset, int buf_size, int initial_read )
|
||||
{
|
||||
raw_remain = arc().size() - offset;
|
||||
RETURN_ERR( arc().seek( offset ) );
|
||||
return buf.begin( inflater_read, this, buf_size, initial_read );
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::first_read( int count )
|
||||
{
|
||||
entry_t const& e = (entry_t&) catalog [catalog_pos];
|
||||
|
||||
// Determine compression
|
||||
{
|
||||
int method = get_le16( e.method );
|
||||
if ( (method && method != Z_DEFLATED) || get_le16( e.vers ) > 20 )
|
||||
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "compression method" );
|
||||
file_deflated = (method != 0);
|
||||
}
|
||||
|
||||
int raw_size = get_le32( e.raw_size );
|
||||
|
||||
int file_offset = get_le32( e.file_offset );
|
||||
int align = file_offset % disk_block_size;
|
||||
{
|
||||
// read header
|
||||
int buf_size = 3 * disk_block_size - 1 + raw_size; // space for all raw data
|
||||
buf_size -= buf_size % disk_block_size;
|
||||
int initial_read = buf_size;
|
||||
if ( !file_deflated || count < size() )
|
||||
{
|
||||
buf_size = read_buf_size;
|
||||
initial_read = disk_block_size * 2;
|
||||
}
|
||||
// TODO: avoid re-reading if buffer already has data we want?
|
||||
RETURN_ERR( fill_buf( file_offset - align, buf_size, initial_read ) );
|
||||
}
|
||||
header_t const& h = (header_t&) buf.data() [align];
|
||||
if ( buf.filled() < align + header_size || memcmp( h.type, "PK\3\4", 4 ) )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
// CRCs of header and file data
|
||||
correct_crc = get_le32( h.crc );
|
||||
if ( !correct_crc )
|
||||
correct_crc = get_le32( e.crc );
|
||||
check( correct_crc == get_le32( e.crc ) ); // catalog CRC should match
|
||||
crc = ::crc32( 0, NULL, 0 );
|
||||
|
||||
// Data offset
|
||||
int data_offset = file_offset + header_size +
|
||||
get_le16( h.filename_len ) + get_le16( h.extra_len );
|
||||
if ( data_offset + raw_size > catalog_begin )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
// Refill buffer if there's lots of extra data after header
|
||||
int buf_offset = data_offset - file_offset + align;
|
||||
if ( buf_offset > buf.filled() )
|
||||
{
|
||||
// TODO: this will almost never occur, making it a good place for bugs
|
||||
buf_offset = data_offset % disk_block_size;
|
||||
RETURN_ERR( fill_buf( data_offset - buf_offset, read_buf_size, disk_block_size ) );
|
||||
}
|
||||
|
||||
raw_remain = raw_size - (buf.filled() - buf_offset);
|
||||
return buf.set_mode( (file_deflated ? buf.mode_raw_deflate : buf.mode_copy), buf_offset );
|
||||
}
|
||||
|
||||
blargg_err_t Zip_Extractor::extract_v( void* out, int count )
|
||||
{
|
||||
if ( tell() == 0 )
|
||||
RETURN_ERR( first_read( count ) );
|
||||
|
||||
int actual = count;
|
||||
RETURN_ERR( buf.read( out, &actual ) );
|
||||
if ( actual < count )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
crc = ::crc32( crc, (byte const*) out, count );
|
||||
if ( count == reader().remain() && crc != correct_crc )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// ZIP archive extractor. Only supports deflation and store (no compression).
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef ZIP_EXTRACTOR_H
|
||||
#define ZIP_EXTRACTOR_H
|
||||
|
||||
#include "File_Extractor.h"
|
||||
#include "Zlib_Inflater.h"
|
||||
|
||||
class Zip_Extractor : public File_Extractor {
|
||||
public:
|
||||
Zip_Extractor();
|
||||
virtual ~Zip_Extractor();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t open_path_v();
|
||||
virtual blargg_err_t open_v();
|
||||
virtual void close_v();
|
||||
|
||||
virtual void clear_file_v();
|
||||
virtual blargg_err_t next_v();
|
||||
virtual blargg_err_t rewind_v();
|
||||
virtual fex_pos_t tell_arc_v() const;
|
||||
virtual blargg_err_t seek_arc_v( fex_pos_t );
|
||||
|
||||
virtual blargg_err_t extract_v( void*, int );
|
||||
|
||||
private:
|
||||
blargg_vector<char> catalog;
|
||||
int catalog_begin; // offset of first catalog entry in file (to detect corruption)
|
||||
int catalog_pos; // position of current entry in catalog
|
||||
BOOST::uint64_t raw_remain; // bytes remaining to be read from zip file for current file
|
||||
unsigned crc; // ongoing CRC of extracted bytes
|
||||
unsigned correct_crc;
|
||||
bool file_deflated;
|
||||
Zlib_Inflater buf;
|
||||
|
||||
blargg_err_t fill_buf( int offset, int buf_size, int initial_read );
|
||||
blargg_err_t update_info( bool advance_first );
|
||||
blargg_err_t first_read( int count );
|
||||
void reorder_entry_header( int offset );
|
||||
static blargg_err_t inflater_read( void* data, void* out, int* count );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,257 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Zlib_Inflater.h"
|
||||
|
||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const block_size = 4096;
|
||||
|
||||
static const char* get_zlib_err( int code )
|
||||
{
|
||||
assert( code != Z_OK );
|
||||
switch ( code )
|
||||
{
|
||||
case Z_MEM_ERROR: return blargg_err_memory;
|
||||
case Z_DATA_ERROR: return blargg_err_file_corrupt;
|
||||
// TODO: handle more error codes
|
||||
}
|
||||
|
||||
const char* str = zError( code );
|
||||
if ( !str )
|
||||
str = BLARGG_ERR( BLARGG_ERR_GENERIC, "problem unzipping data" );
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void Zlib_Inflater::end()
|
||||
{
|
||||
if ( deflated_ )
|
||||
{
|
||||
deflated_ = false;
|
||||
if ( inflateEnd( &zbuf ) )
|
||||
check( false );
|
||||
}
|
||||
buf.clear();
|
||||
|
||||
static z_stream const empty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
memcpy( &zbuf, &empty, sizeof zbuf );
|
||||
}
|
||||
|
||||
Zlib_Inflater::Zlib_Inflater()
|
||||
{
|
||||
deflated_ = false;
|
||||
end(); // initialize things
|
||||
}
|
||||
|
||||
Zlib_Inflater::~Zlib_Inflater()
|
||||
{
|
||||
end();
|
||||
}
|
||||
|
||||
blargg_err_t Zlib_Inflater::fill_buf( int count )
|
||||
{
|
||||
byte* out = buf.end() - count;
|
||||
RETURN_ERR( callback( user_data, out, &count ) );
|
||||
zbuf.avail_in = count;
|
||||
zbuf.next_in = out;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Zlib_Inflater::begin( callback_t new_callback, void* new_user_data,
|
||||
int new_buf_size, int initial_read )
|
||||
{
|
||||
callback = new_callback;
|
||||
user_data = new_user_data;
|
||||
|
||||
end();
|
||||
|
||||
// TODO: decide whether using different size on alloc failure is a good idea
|
||||
//RETURN_ERR( buf.resize( new_buf_size ? new_buf_size : 4 * block_size ) );
|
||||
if ( new_buf_size && buf.resize( new_buf_size ) )
|
||||
{
|
||||
ACK_FAILURE();
|
||||
new_buf_size = 0;
|
||||
}
|
||||
|
||||
if ( !new_buf_size )
|
||||
{
|
||||
RETURN_ERR( buf.resize( 4 * block_size ) );
|
||||
initial_read = 0;
|
||||
}
|
||||
|
||||
// Fill buffer with some data, less than normal buffer size since caller might
|
||||
// just be examining beginning of file.
|
||||
return fill_buf( initial_read ? initial_read : block_size );
|
||||
}
|
||||
|
||||
blargg_err_t Zlib_Inflater::set_mode( mode_t mode, int data_offset )
|
||||
{
|
||||
zbuf.next_in += data_offset;
|
||||
zbuf.avail_in -= data_offset;
|
||||
|
||||
if ( mode == mode_auto )
|
||||
{
|
||||
// examine buffer for gzip header
|
||||
mode = mode_copy;
|
||||
unsigned const min_gzip_size = 2 + 8 + 8;
|
||||
if ( zbuf.avail_in >= min_gzip_size &&
|
||||
zbuf.next_in [0] == 0x1F && zbuf.next_in [1] == 0x8B )
|
||||
mode = mode_ungz;
|
||||
}
|
||||
|
||||
if ( mode != mode_copy )
|
||||
{
|
||||
int wb = MAX_WBITS + 16; // have zlib handle gzip header
|
||||
if ( mode == mode_raw_deflate )
|
||||
wb = -MAX_WBITS;
|
||||
|
||||
int zerr = inflateInit2( &zbuf, wb );
|
||||
if ( zerr )
|
||||
{
|
||||
zbuf.next_in = NULL;
|
||||
return get_zlib_err( zerr );
|
||||
}
|
||||
|
||||
deflated_ = true;
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
/*
|
||||
// Reads/inflates entire stream. All input must be in buffer, and count must be total
|
||||
// of all output.
|
||||
blargg_err_t read_all( void* out, int count );
|
||||
|
||||
|
||||
// zlib automatically applies this optimization (uses inflateFast)
|
||||
// TODO: remove
|
||||
blargg_err_t Zlib_Inflater::read_all( void* out, int count )
|
||||
{
|
||||
if ( deflated_ )
|
||||
{
|
||||
zbuf.next_out = (Bytef*) out;
|
||||
zbuf.avail_out = count;
|
||||
|
||||
int err = inflate( &zbuf, Z_FINISH );
|
||||
|
||||
if ( zbuf.avail_out || err != Z_STREAM_END )
|
||||
return blargg_err_file_corrupt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( zbuf.avail_in < count )
|
||||
return blargg_err_file_corrupt;
|
||||
|
||||
memcpy( out, zbuf.next_in, count );
|
||||
|
||||
zbuf.next_in += count;
|
||||
zbuf.avail_in -= count;
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
*/
|
||||
|
||||
blargg_err_t Zlib_Inflater::read( void* out, int* count_io )
|
||||
{
|
||||
int remain = *count_io;
|
||||
if ( remain && zbuf.next_in )
|
||||
{
|
||||
if ( deflated_ )
|
||||
{
|
||||
zbuf.next_out = (Bytef*) out;
|
||||
zbuf.avail_out = remain;
|
||||
|
||||
while ( 1 )
|
||||
{
|
||||
uInt old_avail_in = zbuf.avail_in;
|
||||
int err = inflate( &zbuf, Z_NO_FLUSH );
|
||||
if ( err == Z_STREAM_END )
|
||||
{
|
||||
remain = zbuf.avail_out;
|
||||
end();
|
||||
break; // no more data to inflate
|
||||
}
|
||||
|
||||
if ( err && (err != Z_BUF_ERROR || old_avail_in) )
|
||||
return get_zlib_err( err );
|
||||
|
||||
if ( !zbuf.avail_out )
|
||||
{
|
||||
remain = 0;
|
||||
break; // requested number of bytes inflated
|
||||
}
|
||||
|
||||
if ( zbuf.avail_in )
|
||||
{
|
||||
// inflate() should never leave input if there's still space for output
|
||||
check( false );
|
||||
return blargg_err_file_corrupt;
|
||||
}
|
||||
|
||||
RETURN_ERR( fill_buf( buf.size() ) );
|
||||
if ( !zbuf.avail_in )
|
||||
return blargg_err_file_corrupt; // stream didn't end but there's no more data
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
// copy buffered data
|
||||
if ( zbuf.avail_in )
|
||||
{
|
||||
long count = zbuf.avail_in;
|
||||
if ( count > remain )
|
||||
count = remain;
|
||||
memcpy( out, zbuf.next_in, count );
|
||||
zbuf.total_out += count;
|
||||
out = (char*) out + count;
|
||||
remain -= count;
|
||||
zbuf.next_in += count;
|
||||
zbuf.avail_in -= count;
|
||||
}
|
||||
|
||||
if ( !zbuf.avail_in && zbuf.next_in < buf.end() )
|
||||
{
|
||||
end();
|
||||
break;
|
||||
}
|
||||
|
||||
// read large request directly
|
||||
if ( remain + zbuf.total_out % block_size >= buf.size() )
|
||||
{
|
||||
int count = remain;
|
||||
RETURN_ERR( callback( user_data, out, &count ) );
|
||||
zbuf.total_out += count;
|
||||
out = (char*) out + count;
|
||||
remain -= count;
|
||||
|
||||
if ( remain )
|
||||
{
|
||||
end();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !remain )
|
||||
break;
|
||||
|
||||
RETURN_ERR( fill_buf( buf.size() - zbuf.total_out % block_size ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
*count_io -= remain;
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Simplifies use of zlib for inflating data
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef ZLIB_INFLATER_H
|
||||
#define ZLIB_INFLATER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Data_Reader.h"
|
||||
#include <zlib.h>
|
||||
|
||||
class Zlib_Inflater {
|
||||
public:
|
||||
|
||||
// Reads at most min(*count,bytes_until_eof()) bytes into *out and set *count
|
||||
// to that number, or returns error if that many can't be read.
|
||||
typedef blargg_err_t (*callback_t)( void* user_data, void* out, int* count );
|
||||
|
||||
// Begins by setting callback and filling buffer. Default buffer is 16K and
|
||||
// filled to 4K, or specify buf_size and initial_read for custom buffer size
|
||||
// and how much to read initially.
|
||||
blargg_err_t begin( callback_t, void* user_data,
|
||||
int buf_size = 0, int initial_read = 0 );
|
||||
|
||||
// Data read into buffer by begin()
|
||||
const unsigned char* data() const { return zbuf.next_in; }
|
||||
int filled() const { return zbuf.avail_in; }
|
||||
|
||||
// Begins inflation using specified mode. Using mode_auto selects between
|
||||
// mode_copy and mode_ungz by examining first two bytes of buffer. Use
|
||||
// buf_offset to specify where data begins in buffer, in case there is
|
||||
// header data that should be skipped.
|
||||
enum mode_t { mode_copy, mode_ungz, mode_raw_deflate, mode_auto };
|
||||
blargg_err_t set_mode( mode_t, int buf_offset = 0 );
|
||||
|
||||
// True if set_mode() has been called with mode_ungz or mode_raw_deflate
|
||||
bool deflated() const { return deflated_; }
|
||||
|
||||
// Reads/inflates at most *count_io bytes into *out and sets *count_io to actual
|
||||
// number of bytes read (less than requested if end of data was reached).
|
||||
// Buffers source data internally, even in copy mode, so input file can be
|
||||
// unbuffered without sacrificing performance.
|
||||
blargg_err_t read( void* out, int* count_io );
|
||||
|
||||
// Total number of bytes read since begin()
|
||||
int tell() const { return zbuf.total_out; }
|
||||
|
||||
// Ends inflation and frees memory
|
||||
void end();
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Zlib_Inflater( const Zlib_Inflater& );
|
||||
Zlib_Inflater& operator = ( const Zlib_Inflater& );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Zlib_Inflater();
|
||||
~Zlib_Inflater();
|
||||
|
||||
private:
|
||||
z_stream_s zbuf;
|
||||
blargg_vector<unsigned char> buf;
|
||||
bool deflated_;
|
||||
callback_t callback;
|
||||
void* user_data;
|
||||
|
||||
blargg_err_t fill_buf( int count );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,51 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
/* Copyright (C) 2008-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void blargg_vector_::init()
|
||||
{
|
||||
begin_ = NULL;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void blargg_vector_::clear()
|
||||
{
|
||||
void* p = begin_;
|
||||
begin_ = NULL;
|
||||
size_ = 0;
|
||||
free( p );
|
||||
}
|
||||
|
||||
blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size )
|
||||
{
|
||||
if ( n != size_ )
|
||||
{
|
||||
if ( n == 0 )
|
||||
{
|
||||
// Simpler to handle explicitly. Realloc will handle a size of 0,
|
||||
// but then we have to avoid raising an error for a NULL return.
|
||||
clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
void* p = realloc( begin_, n * elem_size );
|
||||
CHECK_ALLOC( p );
|
||||
begin_ = p;
|
||||
size_ = n;
|
||||
}
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
// Sets up common environment for Shay Green's libraries.
|
||||
// To change configuration options, modify blargg_config.h, not this file.
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#define BLARGG_COMMON_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
typedef const char* blargg_err_t; // 0 on success, otherwise error string
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef wchar_t blargg_wchar_t;
|
||||
#else
|
||||
typedef uint16_t blargg_wchar_t;
|
||||
#endif
|
||||
|
||||
inline size_t blargg_wcslen( const blargg_wchar_t* str )
|
||||
{
|
||||
size_t length = 0;
|
||||
while ( *str++ ) length++;
|
||||
return length;
|
||||
}
|
||||
|
||||
// Success; no error
|
||||
blargg_err_t const blargg_ok = 0;
|
||||
|
||||
// BLARGG_RESTRICT: equivalent to C99's restrict, where supported
|
||||
#if __GNUC__ >= 3 || _MSC_VER >= 1100
|
||||
#define BLARGG_RESTRICT __restrict
|
||||
#else
|
||||
#define BLARGG_RESTRICT
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 199711
|
||||
#define BLARGG_MUTABLE mutable
|
||||
#else
|
||||
#define BLARGG_MUTABLE
|
||||
#endif
|
||||
|
||||
/* BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant).
|
||||
I don't just use 'abcd' because that's implementation-dependent. */
|
||||
#define BLARGG_4CHAR( a, b, c, d ) \
|
||||
((a&0xFF)*0x1000000 + (b&0xFF)*0x10000 + (c&0xFF)*0x100 + (d&0xFF))
|
||||
|
||||
/* BLARGG_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
|
||||
Can be used at file, function, or class scope. */
|
||||
#ifdef _MSC_VER
|
||||
// MSVC6 (_MSC_VER < 1300) __LINE__ fails when /Zl is specified
|
||||
#define BLARGG_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
|
||||
#else
|
||||
// Others fail when declaring same function multiple times in class,
|
||||
// so differentiate them by line
|
||||
#define BLARGG_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
|
||||
#endif
|
||||
|
||||
/* Pure virtual functions cause a vtable entry to a "called pure virtual"
|
||||
error handler, requiring linkage to the C++ runtime library. This macro is
|
||||
used in place of the "= 0", and simply expands to its argument. During
|
||||
development, it expands to "= 0", allowing detection of missing overrides. */
|
||||
#define BLARGG_PURE( def ) def
|
||||
|
||||
/* My code depends on ASCII anywhere a character or string constant is
|
||||
compared with data read from a file, and anywhere file data is read and
|
||||
treated as a string. */
|
||||
#if '\n'!=0x0A || ' '!=0x20 || '0'!=0x30 || 'A'!=0x41 || 'a'!=0x61
|
||||
#error "ASCII character set required"
|
||||
#endif
|
||||
|
||||
/* My code depends on int being at least 32 bits. Almost everything these days
|
||||
uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints
|
||||
to test with. The issue can't be gotten around by using a suitable blargg_int
|
||||
everywhere either, because int is often converted to implicitly when doing
|
||||
arithmetic on smaller types. */
|
||||
#if UINT_MAX < 0xFFFFFFFF
|
||||
#error "int must be at least 32 bits"
|
||||
#endif
|
||||
|
||||
// In case compiler doesn't support these properly. Used rarely.
|
||||
#define STATIC_CAST(T,expr) static_cast<T> (expr)
|
||||
#define CONST_CAST( T,expr) const_cast<T> (expr)
|
||||
|
||||
// User configuration can override the above macros if necessary
|
||||
#include "blargg_config.h"
|
||||
|
||||
/* BLARGG_DEPRECATED [_TEXT] for any declarations/text to be removed in a
|
||||
future version. In GCC, we can let the compiler warn. In other compilers,
|
||||
we strip it out unless BLARGG_LEGACY is true. */
|
||||
#if BLARGG_LEGACY
|
||||
// Allow old client code to work without warnings
|
||||
#define BLARGG_DEPRECATED_TEXT( text ) text
|
||||
#define BLARGG_DEPRECATED( text ) text
|
||||
#elif __GNUC__ >= 4
|
||||
// In GCC, we can mark declarations and let the compiler warn
|
||||
#define BLARGG_DEPRECATED_TEXT( text ) text
|
||||
#define BLARGG_DEPRECATED( text ) __attribute__ ((deprecated)) text
|
||||
#else
|
||||
// By default, deprecated items are removed, to avoid use in new code
|
||||
#define BLARGG_DEPRECATED_TEXT( text )
|
||||
#define BLARGG_DEPRECATED( text )
|
||||
#endif
|
||||
|
||||
/* BOOST::int8_t, BOOST::int32_t, etc.
|
||||
I used BOOST since I originally was going to allow use of the boost library
|
||||
for prividing the definitions. If I'm defining them, they must be scoped or
|
||||
else they could conflict with the standard ones at global scope. Even if
|
||||
HAVE_STDINT_H isn't defined, I can't assume the typedefs won't exist at
|
||||
global scope already. */
|
||||
#if defined (HAVE_STDINT_H) || \
|
||||
UCHAR_MAX != 0xFF || USHRT_MAX != 0xFFFF || UINT_MAX != 0xFFFFFFFF
|
||||
#include <stdint.h>
|
||||
#define BOOST
|
||||
#else
|
||||
struct BOOST
|
||||
{
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
};
|
||||
#endif
|
||||
|
||||
/* My code is not written with exceptions in mind, so either uses new (nothrow)
|
||||
OR overrides operator new in my classes. The former is best since clients
|
||||
creating objects will get standard exceptions on failure, but that causes it
|
||||
to require the standard C++ library. So, when the client is using the C
|
||||
interface, I override operator new to use malloc. */
|
||||
|
||||
// BLARGG_DISABLE_NOTHROW is put inside classes
|
||||
#ifndef BLARGG_DISABLE_NOTHROW
|
||||
// throw spec mandatory in ISO C++ if NULL can be returned
|
||||
#if __cplusplus >= 199711 || __GNUC__ >= 3 || _MSC_VER >= 1300
|
||||
#define BLARGG_THROWS_NOTHING throw ()
|
||||
#else
|
||||
#define BLARGG_THROWS_NOTHING
|
||||
#endif
|
||||
|
||||
#define BLARGG_DISABLE_NOTHROW \
|
||||
void* operator new ( size_t s ) BLARGG_THROWS_NOTHING { return malloc( s ); }\
|
||||
void operator delete( void* p ) BLARGG_THROWS_NOTHING { free( p ); }
|
||||
|
||||
#define BLARGG_NEW new
|
||||
#else
|
||||
// BLARGG_NEW is used in place of new in library code
|
||||
#include <new>
|
||||
#define BLARGG_NEW new (std::nothrow)
|
||||
#endif
|
||||
|
||||
class blargg_vector_ {
|
||||
protected:
|
||||
void* begin_;
|
||||
size_t size_;
|
||||
void init();
|
||||
blargg_err_t resize_( size_t n, size_t elem_size );
|
||||
public:
|
||||
size_t size() const { return size_; }
|
||||
void clear();
|
||||
};
|
||||
|
||||
// Very lightweight vector for POD types (no constructor/destructor)
|
||||
template<class T>
|
||||
class blargg_vector : public blargg_vector_ {
|
||||
union T_must_be_pod { T t; }; // fails if T is not POD
|
||||
public:
|
||||
blargg_vector() { init(); }
|
||||
~blargg_vector() { clear(); }
|
||||
|
||||
blargg_err_t resize( size_t n ) { return resize_( n, sizeof (T) ); }
|
||||
|
||||
T* begin() { return static_cast<T*> (begin_); }
|
||||
const T* begin() const { return static_cast<T*> (begin_); }
|
||||
|
||||
T* end() { return static_cast<T*> (begin_) + size_; }
|
||||
const T* end() const { return static_cast<T*> (begin_) + size_; }
|
||||
|
||||
T& operator [] ( size_t n )
|
||||
{
|
||||
assert( n < size_ );
|
||||
return static_cast<T*> (begin_) [n];
|
||||
}
|
||||
|
||||
const T& operator [] ( size_t n ) const
|
||||
{
|
||||
assert( n < size_ );
|
||||
return static_cast<T*> (begin_) [n];
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function with user data.
|
||||
// blargg_callback<T> set_callback; // for user, this acts like...
|
||||
// void set_callback( T func, void* user_data = NULL ); // ...this
|
||||
// To call function, do set_callback.f( .. set_callback.data ... );
|
||||
template<class T>
|
||||
struct blargg_callback
|
||||
{
|
||||
T f;
|
||||
void* data;
|
||||
blargg_callback() { f = NULL; }
|
||||
void operator () ( T callback, void* user_data = NULL ) { f = callback; data = user_data; }
|
||||
};
|
||||
|
||||
BLARGG_DEPRECATED( typedef signed int blargg_long; )
|
||||
BLARGG_DEPRECATED( typedef unsigned int blargg_ulong; )
|
||||
#if BLARGG_LEGACY
|
||||
#define BOOST_STATIC_ASSERT BLARGG_STATIC_ASSERT
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,37 @@
|
|||
// Library configuration. Modify this file as necessary.
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BLARGG_CONFIG_H
|
||||
#define BLARGG_CONFIG_H
|
||||
|
||||
// Uncomment a #define line below to have effect described.
|
||||
// #define HAVE_ZLIB_H
|
||||
|
||||
// Enable RAR archive support. Doing so adds extra licensing restrictions
|
||||
// to this library (see unrar/readme.txt for more information).
|
||||
#define FEX_ENABLE_RAR 1
|
||||
|
||||
// Accept file paths encoded as UTF-8. Currently only affects Windows,
|
||||
// as Unix/Linux/Mac OS X already use UTF-8 paths.
|
||||
#define BLARGG_UTF8_PATHS 1
|
||||
|
||||
// Enable support for as building DLL on Windows.
|
||||
//#define BLARGG_BUILD_DLL 1
|
||||
|
||||
// Support only the listed archive types. Remove any you don't need.
|
||||
/*
|
||||
#define FEX_TYPE_LIST \
|
||||
fex_7z_type,\
|
||||
fex_gz_type,\
|
||||
fex_rar_type,\
|
||||
fex_zip_type,
|
||||
*/
|
||||
|
||||
#define HAVE_STDINT_H
|
||||
|
||||
// Use standard config.h if present
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,185 @@
|
|||
// CPU Byte Order Utilities
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BLARGG_ENDIAN_H
|
||||
#define BLARGG_ENDIAN_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
|
||||
#if defined (__i386__) || defined (__x86_64__) || defined (_M_IX86) || defined (_M_X64)
|
||||
#define BLARGG_CPU_X86 1
|
||||
#define BLARGG_CPU_CISC 1
|
||||
#endif
|
||||
|
||||
#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) || \
|
||||
defined (__POWERPC__) || defined (__powerc)
|
||||
#define BLARGG_CPU_POWERPC 1
|
||||
#define BLARGG_CPU_RISC 1
|
||||
#endif
|
||||
|
||||
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
|
||||
// one may be #defined to 1. Only needed if something actually depends on byte order.
|
||||
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
|
||||
#ifdef __GLIBC__
|
||||
// GCC handles this for us
|
||||
#include <endian.h>
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#endif
|
||||
#else
|
||||
|
||||
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
|
||||
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
|
||||
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
|
||||
defined (__sparc__) || BLARGG_CPU_POWERPC || \
|
||||
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#elif !defined (__mips__)
|
||||
// No endian specified; assume little-endian, since it's most common
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
|
||||
#undef BLARGG_LITTLE_ENDIAN
|
||||
#undef BLARGG_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
inline void blargg_verify_byte_order()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
#if BLARGG_BIG_ENDIAN
|
||||
volatile int i = 1;
|
||||
assert( *(volatile char*) &i == 0 );
|
||||
#elif BLARGG_LITTLE_ENDIAN
|
||||
volatile int i = 1;
|
||||
assert( *(volatile char*) &i != 0 );
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned get_le16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [1] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline unsigned get_be16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [0] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [1];
|
||||
}
|
||||
|
||||
inline unsigned get_le32( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [3] << 24 |
|
||||
(unsigned) ((unsigned char const*) p) [2] << 16 |
|
||||
(unsigned) ((unsigned char const*) p) [1] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline unsigned get_be32( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [0] << 24 |
|
||||
(unsigned) ((unsigned char const*) p) [1] << 16 |
|
||||
(unsigned) ((unsigned char const*) p) [2] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [3];
|
||||
}
|
||||
|
||||
inline void set_le16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_be16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_le32( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
inline void set_be32( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [3] = (unsigned char) n;
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
#if BLARGG_NONPORTABLE
|
||||
// Optimized implementation if byte order is known
|
||||
#if BLARGG_LITTLE_ENDIAN
|
||||
#define GET_LE16( addr ) (*(BOOST::uint16_t const*) (addr))
|
||||
#define GET_LE32( addr ) (*(BOOST::uint32_t const*) (addr))
|
||||
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
#elif BLARGG_BIG_ENDIAN
|
||||
#define GET_BE16( addr ) (*(BOOST::uint16_t const*) (addr))
|
||||
#define GET_BE32( addr ) (*(BOOST::uint32_t const*) (addr))
|
||||
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
|
||||
#if BLARGG_CPU_POWERPC
|
||||
// PowerPC has special byte-reversed instructions
|
||||
#if defined (__MWERKS__)
|
||||
#define GET_LE16( addr ) (__lhbrx( addr, 0 ))
|
||||
#define GET_LE32( addr ) (__lwbrx( addr, 0 ))
|
||||
#define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 ))
|
||||
#define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 ))
|
||||
#elif defined (__GNUC__)
|
||||
#define GET_LE16( addr ) ({unsigned short ppc_lhbrx_; __asm__ volatile( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr) : "memory" ); ppc_lhbrx_;})
|
||||
#define GET_LE32( addr ) ({unsigned short ppc_lwbrx_; __asm__ volatile( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr) : "memory" ); ppc_lwbrx_;})
|
||||
#define SET_LE16( addr, in ) ({__asm__ volatile( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );})
|
||||
#define SET_LE32( addr, in ) ({__asm__ volatile( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );})
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE16
|
||||
#define GET_LE16( addr ) get_le16( addr )
|
||||
#define SET_LE16( addr, data ) set_le16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE32
|
||||
#define GET_LE32( addr ) get_le32( addr )
|
||||
#define SET_LE32( addr, data ) set_le32( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE16
|
||||
#define GET_BE16( addr ) get_be16( addr )
|
||||
#define SET_BE16( addr, data ) set_be16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE32
|
||||
#define GET_BE32( addr ) get_be32( addr )
|
||||
#define SET_BE32( addr, data ) set_be32( addr, data )
|
||||
#endif
|
||||
|
||||
// auto-selecting versions
|
||||
|
||||
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
|
||||
inline void set_le( BOOST::uint32_t* p, unsigned n ) { SET_LE32( p, n ); }
|
||||
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
|
||||
inline void set_be( BOOST::uint32_t* p, unsigned n ) { SET_BE32( p, n ); }
|
||||
inline unsigned get_le( BOOST::uint16_t const* p ) { return GET_LE16( p ); }
|
||||
inline unsigned get_le( BOOST::uint32_t const* p ) { return GET_LE32( p ); }
|
||||
inline unsigned get_be( BOOST::uint16_t const* p ) { return GET_BE16( p ); }
|
||||
inline unsigned get_be( BOOST::uint32_t const* p ) { return GET_BE32( p ); }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,113 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "blargg_errors.h"
|
||||
|
||||
/* Copyright (C) 2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
blargg_err_def_t blargg_err_generic = BLARGG_ERR_GENERIC;
|
||||
blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY;
|
||||
blargg_err_def_t blargg_err_caller = BLARGG_ERR_CALLER;
|
||||
blargg_err_def_t blargg_err_internal = BLARGG_ERR_INTERNAL;
|
||||
blargg_err_def_t blargg_err_limitation = BLARGG_ERR_LIMITATION;
|
||||
|
||||
blargg_err_def_t blargg_err_file_missing = BLARGG_ERR_FILE_MISSING;
|
||||
blargg_err_def_t blargg_err_file_read = BLARGG_ERR_FILE_READ;
|
||||
blargg_err_def_t blargg_err_file_write = BLARGG_ERR_FILE_WRITE;
|
||||
blargg_err_def_t blargg_err_file_io = BLARGG_ERR_FILE_IO;
|
||||
blargg_err_def_t blargg_err_file_full = BLARGG_ERR_FILE_FULL;
|
||||
blargg_err_def_t blargg_err_file_eof = BLARGG_ERR_FILE_EOF;
|
||||
|
||||
blargg_err_def_t blargg_err_file_type = BLARGG_ERR_FILE_TYPE;
|
||||
blargg_err_def_t blargg_err_file_feature = BLARGG_ERR_FILE_FEATURE;
|
||||
blargg_err_def_t blargg_err_file_corrupt = BLARGG_ERR_FILE_CORRUPT;
|
||||
|
||||
const char* blargg_err_str( blargg_err_t err )
|
||||
{
|
||||
if ( !err )
|
||||
return "";
|
||||
|
||||
if ( *err == BLARGG_ERR_TYPE("")[0] )
|
||||
return err + 1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool blargg_is_err_type( blargg_err_t err, const char type [] )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
// True if first strlen(type) characters of err match type
|
||||
char const* p = err;
|
||||
while ( *type && *type == *p )
|
||||
{
|
||||
type++;
|
||||
p++;
|
||||
}
|
||||
|
||||
if ( !*type )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* blargg_err_details( blargg_err_t err )
|
||||
{
|
||||
const char* p = err;
|
||||
if ( !p )
|
||||
{
|
||||
p = "";
|
||||
}
|
||||
else if ( *p == BLARGG_ERR_TYPE("")[0] )
|
||||
{
|
||||
while ( *p && *p != ';' )
|
||||
p++;
|
||||
|
||||
// Skip ; and space after it
|
||||
if ( *p )
|
||||
{
|
||||
p++;
|
||||
|
||||
check( *p == ' ' );
|
||||
if ( *p )
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const codes [] )
|
||||
{
|
||||
if ( !err )
|
||||
return 0;
|
||||
|
||||
while ( codes->str && !blargg_is_err_type( err, codes->str ) )
|
||||
codes++;
|
||||
|
||||
return codes->code;
|
||||
}
|
||||
|
||||
blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const codes [] )
|
||||
{
|
||||
if ( !code )
|
||||
return blargg_ok;
|
||||
|
||||
while ( codes->str && codes->code != code )
|
||||
codes++;
|
||||
|
||||
if ( !codes->str )
|
||||
return blargg_err_generic;
|
||||
|
||||
return codes->str;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// Error strings and conversion functions
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BLARGG_ERRORS_H
|
||||
#define BLARGG_ERRORS_H
|
||||
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#include "blargg_common.h"
|
||||
#endif
|
||||
|
||||
typedef const char blargg_err_def_t [];
|
||||
|
||||
// Basic errors
|
||||
extern blargg_err_def_t blargg_err_generic;
|
||||
extern blargg_err_def_t blargg_err_memory;
|
||||
extern blargg_err_def_t blargg_err_caller;
|
||||
extern blargg_err_def_t blargg_err_internal;
|
||||
extern blargg_err_def_t blargg_err_limitation;
|
||||
|
||||
// File low-level
|
||||
extern blargg_err_def_t blargg_err_file_missing; // not found
|
||||
extern blargg_err_def_t blargg_err_file_read;
|
||||
extern blargg_err_def_t blargg_err_file_write;
|
||||
extern blargg_err_def_t blargg_err_file_io;
|
||||
extern blargg_err_def_t blargg_err_file_full;
|
||||
extern blargg_err_def_t blargg_err_file_eof;
|
||||
|
||||
// File high-level
|
||||
extern blargg_err_def_t blargg_err_file_type; // wrong file type
|
||||
extern blargg_err_def_t blargg_err_file_feature;
|
||||
extern blargg_err_def_t blargg_err_file_corrupt;
|
||||
|
||||
// C string describing error, or "" if err == NULL
|
||||
const char* blargg_err_str( blargg_err_t err );
|
||||
|
||||
// True iff error is of given type, or false if err == NULL
|
||||
bool blargg_is_err_type( blargg_err_t, const char type [] );
|
||||
|
||||
// Details of error without describing main cause, or "" if err == NULL
|
||||
const char* blargg_err_details( blargg_err_t err );
|
||||
|
||||
// Converts error string to integer code using mapping table. Calls blargg_is_err_type()
|
||||
// for each str and returns code on first match. Returns 0 if err == NULL.
|
||||
struct blargg_err_to_code_t {
|
||||
const char* str;
|
||||
int code;
|
||||
};
|
||||
int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const [] );
|
||||
|
||||
// Converts error code back to string. If code == 0, returns NULL. If not in table,
|
||||
// returns blargg_err_generic.
|
||||
blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const [] );
|
||||
|
||||
// Generates error string literal with details of cause
|
||||
#define BLARGG_ERR( type, str ) (type "; " str)
|
||||
|
||||
// Extra space to make it clear when blargg_err_str() isn't called to get
|
||||
// printable version of error. At some point, I might prefix error strings
|
||||
// with a code, to speed conversion to a code.
|
||||
#define BLARGG_ERR_TYPE( str ) " " str
|
||||
|
||||
// Error types to pass to BLARGG_ERR macro
|
||||
#define BLARGG_ERR_GENERIC BLARGG_ERR_TYPE( "operation failed" )
|
||||
#define BLARGG_ERR_MEMORY BLARGG_ERR_TYPE( "out of memory" )
|
||||
#define BLARGG_ERR_CALLER BLARGG_ERR_TYPE( "internal usage bug" )
|
||||
#define BLARGG_ERR_INTERNAL BLARGG_ERR_TYPE( "internal bug" )
|
||||
#define BLARGG_ERR_LIMITATION BLARGG_ERR_TYPE( "exceeded limitation" )
|
||||
|
||||
#define BLARGG_ERR_FILE_MISSING BLARGG_ERR_TYPE( "file not found" )
|
||||
#define BLARGG_ERR_FILE_READ BLARGG_ERR_TYPE( "couldn't open file" )
|
||||
#define BLARGG_ERR_FILE_WRITE BLARGG_ERR_TYPE( "couldn't modify file" )
|
||||
#define BLARGG_ERR_FILE_IO BLARGG_ERR_TYPE( "read/write error" )
|
||||
#define BLARGG_ERR_FILE_FULL BLARGG_ERR_TYPE( "disk full" )
|
||||
#define BLARGG_ERR_FILE_EOF BLARGG_ERR_TYPE( "truncated file" )
|
||||
|
||||
#define BLARGG_ERR_FILE_TYPE BLARGG_ERR_TYPE( "wrong file type" )
|
||||
#define BLARGG_ERR_FILE_FEATURE BLARGG_ERR_TYPE( "unsupported file feature" )
|
||||
#define BLARGG_ERR_FILE_CORRUPT BLARGG_ERR_TYPE( "corrupt file" )
|
||||
|
||||
#endif
|
|
@ -0,0 +1,121 @@
|
|||
/* Included at the beginning of library source files, AFTER all other #include
|
||||
lines. Sets up helpful macros and services used in my source code. Since this
|
||||
is only "active" in my source code, I don't have to worry about polluting the
|
||||
global namespace with unprefixed names. */
|
||||
|
||||
// File_Extractor 1.0.0
|
||||
#ifndef BLARGG_SOURCE_H
|
||||
#define BLARGG_SOURCE_H
|
||||
|
||||
#ifndef BLARGG_COMMON_H // optimization only
|
||||
#include "blargg_common.h"
|
||||
#endif
|
||||
#include "blargg_errors.h"
|
||||
|
||||
#include <string.h> /* memcpy(), memset(), memmove() */
|
||||
#include <stddef.h> /* offsetof() */
|
||||
|
||||
/* The following four macros are for debugging only. Some or all might be
|
||||
defined to do nothing, depending on the circumstances. Described is what
|
||||
happens when a particular macro is defined to do something. When defined to
|
||||
do nothing, the macros do NOT evaluate their argument(s). */
|
||||
|
||||
/* If expr is false, prints file and line number, then aborts program. Meant
|
||||
for checking internal state and consistency. A failed assertion indicates a bug
|
||||
in MY code.
|
||||
|
||||
void assert( bool expr ); */
|
||||
#include <assert.h>
|
||||
|
||||
/* If expr is false, prints file and line number, then aborts program. Meant
|
||||
for checking caller-supplied parameters and operations that are outside the
|
||||
control of the module. A failed requirement probably indicates a bug in YOUR
|
||||
code.
|
||||
|
||||
void require( bool expr ); */
|
||||
#undef require
|
||||
#define require( expr ) assert( expr )
|
||||
|
||||
/* Like printf() except output goes to debugging console/file.
|
||||
|
||||
void dprintf( const char format [], ... ); */
|
||||
static inline void blargg_dprintf_( const char [], ... ) { }
|
||||
#undef dprintf
|
||||
#define dprintf (1) ? (void) 0 : blargg_dprintf_
|
||||
|
||||
/* If expr is false, prints file and line number to debug console/log, then
|
||||
continues execution normally. Meant for flagging potential problems or things
|
||||
that should be looked into, but that aren't serious problems.
|
||||
|
||||
void check( bool expr ); */
|
||||
#undef check
|
||||
#define check( expr ) ((void) 0)
|
||||
|
||||
/* If expr yields non-NULL error string, returns it from current function,
|
||||
otherwise continues normally. */
|
||||
#undef RETURN_ERR
|
||||
#define RETURN_ERR( expr ) \
|
||||
do {\
|
||||
blargg_err_t blargg_return_err_ = (expr);\
|
||||
if ( blargg_return_err_ )\
|
||||
return blargg_return_err_;\
|
||||
} while ( 0 )
|
||||
|
||||
/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */
|
||||
#undef CHECK_ALLOC
|
||||
#define CHECK_ALLOC( ptr ) \
|
||||
do {\
|
||||
if ( !(ptr) )\
|
||||
return blargg_err_memory;\
|
||||
} while ( 0 )
|
||||
|
||||
/* The usual min/max functions for built-in types. */
|
||||
|
||||
template<typename T> T min( T x, T y ) { return x < y ? x : y; }
|
||||
template<typename T> T max( T x, T y ) { return x > y ? x : y; }
|
||||
|
||||
#define BLARGG_DEF_MIN_MAX( type )
|
||||
|
||||
BLARGG_DEF_MIN_MAX( int )
|
||||
BLARGG_DEF_MIN_MAX( unsigned )
|
||||
BLARGG_DEF_MIN_MAX( long )
|
||||
BLARGG_DEF_MIN_MAX( unsigned long )
|
||||
BLARGG_DEF_MIN_MAX( float )
|
||||
BLARGG_DEF_MIN_MAX( double )
|
||||
#if __WORDSIZE != 64
|
||||
BLARGG_DEF_MIN_MAX( BOOST::uint64_t )
|
||||
#endif
|
||||
|
||||
// typedef unsigned char byte;
|
||||
typedef unsigned char blargg_byte;
|
||||
#undef byte
|
||||
#define byte blargg_byte
|
||||
|
||||
#ifndef BLARGG_EXPORT
|
||||
#if defined (_WIN32) && BLARGG_BUILD_DLL
|
||||
#define BLARGG_EXPORT __declspec(dllexport)
|
||||
#elif defined (__GNUC__)
|
||||
// can always set visibility, even when not building DLL
|
||||
#define BLARGG_EXPORT __attribute__ ((visibility ("default")))
|
||||
#else
|
||||
#define BLARGG_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLARGG_LEGACY
|
||||
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
|
||||
#define BLARGG_RETURN_ERR RETURN_ERR
|
||||
#endif
|
||||
|
||||
// Called after failed operation when overall operation may still complete OK.
|
||||
// Only used by unit testing framework.
|
||||
#undef ACK_FAILURE
|
||||
#define ACK_FAILURE() ((void)0)
|
||||
|
||||
/* BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf etc.
|
||||
and check */
|
||||
#ifdef BLARGG_SOURCE_BEGIN
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,321 @@
|
|||
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "fex.h"
|
||||
|
||||
#include "File_Extractor.h"
|
||||
#include "blargg_endian.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* Copyright (C) 2005-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
|
||||
//// Types
|
||||
|
||||
BLARGG_EXPORT const fex_type_t* fex_type_list( void )
|
||||
{
|
||||
static fex_type_t const fex_type_list_ [] =
|
||||
{
|
||||
#ifdef FEX_TYPE_LIST
|
||||
FEX_TYPE_LIST
|
||||
#else
|
||||
// Modify blargg_config.h to change type list, NOT this file
|
||||
fex_7z_type,
|
||||
fex_gz_type,
|
||||
#if FEX_ENABLE_RAR
|
||||
fex_rar_type,
|
||||
#endif
|
||||
fex_zip_type,
|
||||
#endif
|
||||
fex_bin_type,
|
||||
NULL
|
||||
};
|
||||
|
||||
return fex_type_list_;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_init( void )
|
||||
{
|
||||
static bool inited;
|
||||
if ( !inited )
|
||||
{
|
||||
for ( fex_type_t const* t = fex_type_list(); *t != NULL; ++t )
|
||||
{
|
||||
if ( (*t)->init )
|
||||
RETURN_ERR( (*t)->init() );
|
||||
}
|
||||
inited = true;
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT const char* fex_identify_header( void const* header )
|
||||
{
|
||||
unsigned four = get_be32( header );
|
||||
switch ( four )
|
||||
{
|
||||
case 0x52457E5E:
|
||||
case 0x52617221: return ".rar";
|
||||
|
||||
case 0x377ABCAF: return ".7z";
|
||||
|
||||
case 0x504B0304:
|
||||
case 0x504B0506: return ".zip";
|
||||
|
||||
case 0x53495421: return ".sit";
|
||||
case 0x41724301: return ".arc";
|
||||
case 0x4D534346: return ".cab";
|
||||
case 0x5A4F4F20: return ".zoo";
|
||||
}
|
||||
|
||||
unsigned three = four >> 8;
|
||||
switch ( three )
|
||||
{
|
||||
case 0x425A68: return ".bz2";
|
||||
}
|
||||
|
||||
unsigned two = four >> 16;
|
||||
switch ( two )
|
||||
{
|
||||
case 0x1F8B: return ".gz";
|
||||
case 0x60EA: return ".arj";
|
||||
}
|
||||
|
||||
unsigned skip_first_two = four & 0xFFFF;
|
||||
if ( skip_first_two == 0x2D6C )
|
||||
return ".lha";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static int fex_has_extension_( const char str [], const char suffix [], size_t str_len )
|
||||
{
|
||||
size_t suffix_len = strlen( suffix );
|
||||
if ( str_len >= suffix_len )
|
||||
{
|
||||
str += str_len - suffix_len;
|
||||
while ( *str && tolower( (unsigned char) *str ) == *suffix )
|
||||
{
|
||||
str++;
|
||||
suffix++;
|
||||
}
|
||||
}
|
||||
return *suffix == 0;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT int fex_has_extension( const char str [], const char suffix [] )
|
||||
{
|
||||
return fex_has_extension_( str, suffix, strlen( str ) );
|
||||
}
|
||||
|
||||
static int is_archive_extension( const char str [] )
|
||||
{
|
||||
static const char exts [] [6] = {
|
||||
".7z",
|
||||
".arc",
|
||||
".arj",
|
||||
".bz2",
|
||||
".cab",
|
||||
".dmg",
|
||||
".gz",
|
||||
".lha",
|
||||
".lz",
|
||||
".lzh",
|
||||
".lzma",
|
||||
".lzo",
|
||||
".lzx",
|
||||
".pea",
|
||||
".rar",
|
||||
".sit",
|
||||
".sitx",
|
||||
".tgz",
|
||||
".tlz",
|
||||
".z",
|
||||
".zip",
|
||||
".zoo",
|
||||
""
|
||||
};
|
||||
|
||||
size_t str_len = strlen( str );
|
||||
const char (*ext) [6] = exts;
|
||||
for ( ; **ext; ext++ )
|
||||
{
|
||||
if ( fex_has_extension_( str, *ext, str_len ) )
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_type_t fex_identify_extension( const char str [] )
|
||||
{
|
||||
size_t str_len = strlen( str );
|
||||
for ( fex_type_t const* types = fex_type_list(); *types; types++ )
|
||||
{
|
||||
if ( fex_has_extension_( str, (*types)->extension, str_len ) )
|
||||
{
|
||||
// Avoid treating known archive type as binary
|
||||
if ( *(*types)->extension || !is_archive_extension( str ) )
|
||||
return *types;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_identify_file( fex_type_t* type_out, const char path [] )
|
||||
{
|
||||
*type_out = NULL;
|
||||
|
||||
fex_type_t type = fex_identify_extension( path );
|
||||
|
||||
// Unsupported extension?
|
||||
if ( !type )
|
||||
return blargg_ok; // reject
|
||||
|
||||
// Unknown/no extension?
|
||||
if ( !*(type->extension) )
|
||||
{
|
||||
// Examine header
|
||||
FEX_FILE_READER in;
|
||||
RETURN_ERR( in.open( path ) );
|
||||
if ( in.remain() >= fex_identify_header_size )
|
||||
{
|
||||
char h [fex_identify_header_size];
|
||||
RETURN_ERR( in.read( h, sizeof h ) );
|
||||
|
||||
type = fex_identify_extension( fex_identify_header( h ) );
|
||||
}
|
||||
}
|
||||
|
||||
*type_out = type;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_open_type( fex_t** fe_out, const char path [], fex_type_t type )
|
||||
{
|
||||
*fe_out = NULL;
|
||||
|
||||
if ( !type )
|
||||
return blargg_err_file_type;
|
||||
|
||||
fex_t* fe = type->new_fex();
|
||||
CHECK_ALLOC( fe );
|
||||
|
||||
fex_err_t err = fe->open( path );
|
||||
if ( err )
|
||||
{
|
||||
delete fe;
|
||||
return err;
|
||||
}
|
||||
|
||||
*fe_out = fe;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_open( fex_t** fe_out, const char path [] )
|
||||
{
|
||||
*fe_out = NULL;
|
||||
|
||||
fex_type_t type;
|
||||
RETURN_ERR( fex_identify_file( &type, path ) );
|
||||
|
||||
return fex_open_type( fe_out, path, type );
|
||||
}
|
||||
|
||||
|
||||
//// Wide paths
|
||||
|
||||
char* fex_wide_to_path( const blargg_wchar_t* wide )
|
||||
{
|
||||
return blargg_to_utf8( wide );
|
||||
}
|
||||
|
||||
void fex_free_path( char* path )
|
||||
{
|
||||
free( path );
|
||||
}
|
||||
|
||||
|
||||
//// Errors
|
||||
|
||||
#define ENTRY( name ) { blargg_err_##name, fex_err_##name }
|
||||
static blargg_err_to_code_t const fex_codes [] =
|
||||
{
|
||||
ENTRY( generic ),
|
||||
ENTRY( memory ),
|
||||
ENTRY( caller ),
|
||||
ENTRY( internal ),
|
||||
ENTRY( limitation ),
|
||||
|
||||
ENTRY( file_missing ),
|
||||
ENTRY( file_read ),
|
||||
ENTRY( file_io ),
|
||||
ENTRY( file_eof ),
|
||||
|
||||
ENTRY( file_type ),
|
||||
ENTRY( file_feature ),
|
||||
ENTRY( file_corrupt ),
|
||||
|
||||
{ 0, -1 }
|
||||
};
|
||||
#undef ENTRY
|
||||
|
||||
static int err_code( fex_err_t err )
|
||||
{
|
||||
return blargg_err_to_code( err, fex_codes );
|
||||
}
|
||||
|
||||
BLARGG_EXPORT int fex_err_code( fex_err_t err )
|
||||
{
|
||||
int code = err_code( err );
|
||||
return (code >= 0 ? code : fex_err_generic);
|
||||
}
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_code_to_err( int code )
|
||||
{
|
||||
return blargg_code_to_err( code, fex_codes );
|
||||
}
|
||||
|
||||
BLARGG_EXPORT const char* fex_err_details( fex_err_t err )
|
||||
{
|
||||
// If we don't have error code assigned, return entire string
|
||||
return (err_code( err ) >= 0 ? blargg_err_details( err ) : blargg_err_str( err ));
|
||||
}
|
||||
|
||||
|
||||
//// Wrappers
|
||||
|
||||
BLARGG_EXPORT fex_err_t fex_read( fex_t* fe, void* out, int count )
|
||||
{
|
||||
RETURN_ERR( fe->stat() );
|
||||
return fe->reader().read( out, count );
|
||||
}
|
||||
|
||||
BLARGG_EXPORT void fex_close ( fex_t* fe ) { delete fe; }
|
||||
BLARGG_EXPORT fex_type_t fex_type ( const fex_t* fe ) { return fe->type(); }
|
||||
BLARGG_EXPORT int fex_done ( const fex_t* fe ) { return fe->done(); }
|
||||
BLARGG_EXPORT const char* fex_name ( const fex_t* fe ) { return fe->name(); }
|
||||
BLARGG_EXPORT const blargg_wchar_t* fex_wname ( const fex_t* fe ) { return fe->wname(); }
|
||||
BLARGG_EXPORT uint64_t fex_size ( const fex_t* fe ) { return fe->size(); }
|
||||
BLARGG_EXPORT unsigned fex_dos_date ( const fex_t* fe ) { return fe->dos_date(); }
|
||||
BLARGG_EXPORT unsigned fex_crc32 ( const fex_t* fe ) { return fe->crc32(); }
|
||||
BLARGG_EXPORT fex_err_t fex_stat ( fex_t* fe ) { return fe->stat(); }
|
||||
BLARGG_EXPORT fex_err_t fex_next ( fex_t* fe ) { return fe->next(); }
|
||||
BLARGG_EXPORT fex_err_t fex_rewind ( fex_t* fe ) { return fe->rewind(); }
|
||||
BLARGG_EXPORT uint64_t fex_tell ( const fex_t* fe ) { return fe->tell(); }
|
||||
BLARGG_EXPORT fex_pos_t fex_tell_arc ( const fex_t* fe ) { return fe->tell_arc(); }
|
||||
BLARGG_EXPORT fex_err_t fex_seek_arc ( fex_t* fe, fex_pos_t pos ) { return fe->seek_arc( pos ); }
|
||||
BLARGG_EXPORT const char* fex_type_extension ( fex_type_t t ) { return t->extension; }
|
||||
BLARGG_EXPORT const char* fex_type_name ( fex_type_t t ) { return t->name; }
|
||||
BLARGG_EXPORT fex_err_t fex_data ( fex_t* fe, const void** data_out ) { return fe->data( data_out ); }
|
||||
BLARGG_EXPORT const char* fex_err_str ( fex_err_t err ) { return blargg_err_str( err ); }
|
|
@ -0,0 +1,206 @@
|
|||
/** Uniform access to zip, gzip, 7-zip, and RAR compressed archives \file */
|
||||
|
||||
/* File_Extractor 1.0.0 */
|
||||
#ifndef FEX_H
|
||||
#define FEX_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** First parameter of most functions is fex_t*, or const fex_t* if nothing is
|
||||
changed. Once one of these functions returns an error, the archive should not
|
||||
be used any further, other than to close it. One exception is
|
||||
fex_error_file_eof; the archive may still be used after this. */
|
||||
typedef struct fex_t fex_t;
|
||||
|
||||
/** Pointer to error, or NULL if function was successful. See error functions
|
||||
below. */
|
||||
#ifndef fex_err_t /* (#ifndef allows better testing of library) */
|
||||
typedef const char* fex_err_t;
|
||||
#endif
|
||||
|
||||
|
||||
/**** File types ****/
|
||||
|
||||
/** Archive file type identifier. Can also hold NULL. */
|
||||
typedef const struct fex_type_t_* fex_type_t;
|
||||
|
||||
/** Array of supported types, with NULL at end */
|
||||
const fex_type_t* fex_type_list( void );
|
||||
|
||||
/** Name of this archive type, e.g. "ZIP archive", "file" */
|
||||
const char* fex_type_name( fex_type_t );
|
||||
|
||||
/** Usual file extension for type, e.g. ".zip", ".7z". For binary file type,
|
||||
returns "", since it can open any file. */
|
||||
const char* fex_type_extension( fex_type_t );
|
||||
|
||||
|
||||
/**** Wide-character file paths ****/
|
||||
|
||||
/** Converts wide-character path to form suitable for use with fex functions. */
|
||||
char* fex_wide_to_path( const blargg_wchar_t* wide );
|
||||
|
||||
/** Frees converted path. OK to pass NULL. */
|
||||
void fex_free_path( char* );
|
||||
|
||||
|
||||
/**** Identification ****/
|
||||
|
||||
/** True if str ends in extension. If extension is "", always returns true.
|
||||
Converts str to lowercase before comparison, so extension should ALREADY be
|
||||
lowercase (i.e. pass ".zip", NOT ".ZIP"). */
|
||||
int fex_has_extension( const char str [], const char extension [] );
|
||||
|
||||
/** Determines type based on first fex_identify_header_size bytes of file.
|
||||
Returns usual file extension this should have (e.g. ".zip", ".gz", etc.).
|
||||
Returns "" if file header is not recognized. */
|
||||
const char* fex_identify_header( const void* header );
|
||||
enum { fex_identify_header_size = 16 };
|
||||
|
||||
/** Determines type based on extension of a file path, or just a lone extension
|
||||
(must include '.', e.g. ".zip", NOT just "zip"). Returns NULL if extension is
|
||||
for an unsupported type (e.g. ".lzh"). */
|
||||
fex_type_t fex_identify_extension( const char path_or_extension [] );
|
||||
|
||||
/** Determines type based on filename extension and/or file header. Sets *out
|
||||
to determined type, or NULL if type is not supported. */
|
||||
fex_err_t fex_identify_file( fex_type_t* out, const char path [] );
|
||||
|
||||
/** Type of an already-opened archive */
|
||||
fex_type_t fex_type( const fex_t* );
|
||||
|
||||
|
||||
/**** Open/close ****/
|
||||
|
||||
/** Initializes static tables used by library. Automatically called by
|
||||
fex_open(). OK to call more than once. */
|
||||
fex_err_t fex_init( void );
|
||||
|
||||
/** Opens archive and points *out at it. If error, sets *out to NULL. */
|
||||
fex_err_t fex_open( fex_t** out, const char path [] );
|
||||
|
||||
/** Opens archive of specified type and sets *out. Returns error if file is not
|
||||
of that archive type. If error, sets *out to NULL. */
|
||||
fex_err_t fex_open_type( fex_t** out, const char path [], fex_type_t );
|
||||
|
||||
/** Closes archive and frees memory. OK to pass NULL. */
|
||||
void fex_close( fex_t* );
|
||||
|
||||
|
||||
/**** Scanning ****/
|
||||
|
||||
/** True if at end of archive. Must be called after fex_open() or fex_rewind(),
|
||||
as an archive might contain no files. */
|
||||
int fex_done( const fex_t* );
|
||||
|
||||
/** Goes to next file in archive. If there are no more files, fex_done() will
|
||||
now return true. */
|
||||
fex_err_t fex_next( fex_t* );
|
||||
|
||||
/** Goes back to first file in archive, as if it were just opened with
|
||||
fex_open() */
|
||||
fex_err_t fex_rewind( fex_t* );
|
||||
|
||||
/** Saved position in archive. Can also store zero. */
|
||||
typedef uint64_t fex_pos_t;
|
||||
|
||||
/** Position of current file in archive. Never returns zero. */
|
||||
fex_pos_t fex_tell_arc( const fex_t* );
|
||||
|
||||
/** Returns to file at previously-saved position */
|
||||
fex_err_t fex_seek_arc( fex_t*, fex_pos_t );
|
||||
|
||||
|
||||
/**** Info ****/
|
||||
|
||||
/** Name of current file */
|
||||
const char* fex_name( const fex_t* );
|
||||
|
||||
/** Wide-character name of current file, or NULL if unavailable */
|
||||
const blargg_wchar_t* fex_wname( const fex_t* );
|
||||
|
||||
/** Makes further information available for file */
|
||||
fex_err_t fex_stat( fex_t* );
|
||||
|
||||
/** Size of current file. fex_stat() or fex_data() must have been called. */
|
||||
uint64_t fex_size( const fex_t* );
|
||||
|
||||
/** Modification date of current file (MS-DOS format), or 0 if unavailable.
|
||||
fex_stat() must have been called. */
|
||||
unsigned int fex_dos_date( const fex_t* );
|
||||
|
||||
/** CRC-32 checksum of current file's contents, or 0 if unavailable. Doesn't
|
||||
require calculation; simply gets it from file's header. fex_stat() must have
|
||||
been called. */
|
||||
unsigned int fex_crc32( const fex_t* );
|
||||
|
||||
|
||||
/**** Extraction ****/
|
||||
|
||||
/** Reads n bytes from current file. Reading past end of file results in
|
||||
fex_err_file_eof. */
|
||||
fex_err_t fex_read( fex_t*, void* out, int n );
|
||||
|
||||
/** Number of bytes read from current file */
|
||||
uint64_t fex_tell( const fex_t* );
|
||||
|
||||
/** Points *out at current file's data in memory. Pointer is valid until
|
||||
fex_next(), fex_rewind(), fex_seek_arc(), or fex_close() is called. Pointer
|
||||
must NOT be freed(); library frees it automatically. If error, sets *out to
|
||||
NULL. */
|
||||
fex_err_t fex_data( fex_t*, const void** out );
|
||||
|
||||
|
||||
/**** Errors ****/
|
||||
|
||||
/** Error string associated with err. Returns "" if err is NULL. Returns err
|
||||
unchanged if it isn't a fex_err_t returned by library. */
|
||||
const char* fex_err_str( fex_err_t err );
|
||||
|
||||
/** Details of error beyond main cause, or "" if none or err is NULL. Returns
|
||||
err unchanged if it isn't a fex_err_t returned by library. */
|
||||
const char* fex_err_details( fex_err_t err );
|
||||
|
||||
/** Numeric code corresponding to err. Returns fex_ok if err is NULL. Returns
|
||||
fex_err_generic if err isn't a fex_err_t returned by library. */
|
||||
int fex_err_code( fex_err_t err );
|
||||
|
||||
enum {
|
||||
fex_ok = 0,/**< Successful call. Guaranteed to be zero. */
|
||||
fex_err_generic = 0x01,/**< Error of unspecified type */
|
||||
fex_err_memory = 0x02,/**< Out of memory */
|
||||
fex_err_caller = 0x03,/**< Caller called function with bad args */
|
||||
fex_err_internal = 0x04,/**< Internal problem, bug, etc. */
|
||||
fex_err_limitation = 0x05,/**< Exceeded program limit */
|
||||
|
||||
fex_err_file_missing = 0x20,/**< File not found at specified path */
|
||||
fex_err_file_read = 0x21,/**< Couldn't open file for reading */
|
||||
fex_err_file_io = 0x23,/**< Read/write error */
|
||||
fex_err_file_eof = 0x25,/**< Tried to read past end of file */
|
||||
|
||||
fex_err_file_type = 0x30,/**< File is of wrong type */
|
||||
fex_err_file_feature = 0x32,/**< File requires unsupported feature */
|
||||
fex_err_file_corrupt = 0x33 /**< File is corrupt */
|
||||
};
|
||||
|
||||
/** fex_err_t corresponding to numeric code. Note that this might not recover
|
||||
the original fex_err_t before it was converted to a numeric code; in
|
||||
particular, fex_err_details(fex_code_to_err(code)) will be "" in most cases. */
|
||||
fex_err_t fex_code_to_err( int code );
|
||||
|
||||
|
||||
/* Deprecated */
|
||||
typedef fex_t File_Extractor;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libquicknes", "libquicknes.vcxproj", "{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\fex\blargg_common.cpp" />
|
||||
<ClCompile Include="..\fex\blargg_errors.cpp" />
|
||||
<ClCompile Include="..\fex\Data_Reader.cpp" />
|
||||
<ClCompile Include="..\nes_emu\abstract_file.cpp" />
|
||||
<ClCompile Include="..\nes_emu\apu_state.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Blip_Buffer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Effects_Buffer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Mapper_Fme7.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Mapper_Mmc5.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Mapper_Namco106.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Mapper_Vrc6.cpp" />
|
||||
<ClCompile Include="..\nes_emu\misc_mappers.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Multi_Buffer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Apu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Buffer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Cart.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Core.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Cpu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\nes_data.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Effects_Buffer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Emu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_File.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Film.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Film_Data.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Film_Packer.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Fme7_Apu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Mapper.cpp" />
|
||||
<ClCompile Include="..\nes_emu\nes_mappers.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Mmc1.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Mmc3.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Namco_Apu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Oscs.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu_Impl.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu_Rendering.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Recorder.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_State.cpp" />
|
||||
<ClCompile Include="..\nes_emu\nes_util.cpp" />
|
||||
<ClCompile Include="..\nes_emu\Nes_Vrc6_Apu.cpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}</ProjectGuid>
|
||||
<RootNamespace>libquicknes</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);BLARGG_SOURCE_BEGIN="blargg_common.h";BLARGG_ENABLE_OPTIMIZER="blargg_common.h"</PreprocessorDefinitions>
|
||||
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);BLARGG_SOURCE_BEGIN="blargg_common.h";BLARGG_ENABLE_OPTIMIZER="blargg_common.h"</PreprocessorDefinitions>
|
||||
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -0,0 +1,148 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\fex">
|
||||
<UniqueIdentifier>{ba7f113f-5234-44b7-a84c-35ad9dd39db2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\nes_emu">
|
||||
<UniqueIdentifier>{704585a9-2165-422f-8457-b6c5ffe9179d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\fex">
|
||||
<UniqueIdentifier>{109f60b9-f5f8-4f85-807d-b0c977848674}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\nes_emu">
|
||||
<UniqueIdentifier>{278454e6-01bf-4140-8707-bc2a4512c2ac}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\nes_emu\abstract_file.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\apu_state.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Blip_Buffer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Effects_Buffer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Mapper_Fme7.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Mapper_Mmc5.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Mapper_Namco106.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Mapper_Vrc6.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\misc_mappers.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Multi_Buffer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Apu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Buffer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Cart.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Core.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Cpu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\nes_data.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Effects_Buffer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Emu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_File.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Film.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Film_Data.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Film_Packer.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Fme7_Apu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Mapper.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\nes_mappers.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Mmc1.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Mmc3.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Namco_Apu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Oscs.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu_Impl.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Ppu_Rendering.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Recorder.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_State.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\nes_util.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\nes_emu\Nes_Vrc6_Apu.cpp">
|
||||
<Filter>Source Files\nes_emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\fex\blargg_common.cpp">
|
||||
<Filter>Source Files\fex</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\fex\blargg_errors.cpp">
|
||||
<Filter>Source Files\fex</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\fex\Data_Reader.cpp">
|
||||
<Filter>Source Files\fex</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,216 @@
|
|||
DEBUG = 0
|
||||
|
||||
ifeq ($(platform),)
|
||||
platform = unix
|
||||
ifeq ($(shell uname -a),)
|
||||
platform = win
|
||||
else ifneq ($(findstring MINGW,$(shell uname -a)),)
|
||||
platform = win
|
||||
else ifneq ($(findstring Darwin,$(shell uname -a)),)
|
||||
platform = osx
|
||||
else ifneq ($(findstring win,$(shell uname -a)),)
|
||||
platform = win
|
||||
endif
|
||||
endif
|
||||
|
||||
# system platform
|
||||
system_platform = unix
|
||||
ifeq ($(shell uname -a),)
|
||||
EXE_EXT = .exe
|
||||
system_platform = win
|
||||
else ifneq ($(findstring Darwin,$(shell uname -a)),)
|
||||
system_platform = osx
|
||||
else ifneq ($(findstring MINGW,$(shell uname -a)),)
|
||||
system_platform = win
|
||||
endif
|
||||
|
||||
LIBRETRO_DIR := libretro
|
||||
TARGET_NAME := quicknes
|
||||
|
||||
ifeq ($(platform), unix)
|
||||
TARGET := $(TARGET_NAME)_libretro.so
|
||||
fpic := -fPIC
|
||||
SHARED := -shared -Wl,-version-script=link.T -Wl,-no-undefined
|
||||
else ifeq ($(platform), osx)
|
||||
TARGET := $(TARGET_NAME)_libretro.dylib
|
||||
fpic := -fPIC
|
||||
SHARED := -dynamiclib
|
||||
else ifeq ($(platform), ios)
|
||||
TARGET := $(TARGET_NAME)_libretro_ios.dylib
|
||||
fpic := -fPIC
|
||||
SHARED := -dynamiclib
|
||||
|
||||
CC = clang -arch armv7 -isysroot $(IOSSDK) -miphoneos-version-min=5.0
|
||||
CXX = clang++ -arch armv7 -isysroot $(IOSSDK) -miphoneos-version-min=5.0
|
||||
PLATFORM_DEFINES += -DIOS -miphoneos-version-min=5.0
|
||||
else ifeq ($(platform), qnx)
|
||||
TARGET := $(TARGET_NAME)_libretro_qnx.so
|
||||
fpic := -fPIC
|
||||
SHARED := -shared -Wl,--version-script=link.T
|
||||
|
||||
CC = qcc -Vgcc_ntoarmv7le
|
||||
CXX = QCC -Vgcc_ntoarmv7le
|
||||
AR = QCC -Vgcc_ntoarmv7le
|
||||
PLATFORM_DEFINES += -D__BLACKBERRY_QNX__
|
||||
else ifeq ($(platform), ps3)
|
||||
TARGET := $(TARGET_NAME)_libretro_ps3.a
|
||||
CXX = $(CELL_SDK)/host-win32/ppu/bin/ppu-lv2-g++.exe
|
||||
AR = $(CELL_SDK)/host-win32/ppu/bin/ppu-lv2-ar.exe
|
||||
PLATFORM_DEFINES := -D__CELLOS_LV2__
|
||||
STATIC_LINKING = 1
|
||||
else ifeq ($(platform), sncps3)
|
||||
TARGET := $(TARGET_NAME)_libretro_ps3.a
|
||||
CC = $(CELL_SDK)/host-win32/sn/bin/ps3ppusnc.exe
|
||||
CXX = $(CELL_SDK)/host-win32/sn/bin/ps3ppusnc.exe
|
||||
AR = $(CELL_SDK)/host-win32/sn/bin/ps3snarl.exe
|
||||
PLATFORM_DEFINES := -D__CELLOS_LV2__
|
||||
STATIC_LINKING = 1
|
||||
else ifeq ($(platform), psl1ght)
|
||||
TARGET := $(TARGET_NAME)_libretro_psl1ght.a
|
||||
CC = $(PS3DEV)/ppu/bin/ppu-gcc$(EXE_EXT)
|
||||
CXX = $(PS3DEV)/ppu/bin/ppu-g++$(EXE_EXT)
|
||||
AR = $(PS3DEV)/ppu/bin/ppu-ar$(EXE_EXT)
|
||||
PLATFORM_DEFINES := -D__CELLOS_LV2__
|
||||
STATIC_LINKING = 1
|
||||
else ifeq ($(platform), xenon)
|
||||
TARGET := $(TARGET_NAME)_libretro_xenon360.a
|
||||
CC = xenon-gcc$(EXE_EXT)
|
||||
CXX = xenon-g++$(EXE_EXT)
|
||||
AR = xenon-ar$(EXE_EXT)
|
||||
PLATFORM_DEFINES := -D__LIBXENON__
|
||||
STATIC_LINKING = 1
|
||||
else ifeq ($(platform), ngc)
|
||||
TARGET := $(TARGET_NAME)_libretro_ngc.a
|
||||
CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT)
|
||||
CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT)
|
||||
AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
|
||||
PLATFORM_DEFINES += -DGEKKO -DHW_DOL -mrvl -mcpu=750 -meabi -mhard-float
|
||||
STATIC_LINKING = 1
|
||||
else ifeq ($(platform), wii)
|
||||
TARGET := $(TARGET_NAME)_libretro_wii.a
|
||||
CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT)
|
||||
CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT)
|
||||
AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
|
||||
PLATFORM_DEFINES += -DGEKKO -DHW_RVL -mrvl -mcpu=750 -meabi -mhard-float
|
||||
STATIC_LINKING = 1
|
||||
else ifneq (,$(findstring armv,$(platform)))
|
||||
TARGET := $(TARGET_NAME)_libretro.so
|
||||
fpic := -fPIC
|
||||
SHARED := -shared -Wl,-version-script=link.T -Wl,-no-undefined
|
||||
CC = gcc
|
||||
ifneq (,$(findstring cortexa8,$(platform)))
|
||||
PLATFORM_DEFINES += -marm -mcpu=cortex-a8
|
||||
else ifneq (,$(findstring cortexa9,$(platform)))
|
||||
PLATFORM_DEFINES += -marm -mcpu=cortex-a9
|
||||
endif
|
||||
PLATFORM_DEFINES += -marm
|
||||
ifneq (,$(findstring neon,$(platform)))
|
||||
PLATFORM_DEFINES += -mfpu=neon
|
||||
HAVE_NEON = 1
|
||||
endif
|
||||
ifneq (,$(findstring softfloat,$(platform)))
|
||||
PLATFORM_DEFINES += -mfloat-abi=softfp
|
||||
else ifneq (,$(findstring hardfloat,$(platform)))
|
||||
PLATFORM_DEFINES += -mfloat-abi=hard
|
||||
endif
|
||||
PLATFORM_DEFINES += -DARM
|
||||
else
|
||||
TARGET := $(TARGET_NAME)_libretro.dll
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
SHARED := -shared -static-libgcc -static-libstdc++ -Wl,-no-undefined -Wl,-version-script=link.T
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CFLAGS += -O0 -g
|
||||
CXXFLAGS += -O0 -g
|
||||
else
|
||||
CFLAGS += -O3
|
||||
CXXFLAGS += -O3
|
||||
endif
|
||||
|
||||
EMU_DIR := ../nes_emu
|
||||
|
||||
CXXSRCS := \
|
||||
$(EMU_DIR)/abstract_file.cpp \
|
||||
$(EMU_DIR)/apu_state.cpp \
|
||||
$(EMU_DIR)/Blip_Buffer.cpp \
|
||||
$(EMU_DIR)/Effects_Buffer.cpp \
|
||||
$(EMU_DIR)/Mapper_Fme7.cpp \
|
||||
$(EMU_DIR)/Mapper_Mmc5.cpp \
|
||||
$(EMU_DIR)/Mapper_Namco106.cpp \
|
||||
$(EMU_DIR)/Mapper_Vrc6.cpp \
|
||||
$(EMU_DIR)/misc_mappers.cpp \
|
||||
$(EMU_DIR)/Multi_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Cart.cpp \
|
||||
$(EMU_DIR)/Nes_Core.cpp \
|
||||
$(EMU_DIR)/Nes_Cpu.cpp \
|
||||
$(EMU_DIR)/nes_data.cpp \
|
||||
$(EMU_DIR)/Nes_Effects_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Emu.cpp \
|
||||
$(EMU_DIR)/Nes_File.cpp \
|
||||
$(EMU_DIR)/Nes_Film.cpp \
|
||||
$(EMU_DIR)/Nes_Film_Data.cpp \
|
||||
$(EMU_DIR)/Nes_Film_Packer.cpp \
|
||||
$(EMU_DIR)/Nes_Fme7_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Mapper.cpp \
|
||||
$(EMU_DIR)/nes_mappers.cpp \
|
||||
$(EMU_DIR)/Nes_Mmc1.cpp \
|
||||
$(EMU_DIR)/Nes_Mmc3.cpp \
|
||||
$(EMU_DIR)/Nes_Namco_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Oscs.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu_Impl.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu_Rendering.cpp \
|
||||
$(EMU_DIR)/Nes_Recorder.cpp \
|
||||
$(EMU_DIR)/Nes_State.cpp \
|
||||
$(EMU_DIR)/nes_util.cpp \
|
||||
$(EMU_DIR)/Nes_Vrc6_Apu.cpp \
|
||||
libretro.cpp
|
||||
|
||||
LIBSRCS := \
|
||||
../fex/Data_Reader.cpp \
|
||||
../fex/blargg_errors.cpp \
|
||||
../fex/blargg_common.cpp
|
||||
|
||||
CXXOBJ := $(CXXSRCS:.cpp=.o) $(LIBSRCS:.cpp=.o)
|
||||
|
||||
OBJS := $(CXXOBJ)
|
||||
|
||||
DEFINES := -D__LIBRETRO__ $(PLATFORM_DEFINES) -Wall -Wno-multichar -Wno-unused-variable -Wno-sign-compare -DNDEBUG \
|
||||
-DSTD_AUTO_FILE_WRITER=Std_File_Writer \
|
||||
-DSTD_AUTO_FILE_READER=Std_File_Reader \
|
||||
-DSTD_AUTO_FILE_COMP_READER=Std_File_Reader \
|
||||
-DSTD_AUTO_FILE_COMP_WRITER=Std_File_Writer
|
||||
|
||||
CFLAGS += $(fpic) $(DEFINES) -std=gnu99
|
||||
CXXFLAGS += $(fpic) $(DEFINES)
|
||||
|
||||
INCDIRS := -I$(EMU_DIR) -I. -I..
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
ifeq ($(STATIC_LINKING), 1)
|
||||
$(AR) rcs $@ $(OBJS)
|
||||
else
|
||||
$(CXX) -o $@ $(SHARED) $(OBJS) $(LDFLAGS) $(LIBS)
|
||||
endif
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS)
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS)
|
||||
|
||||
clean-objs:
|
||||
rm -f $(OBJS)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS)
|
||||
rm -f $(TARGET)
|
||||
|
||||
.PHONY: clean clean-objs
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
ifeq ($(TARGET_ARCH),arm)
|
||||
LOCAL_CFLAGS += -DANDROID_ARM
|
||||
LOCAL_ARM_MODE := arm
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET_ARCH),x86)
|
||||
LOCAL_CFLAGS += -DANDROID_X86
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET_ARCH),mips)
|
||||
LOCAL_CFLAGS += -DANDROID_MIPS
|
||||
endif
|
||||
|
||||
LOCAL_MODULE := libretro
|
||||
|
||||
EMU_DIR := ../../nes_emu
|
||||
LIBRETRO_DIR := ../
|
||||
|
||||
CXXSRCS := \
|
||||
$(EMU_DIR)/abstract_file.cpp \
|
||||
$(EMU_DIR)/apu_state.cpp \
|
||||
$(EMU_DIR)/Blip_Buffer.cpp \
|
||||
$(EMU_DIR)/Effects_Buffer.cpp \
|
||||
$(EMU_DIR)/Mapper_Fme7.cpp \
|
||||
$(EMU_DIR)/Mapper_Mmc5.cpp \
|
||||
$(EMU_DIR)/Mapper_Namco106.cpp \
|
||||
$(EMU_DIR)/Mapper_Vrc6.cpp \
|
||||
$(EMU_DIR)/misc_mappers.cpp \
|
||||
$(EMU_DIR)/Multi_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Cart.cpp \
|
||||
$(EMU_DIR)/Nes_Core.cpp \
|
||||
$(EMU_DIR)/Nes_Cpu.cpp \
|
||||
$(EMU_DIR)/nes_data.cpp \
|
||||
$(EMU_DIR)/Nes_Effects_Buffer.cpp \
|
||||
$(EMU_DIR)/Nes_Emu.cpp \
|
||||
$(EMU_DIR)/Nes_File.cpp \
|
||||
$(EMU_DIR)/Nes_Film.cpp \
|
||||
$(EMU_DIR)/Nes_Film_Data.cpp \
|
||||
$(EMU_DIR)/Nes_Film_Packer.cpp \
|
||||
$(EMU_DIR)/Nes_Fme7_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Mapper.cpp \
|
||||
$(EMU_DIR)/nes_mappers.cpp \
|
||||
$(EMU_DIR)/Nes_Mmc1.cpp \
|
||||
$(EMU_DIR)/Nes_Mmc3.cpp \
|
||||
$(EMU_DIR)/Nes_Namco_Apu.cpp \
|
||||
$(EMU_DIR)/Nes_Oscs.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu_Impl.cpp \
|
||||
$(EMU_DIR)/Nes_Ppu_Rendering.cpp \
|
||||
$(EMU_DIR)/Nes_Recorder.cpp \
|
||||
$(EMU_DIR)/Nes_State.cpp \
|
||||
$(EMU_DIR)/nes_util.cpp \
|
||||
$(EMU_DIR)/Nes_Vrc6_Apu.cpp \
|
||||
$(LIBRETRO_DIR)/libretro.cpp
|
||||
|
||||
LIBSRCS := \
|
||||
$(LIBRETRO_DIR)/../fex/Data_Reader.cpp \
|
||||
$(LIBRETRO_DIR)/../fex/blargg_errors.cpp \
|
||||
$(LIBRETRO_DIR)/../fex/blargg_common.cpp
|
||||
|
||||
LOCAL_SRC_FILES = $(CXXSRCS) $(LIBSRCS)
|
||||
LOCAL_CXXFLAGS = -DANDROID -D__LIBRETRO__ -Wall -Wno-multichar -Wno-unused-variable -Wno-sign-compare -DNDEBUG \
|
||||
-DSTD_AUTO_FILE_WRITER=Std_File_Writer \
|
||||
-DSTD_AUTO_FILE_READER=Std_File_Reader \
|
||||
-DSTD_AUTO_FILE_COMP_READER=Std_File_Reader \
|
||||
-DSTD_AUTO_FILE_COMP_WRITER=Std_File_Writer
|
||||
LOCAL_C_INCLUDES = $(LIBRETRO_DIR) $(EMU_DIR) $(EMU_DIR)/..
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
|
@ -0,0 +1,2 @@
|
|||
APP_STL := gnustl_static
|
||||
APP_ABI := all
|
|
@ -0,0 +1,267 @@
|
|||
#include "libretro.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "Nes_Emu.h"
|
||||
#include "fex/Data_Reader.h"
|
||||
#include "abstract_file.h"
|
||||
|
||||
static Nes_Emu *emu;
|
||||
|
||||
void retro_init(void)
|
||||
{
|
||||
delete emu;
|
||||
emu = new Nes_Emu;
|
||||
}
|
||||
|
||||
void retro_deinit(void)
|
||||
{
|
||||
delete emu;
|
||||
emu = 0;
|
||||
}
|
||||
|
||||
unsigned retro_api_version(void)
|
||||
{
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(unsigned, unsigned)
|
||||
{
|
||||
}
|
||||
|
||||
void retro_get_system_info(struct retro_system_info *info)
|
||||
{
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->library_name = "QuickNES";
|
||||
info->library_version = "v1";
|
||||
info->need_fullpath = false;
|
||||
info->valid_extensions = "nes"; // Anything is fine, we don't care.
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(struct retro_system_av_info *info)
|
||||
{
|
||||
const retro_system_timing timing = { Nes_Emu::frame_rate, 44100.0 };
|
||||
info->timing = timing;
|
||||
|
||||
const retro_game_geometry geom = {
|
||||
Nes_Emu::image_width,
|
||||
Nes_Emu::image_height,
|
||||
Nes_Emu::image_width,
|
||||
Nes_Emu::image_height,
|
||||
4.0 / 3.0,
|
||||
};
|
||||
info->geometry = geom;
|
||||
}
|
||||
|
||||
static retro_video_refresh_t video_cb;
|
||||
static retro_audio_sample_t audio_cb;
|
||||
static retro_audio_sample_batch_t audio_batch_cb;
|
||||
static retro_environment_t environ_cb;
|
||||
static retro_input_poll_t input_poll_cb;
|
||||
static retro_input_state_t input_state_cb;
|
||||
|
||||
void retro_set_environment(retro_environment_t cb)
|
||||
{
|
||||
environ_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t cb)
|
||||
{
|
||||
audio_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
||||
{
|
||||
audio_batch_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t cb)
|
||||
{
|
||||
input_poll_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t cb)
|
||||
{
|
||||
input_state_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb)
|
||||
{
|
||||
video_cb = cb;
|
||||
}
|
||||
|
||||
void retro_reset(void)
|
||||
{
|
||||
if (emu)
|
||||
emu->reset();
|
||||
}
|
||||
|
||||
#define JOY_A 1
|
||||
#define JOY_B 2
|
||||
#define JOY_SELECT 4
|
||||
#define JOY_START 8
|
||||
#define JOY_UP 0x10
|
||||
#define JOY_DOWN 0x20
|
||||
#define JOY_LEFT 0x40
|
||||
#define JOY_RIGHT 0x80
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned retro;
|
||||
unsigned nes;
|
||||
} keymap;
|
||||
|
||||
static const keymap bindmap[] = {
|
||||
{ RETRO_DEVICE_ID_JOYPAD_A, JOY_A },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_B, JOY_B },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_SELECT, JOY_SELECT },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_START, JOY_START },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_UP, JOY_UP },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_DOWN, JOY_DOWN },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_LEFT, JOY_LEFT },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_RIGHT, JOY_RIGHT },
|
||||
};
|
||||
|
||||
static void update_input(int pads[2])
|
||||
{
|
||||
pads[0] = pads[1] = 0;
|
||||
input_poll_cb();
|
||||
|
||||
for (unsigned p = 0; p < 2; p++)
|
||||
for (unsigned bind = 0; bind < sizeof(bindmap) / sizeof(bindmap[0]); bind++)
|
||||
pads[p] |= input_state_cb(p, RETRO_DEVICE_JOYPAD, 0, bindmap[bind].retro) ? bindmap[bind].nes : 0;
|
||||
}
|
||||
|
||||
void retro_run(void)
|
||||
{
|
||||
int pads[2] = {0};
|
||||
update_input(pads);
|
||||
|
||||
emu->emulate_frame(pads[0], pads[1]);
|
||||
const Nes_Emu::frame_t &frame = emu->frame();
|
||||
|
||||
static uint32_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
|
||||
|
||||
const uint8_t *in_pixels = frame.pixels;
|
||||
uint32_t *out_pixels = video_buffer;
|
||||
|
||||
for (unsigned h = 0; h < Nes_Emu::image_height;
|
||||
h++, in_pixels += frame.pitch, out_pixels += Nes_Emu::image_width)
|
||||
{
|
||||
for (unsigned w = 0; w < Nes_Emu::image_width; w++)
|
||||
{
|
||||
unsigned col = frame.palette[in_pixels[w]];
|
||||
const Nes_Emu::rgb_t& rgb = emu->nes_colors[col];
|
||||
unsigned r = rgb.red;
|
||||
unsigned g = rgb.green;
|
||||
unsigned b = rgb.blue;
|
||||
out_pixels[w] = (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
}
|
||||
|
||||
video_cb(video_buffer, Nes_Emu::image_width, Nes_Emu::image_height,
|
||||
Nes_Emu::image_width * sizeof(uint32_t));
|
||||
|
||||
// Mono -> Stereo.
|
||||
int16_t samples[2048];
|
||||
long read_samples = emu->read_samples(samples, 2048);
|
||||
int16_t out_samples[4096];
|
||||
for (long i = 0; i < read_samples; i++)
|
||||
out_samples[(i << 1)] = out_samples[(i << 1) + 1] = samples[i];
|
||||
|
||||
audio_batch_cb(out_samples, read_samples);
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info *info)
|
||||
{
|
||||
if (!emu)
|
||||
return false;
|
||||
|
||||
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
||||
{
|
||||
fprintf(stderr, "XRGB8888 is not supported.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
emu->set_sample_rate(44100);
|
||||
emu->set_equalizer(Nes_Emu::nes_eq);
|
||||
emu->set_palette_range(0);
|
||||
|
||||
static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
|
||||
emu->set_pixels(video_buffer, Nes_Emu::image_width);
|
||||
|
||||
Mem_File_Reader reader(info->data, info->size);
|
||||
return !emu->load_ines(reader);
|
||||
}
|
||||
|
||||
void retro_unload_game(void)
|
||||
{
|
||||
emu->close();
|
||||
}
|
||||
|
||||
unsigned retro_get_region(void)
|
||||
{
|
||||
return RETRO_REGION_NTSC;
|
||||
}
|
||||
|
||||
bool retro_load_game_special(unsigned, const struct retro_game_info *, size_t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void)
|
||||
{
|
||||
Mem_Writer writer;
|
||||
if (emu->save_state(writer))
|
||||
return 0;
|
||||
|
||||
return writer.size();
|
||||
}
|
||||
|
||||
bool retro_serialize(void *data, size_t size)
|
||||
{
|
||||
Mem_Writer writer(data, size);
|
||||
return !emu->save_state(writer);
|
||||
}
|
||||
|
||||
bool retro_unserialize(const void *data, size_t size)
|
||||
{
|
||||
Mem_File_Reader reader(data, size);
|
||||
return !emu->load_state(reader);
|
||||
}
|
||||
|
||||
void *retro_get_memory_data(unsigned id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case RETRO_MEMORY_SAVE_RAM:
|
||||
return emu->high_mem();
|
||||
case RETRO_MEMORY_SYSTEM_RAM:
|
||||
return emu->low_mem();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case RETRO_MEMORY_SAVE_RAM:
|
||||
return Nes_Emu::high_mem_size;
|
||||
case RETRO_MEMORY_SYSTEM_RAM:
|
||||
return Nes_Emu::low_mem_size;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void retro_cheat_reset(void)
|
||||
{}
|
||||
|
||||
void retro_cheat_set(unsigned, bool, const char *)
|
||||
{}
|
||||
|
|
@ -0,0 +1,758 @@
|
|||
#ifndef LIBRETRO_H__
|
||||
#define LIBRETRO_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
|
||||
// Hack applied for MSVC when compiling in C89 mode as it isn't C99 compliant.
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
#if defined(_MSC_VER) && !defined(SN_TARGET_PS3) && !defined(__cplusplus)
|
||||
#define bool unsigned char
|
||||
#define true 1
|
||||
#define false 0
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Used for checking API/ABI mismatches that can break libretro implementations.
|
||||
// It is not incremented for compatible changes to the API.
|
||||
#define RETRO_API_VERSION 1
|
||||
|
||||
// Libretro's fundamental device abstractions.
|
||||
#define RETRO_DEVICE_MASK 0xff
|
||||
#define RETRO_DEVICE_NONE 0
|
||||
|
||||
// The JOYPAD is called RetroPad. It is essentially a Super Nintendo controller,
|
||||
// but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock.
|
||||
#define RETRO_DEVICE_JOYPAD 1
|
||||
|
||||
// The mouse is a simple mouse, similar to Super Nintendo's mouse.
|
||||
// X and Y coordinates are reported relatively to last poll (poll callback).
|
||||
// It is up to the libretro implementation to keep track of where the mouse pointer is supposed to be on the screen.
|
||||
// The frontend must make sure not to interfere with its own hardware mouse pointer.
|
||||
#define RETRO_DEVICE_MOUSE 2
|
||||
|
||||
// KEYBOARD device lets one poll for raw key pressed.
|
||||
// It is poll based, so input callback will return with the current pressed state.
|
||||
#define RETRO_DEVICE_KEYBOARD 3
|
||||
|
||||
// Lightgun X/Y coordinates are reported relatively to last poll, similar to mouse.
|
||||
#define RETRO_DEVICE_LIGHTGUN 4
|
||||
|
||||
// The ANALOG device is an extension to JOYPAD (RetroPad).
|
||||
// Similar to DualShock it adds two analog sticks.
|
||||
// This is treated as a separate device type as it returns values in the full analog range
|
||||
// of [-0x8000, 0x7fff]. Positive X axis is right. Positive Y axis is down.
|
||||
// Only use ANALOG type when polling for analog values of the axes.
|
||||
#define RETRO_DEVICE_ANALOG 5
|
||||
|
||||
// Abstracts the concept of a pointing mechanism, e.g. touch.
|
||||
// This allows libretro to query in absolute coordinates where on the screen a mouse (or something similar) is being placed.
|
||||
// For a touch centric device, coordinates reported are the coordinates of the press.
|
||||
//
|
||||
// Coordinates in X and Y are reported as:
|
||||
// [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen,
|
||||
// and 0x7fff corresponds to the far right/bottom of the screen.
|
||||
// The "screen" is here defined as area that is passed to the frontend and later displayed on the monitor.
|
||||
// The frontend is free to scale/resize this screen as it sees fit, however,
|
||||
// (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the game image, etc.
|
||||
//
|
||||
// To check if the pointer coordinates are valid (e.g. a touch display actually being touched),
|
||||
// PRESSED returns 1 or 0.
|
||||
// If using a mouse, PRESSED will usually correspond to the left mouse button.
|
||||
// PRESSED will only return 1 if the pointer is inside the game screen.
|
||||
//
|
||||
// For multi-touch, the index variable can be used to successively query more presses.
|
||||
// If index = 0 returns true for _PRESSED, coordinates can be extracted
|
||||
// with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with index = 1, and so on.
|
||||
// Eventually _PRESSED will return false for an index. No further presses are registered at this point.
|
||||
#define RETRO_DEVICE_POINTER 6
|
||||
|
||||
// These device types are specializations of the base types above.
|
||||
// They should only be used in retro_set_controller_type() to inform libretro implementations
|
||||
// about use of a very specific device type.
|
||||
//
|
||||
// In input state callback, however, only the base type should be used in the 'device' field.
|
||||
#define RETRO_DEVICE_JOYPAD_MULTITAP ((1 << 8) | RETRO_DEVICE_JOYPAD)
|
||||
#define RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE ((1 << 8) | RETRO_DEVICE_LIGHTGUN)
|
||||
#define RETRO_DEVICE_LIGHTGUN_JUSTIFIER ((2 << 8) | RETRO_DEVICE_LIGHTGUN)
|
||||
#define RETRO_DEVICE_LIGHTGUN_JUSTIFIERS ((3 << 8) | RETRO_DEVICE_LIGHTGUN)
|
||||
|
||||
// Buttons for the RetroPad (JOYPAD).
|
||||
// The placement of these is equivalent to placements on the Super Nintendo controller.
|
||||
// L2/R2/L3/R3 buttons correspond to the PS1 DualShock.
|
||||
#define RETRO_DEVICE_ID_JOYPAD_B 0
|
||||
#define RETRO_DEVICE_ID_JOYPAD_Y 1
|
||||
#define RETRO_DEVICE_ID_JOYPAD_SELECT 2
|
||||
#define RETRO_DEVICE_ID_JOYPAD_START 3
|
||||
#define RETRO_DEVICE_ID_JOYPAD_UP 4
|
||||
#define RETRO_DEVICE_ID_JOYPAD_DOWN 5
|
||||
#define RETRO_DEVICE_ID_JOYPAD_LEFT 6
|
||||
#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7
|
||||
#define RETRO_DEVICE_ID_JOYPAD_A 8
|
||||
#define RETRO_DEVICE_ID_JOYPAD_X 9
|
||||
#define RETRO_DEVICE_ID_JOYPAD_L 10
|
||||
#define RETRO_DEVICE_ID_JOYPAD_R 11
|
||||
#define RETRO_DEVICE_ID_JOYPAD_L2 12
|
||||
#define RETRO_DEVICE_ID_JOYPAD_R2 13
|
||||
#define RETRO_DEVICE_ID_JOYPAD_L3 14
|
||||
#define RETRO_DEVICE_ID_JOYPAD_R3 15
|
||||
|
||||
// Index / Id values for ANALOG device.
|
||||
#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0
|
||||
#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1
|
||||
#define RETRO_DEVICE_ID_ANALOG_X 0
|
||||
#define RETRO_DEVICE_ID_ANALOG_Y 1
|
||||
|
||||
// Id values for MOUSE.
|
||||
#define RETRO_DEVICE_ID_MOUSE_X 0
|
||||
#define RETRO_DEVICE_ID_MOUSE_Y 1
|
||||
#define RETRO_DEVICE_ID_MOUSE_LEFT 2
|
||||
#define RETRO_DEVICE_ID_MOUSE_RIGHT 3
|
||||
|
||||
// Id values for LIGHTGUN types.
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_X 0
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_Y 1
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5
|
||||
#define RETRO_DEVICE_ID_LIGHTGUN_START 6
|
||||
|
||||
// Id values for POINTER.
|
||||
#define RETRO_DEVICE_ID_POINTER_X 0
|
||||
#define RETRO_DEVICE_ID_POINTER_Y 1
|
||||
#define RETRO_DEVICE_ID_POINTER_PRESSED 2
|
||||
|
||||
// Returned from retro_get_region().
|
||||
#define RETRO_REGION_NTSC 0
|
||||
#define RETRO_REGION_PAL 1
|
||||
|
||||
// Passed to retro_get_memory_data/size().
|
||||
// If the memory type doesn't apply to the implementation NULL/0 can be returned.
|
||||
#define RETRO_MEMORY_MASK 0xff
|
||||
|
||||
// Regular save ram. This ram is usually found on a game cartridge, backed up by a battery.
|
||||
// If save game data is too complex for a single memory buffer,
|
||||
// the SYSTEM_DIRECTORY environment callback can be used.
|
||||
#define RETRO_MEMORY_SAVE_RAM 0
|
||||
|
||||
// Some games have a built-in clock to keep track of time.
|
||||
// This memory is usually just a couple of bytes to keep track of time.
|
||||
#define RETRO_MEMORY_RTC 1
|
||||
|
||||
// System ram lets a frontend peek into a game systems main RAM.
|
||||
#define RETRO_MEMORY_SYSTEM_RAM 2
|
||||
|
||||
// Video ram lets a frontend peek into a game systems video RAM (VRAM).
|
||||
#define RETRO_MEMORY_VIDEO_RAM 3
|
||||
|
||||
// Special memory types.
|
||||
#define RETRO_MEMORY_SNES_BSX_RAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM)
|
||||
#define RETRO_MEMORY_SNES_BSX_PRAM ((2 << 8) | RETRO_MEMORY_SAVE_RAM)
|
||||
#define RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM)
|
||||
#define RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM ((4 << 8) | RETRO_MEMORY_SAVE_RAM)
|
||||
#define RETRO_MEMORY_SNES_GAME_BOY_RAM ((5 << 8) | RETRO_MEMORY_SAVE_RAM)
|
||||
#define RETRO_MEMORY_SNES_GAME_BOY_RTC ((6 << 8) | RETRO_MEMORY_RTC)
|
||||
|
||||
// Special game types passed into retro_load_game_special().
|
||||
// Only used when multiple ROMs are required.
|
||||
#define RETRO_GAME_TYPE_BSX 0x101
|
||||
#define RETRO_GAME_TYPE_BSX_SLOTTED 0x102
|
||||
#define RETRO_GAME_TYPE_SUFAMI_TURBO 0x103
|
||||
#define RETRO_GAME_TYPE_SUPER_GAME_BOY 0x104
|
||||
|
||||
// Keysyms used for ID in input state callback when polling RETRO_KEYBOARD.
|
||||
enum retro_key
|
||||
{
|
||||
RETROK_UNKNOWN = 0,
|
||||
RETROK_FIRST = 0,
|
||||
RETROK_BACKSPACE = 8,
|
||||
RETROK_TAB = 9,
|
||||
RETROK_CLEAR = 12,
|
||||
RETROK_RETURN = 13,
|
||||
RETROK_PAUSE = 19,
|
||||
RETROK_ESCAPE = 27,
|
||||
RETROK_SPACE = 32,
|
||||
RETROK_EXCLAIM = 33,
|
||||
RETROK_QUOTEDBL = 34,
|
||||
RETROK_HASH = 35,
|
||||
RETROK_DOLLAR = 36,
|
||||
RETROK_AMPERSAND = 38,
|
||||
RETROK_QUOTE = 39,
|
||||
RETROK_LEFTPAREN = 40,
|
||||
RETROK_RIGHTPAREN = 41,
|
||||
RETROK_ASTERISK = 42,
|
||||
RETROK_PLUS = 43,
|
||||
RETROK_COMMA = 44,
|
||||
RETROK_MINUS = 45,
|
||||
RETROK_PERIOD = 46,
|
||||
RETROK_SLASH = 47,
|
||||
RETROK_0 = 48,
|
||||
RETROK_1 = 49,
|
||||
RETROK_2 = 50,
|
||||
RETROK_3 = 51,
|
||||
RETROK_4 = 52,
|
||||
RETROK_5 = 53,
|
||||
RETROK_6 = 54,
|
||||
RETROK_7 = 55,
|
||||
RETROK_8 = 56,
|
||||
RETROK_9 = 57,
|
||||
RETROK_COLON = 58,
|
||||
RETROK_SEMICOLON = 59,
|
||||
RETROK_LESS = 60,
|
||||
RETROK_EQUALS = 61,
|
||||
RETROK_GREATER = 62,
|
||||
RETROK_QUESTION = 63,
|
||||
RETROK_AT = 64,
|
||||
RETROK_LEFTBRACKET = 91,
|
||||
RETROK_BACKSLASH = 92,
|
||||
RETROK_RIGHTBRACKET = 93,
|
||||
RETROK_CARET = 94,
|
||||
RETROK_UNDERSCORE = 95,
|
||||
RETROK_BACKQUOTE = 96,
|
||||
RETROK_a = 97,
|
||||
RETROK_b = 98,
|
||||
RETROK_c = 99,
|
||||
RETROK_d = 100,
|
||||
RETROK_e = 101,
|
||||
RETROK_f = 102,
|
||||
RETROK_g = 103,
|
||||
RETROK_h = 104,
|
||||
RETROK_i = 105,
|
||||
RETROK_j = 106,
|
||||
RETROK_k = 107,
|
||||
RETROK_l = 108,
|
||||
RETROK_m = 109,
|
||||
RETROK_n = 110,
|
||||
RETROK_o = 111,
|
||||
RETROK_p = 112,
|
||||
RETROK_q = 113,
|
||||
RETROK_r = 114,
|
||||
RETROK_s = 115,
|
||||
RETROK_t = 116,
|
||||
RETROK_u = 117,
|
||||
RETROK_v = 118,
|
||||
RETROK_w = 119,
|
||||
RETROK_x = 120,
|
||||
RETROK_y = 121,
|
||||
RETROK_z = 122,
|
||||
RETROK_DELETE = 127,
|
||||
|
||||
RETROK_KP0 = 256,
|
||||
RETROK_KP1 = 257,
|
||||
RETROK_KP2 = 258,
|
||||
RETROK_KP3 = 259,
|
||||
RETROK_KP4 = 260,
|
||||
RETROK_KP5 = 261,
|
||||
RETROK_KP6 = 262,
|
||||
RETROK_KP7 = 263,
|
||||
RETROK_KP8 = 264,
|
||||
RETROK_KP9 = 265,
|
||||
RETROK_KP_PERIOD = 266,
|
||||
RETROK_KP_DIVIDE = 267,
|
||||
RETROK_KP_MULTIPLY = 268,
|
||||
RETROK_KP_MINUS = 269,
|
||||
RETROK_KP_PLUS = 270,
|
||||
RETROK_KP_ENTER = 271,
|
||||
RETROK_KP_EQUALS = 272,
|
||||
|
||||
RETROK_UP = 273,
|
||||
RETROK_DOWN = 274,
|
||||
RETROK_RIGHT = 275,
|
||||
RETROK_LEFT = 276,
|
||||
RETROK_INSERT = 277,
|
||||
RETROK_HOME = 278,
|
||||
RETROK_END = 279,
|
||||
RETROK_PAGEUP = 280,
|
||||
RETROK_PAGEDOWN = 281,
|
||||
|
||||
RETROK_F1 = 282,
|
||||
RETROK_F2 = 283,
|
||||
RETROK_F3 = 284,
|
||||
RETROK_F4 = 285,
|
||||
RETROK_F5 = 286,
|
||||
RETROK_F6 = 287,
|
||||
RETROK_F7 = 288,
|
||||
RETROK_F8 = 289,
|
||||
RETROK_F9 = 290,
|
||||
RETROK_F10 = 291,
|
||||
RETROK_F11 = 292,
|
||||
RETROK_F12 = 293,
|
||||
RETROK_F13 = 294,
|
||||
RETROK_F14 = 295,
|
||||
RETROK_F15 = 296,
|
||||
|
||||
RETROK_NUMLOCK = 300,
|
||||
RETROK_CAPSLOCK = 301,
|
||||
RETROK_SCROLLOCK = 302,
|
||||
RETROK_RSHIFT = 303,
|
||||
RETROK_LSHIFT = 304,
|
||||
RETROK_RCTRL = 305,
|
||||
RETROK_LCTRL = 306,
|
||||
RETROK_RALT = 307,
|
||||
RETROK_LALT = 308,
|
||||
RETROK_RMETA = 309,
|
||||
RETROK_LMETA = 310,
|
||||
RETROK_LSUPER = 311,
|
||||
RETROK_RSUPER = 312,
|
||||
RETROK_MODE = 313,
|
||||
RETROK_COMPOSE = 314,
|
||||
|
||||
RETROK_HELP = 315,
|
||||
RETROK_PRINT = 316,
|
||||
RETROK_SYSREQ = 317,
|
||||
RETROK_BREAK = 318,
|
||||
RETROK_MENU = 319,
|
||||
RETROK_POWER = 320,
|
||||
RETROK_EURO = 321,
|
||||
RETROK_UNDO = 322,
|
||||
|
||||
RETROK_LAST,
|
||||
|
||||
RETROK_DUMMY = INT_MAX // Ensure sizeof(enum) == sizeof(int)
|
||||
};
|
||||
|
||||
enum retro_mod
|
||||
{
|
||||
RETROKMOD_NONE = 0x0000,
|
||||
|
||||
RETROKMOD_SHIFT = 0x01,
|
||||
RETROKMOD_CTRL = 0x02,
|
||||
RETROKMOD_ALT = 0x04,
|
||||
RETROKMOD_META = 0x08,
|
||||
|
||||
RETROKMOD_NUMLOCK = 0x10,
|
||||
RETROKMOD_CAPSLOCK = 0x20,
|
||||
RETROKMOD_SCROLLOCK = 0x40,
|
||||
|
||||
RETROKMOD_DUMMY = INT_MAX // Ensure sizeof(enum) == sizeof(int)
|
||||
};
|
||||
|
||||
// If set, this call is not part of the public libretro API yet. It can change or be removed at any time.
|
||||
#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000
|
||||
|
||||
// Environment commands.
|
||||
#define RETRO_ENVIRONMENT_SET_ROTATION 1 // const unsigned * --
|
||||
// Sets screen rotation of graphics.
|
||||
// Is only implemented if rotation can be accelerated by hardware.
|
||||
// Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, 270 degrees
|
||||
// counter-clockwise respectively.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_GET_OVERSCAN 2 // bool * --
|
||||
// Boolean value whether or not the implementation should use overscan, or crop away overscan.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 // bool * --
|
||||
// Boolean value whether or not frontend supports frame duping,
|
||||
// passing NULL to video frame callback.
|
||||
//
|
||||
// Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), and reserved to avoid possible ABI clash.
|
||||
#define RETRO_ENVIRONMENT_SET_MESSAGE 6 // const struct retro_message * --
|
||||
// Sets a message to be displayed in implementation-specific manner for a certain amount of 'frames'.
|
||||
// Should not be used for trivial messages, which should simply be logged to stderr.
|
||||
#define RETRO_ENVIRONMENT_SHUTDOWN 7 // N/A (NULL) --
|
||||
// Requests the frontend to shutdown.
|
||||
// Should only be used if game has a specific
|
||||
// way to shutdown the game from a menu item or similar.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8
|
||||
// const unsigned * --
|
||||
// Gives a hint to the frontend how demanding this implementation
|
||||
// is on a system. E.g. reporting a level of 2 means
|
||||
// this implementation should run decently on all frontends
|
||||
// of level 2 and up.
|
||||
//
|
||||
// It can be used by the frontend to potentially warn
|
||||
// about too demanding implementations.
|
||||
//
|
||||
// The levels are "floating", but roughly defined as:
|
||||
// 0: Low-powered embedded devices such as Raspberry Pi
|
||||
// 1: 6th generation consoles, such as Wii/Xbox 1, and phones, tablets, etc.
|
||||
// 2: 7th generation consoles, such as PS3/360, with sub-par CPUs.
|
||||
// 3: Modern desktop/laptops with reasonably powerful CPUs.
|
||||
// 4: High-end desktops with very powerful CPUs.
|
||||
//
|
||||
// This function can be called on a per-game basis,
|
||||
// as certain games an implementation can play might be
|
||||
// particularily demanding.
|
||||
// If called, it should be called in retro_load_game().
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9
|
||||
// const char ** --
|
||||
// Returns the "system" directory of the frontend.
|
||||
// This directory can be used to store system specific ROMs such as BIOSes, configuration data, etc.
|
||||
// The returned value can be NULL.
|
||||
// If so, no such directory is defined,
|
||||
// and it's up to the implementation to find a suitable directory.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10
|
||||
// const enum retro_pixel_format * --
|
||||
// Sets the internal pixel format used by the implementation.
|
||||
// The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555.
|
||||
// This pixel format however, is deprecated (see enum retro_pixel_format).
|
||||
// If the call returns false, the frontend does not support this pixel format.
|
||||
// This function should be called inside retro_load_game() or retro_get_system_av_info().
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11
|
||||
// const struct retro_input_descriptor * --
|
||||
// Sets an array of retro_input_descriptors.
|
||||
// It is up to the frontend to present this in a usable way.
|
||||
// The array is terminated by retro_input_descriptor::description being set to NULL.
|
||||
// This function can be called at any time, but it is recommended to call it as early as possible.
|
||||
#define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12
|
||||
// const struct retro_keyboard_callback * --
|
||||
// Sets a callback function used to notify core about keyboard events.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13
|
||||
// const struct retro_disk_control_callback * --
|
||||
// Sets an interface which frontend can use to eject and insert disk images.
|
||||
// This is used for games which consist of multiple images and must be manually
|
||||
// swapped out by the user (e.g. PSX).
|
||||
#define RETRO_ENVIRONMENT_SET_HW_RENDER (14 | RETRO_ENVIRONMENT_EXPERIMENTAL)
|
||||
// struct retro_hw_render_callback * --
|
||||
// NOTE: This call is currently very experimental, and should not be considered part of the public API.
|
||||
// The interface could be changed or removed at any time.
|
||||
// Sets an interface to let a libretro core render with hardware acceleration.
|
||||
// Should be called in retro_load_game().
|
||||
// If successful, libretro cores will be able to render to a frontend-provided framebuffer.
|
||||
// The size of this framebuffer will be at least as large as max_width/max_height provided in get_av_info().
|
||||
// If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or NULL to retro_video_refresh_t.
|
||||
#define RETRO_ENVIRONMENT_GET_VARIABLE 15
|
||||
// struct retro_variable * --
|
||||
// Interface to aquire user-defined information from environment
|
||||
// that cannot feasibly be supported in a multi-system way.
|
||||
// 'key' should be set to a key which has already been set by SET_VARIABLES.
|
||||
// 'data' will be set to a value or NULL.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_SET_VARIABLES 16
|
||||
// const struct retro_variable * --
|
||||
// Allows an implementation to signal the environment
|
||||
// which variables it might want to check for later using GET_VARIABLE.
|
||||
// This allows the frontend to present these variables to a user dynamically.
|
||||
// This should be called as early as possible (ideally in retro_set_environment).
|
||||
//
|
||||
// 'data' points to an array of retro_variable structs terminated by a { NULL, NULL } element.
|
||||
// retro_variable::key should be namespaced to not collide with other implementations' keys. E.g. A core called 'foo' should use keys named as 'foo_option'.
|
||||
// retro_variable::value should contain a human readable description of the key as well as a '|' delimited list of expected values.
|
||||
// The number of possible options should be very limited, i.e. it should be feasible to cycle through options without a keyboard.
|
||||
// First entry should be treated as a default.
|
||||
//
|
||||
// Example entry:
|
||||
// { "foo_option", "Speed hack coprocessor X; false|true" }
|
||||
//
|
||||
// Text before first ';' is description. This ';' must be followed by a space, and followed by a list of possible values split up with '|'.
|
||||
// Only strings are operated on. The possible values will generally be displayed and stored as-is by the frontend.
|
||||
//
|
||||
#define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17
|
||||
// bool * --
|
||||
// Result is set to true if some variables are updated by
|
||||
// frontend since last call to RETRO_ENVIRONMENT_GET_VARIABLE.
|
||||
// Variables should be queried with GET_VARIABLE.
|
||||
|
||||
// Pass this to retro_video_refresh_t if rendering to hardware.
|
||||
// Passing NULL to retro_video_refresh_t is still a frame dupe as normal.
|
||||
#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1)
|
||||
|
||||
// Invalidates the current HW context.
|
||||
// If called, all GPU resources must be reinitialized.
|
||||
// Usually called when frontend reinits video driver.
|
||||
// Also called first time video driver is initialized, allowing libretro core to init resources.
|
||||
typedef void (*retro_hw_context_reset_t)(void);
|
||||
// Gets current framebuffer which is to be rendered to. Could change every frame potentially.
|
||||
typedef uintptr_t (*retro_hw_get_current_framebuffer_t)(void);
|
||||
|
||||
// Get a symbol from HW context.
|
||||
typedef void (*retro_proc_address_t)(void);
|
||||
typedef retro_proc_address_t (*retro_hw_get_proc_address_t)(const char *sym);
|
||||
|
||||
enum retro_hw_context_type
|
||||
{
|
||||
RETRO_HW_CONTEXT_NONE = 0,
|
||||
RETRO_HW_CONTEXT_OPENGL, // OpenGL 2.x. Latest version available before 3.x+.
|
||||
RETRO_HW_CONTEXT_OPENGLES2, // GLES 2.0
|
||||
|
||||
RETRO_HW_CONTEXT_DUMMY = INT_MAX
|
||||
};
|
||||
|
||||
struct retro_hw_render_callback
|
||||
{
|
||||
enum retro_hw_context_type context_type; // Which API to use. Set by libretro core.
|
||||
retro_hw_context_reset_t context_reset; // Set by libretro core.
|
||||
retro_hw_get_current_framebuffer_t get_current_framebuffer; // Set by frontend.
|
||||
retro_hw_get_proc_address_t get_proc_address; // Set by frontend.
|
||||
bool depth; // Set if render buffers should have depth component attached.
|
||||
};
|
||||
|
||||
// Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. Called by the frontend in response to keyboard events.
|
||||
// down is set if the key is being pressed, or false if it is being released.
|
||||
// keycode is the RETROK value of the char.
|
||||
// character is the text character of the pressed key. (UTF-32).
|
||||
// key_modifiers is a set of RETROKMOD values or'ed together.
|
||||
typedef void (*retro_keyboard_event_t)(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers);
|
||||
|
||||
struct retro_keyboard_callback
|
||||
{
|
||||
retro_keyboard_event_t callback;
|
||||
};
|
||||
|
||||
// Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE.
|
||||
// Should be set for implementations which can swap out multiple disk images in runtime.
|
||||
// If the implementation can do this automatically, it should strive to do so.
|
||||
// However, there are cases where the user must manually do so.
|
||||
//
|
||||
// Overview: To swap a disk image, eject the disk image with set_eject_state(true).
|
||||
// Set the disk index with set_image_index(index). Insert the disk again with set_eject_state(false).
|
||||
|
||||
// If ejected is true, "ejects" the virtual disk tray.
|
||||
// When ejected, the disk image index can be set.
|
||||
typedef bool (*retro_set_eject_state_t)(bool ejected);
|
||||
// Gets current eject state. The initial state is 'not ejected'.
|
||||
typedef bool (*retro_get_eject_state_t)(void);
|
||||
// Gets current disk index. First disk is index 0.
|
||||
// If return value is >= get_num_images(), no disk is currently inserted.
|
||||
typedef unsigned (*retro_get_image_index_t)(void);
|
||||
// Sets image index. Can only be called when disk is ejected.
|
||||
// The implementation supports setting "no disk" by using an index >= get_num_images().
|
||||
typedef bool (*retro_set_image_index_t)(unsigned index);
|
||||
// Gets total number of images which are available to use.
|
||||
typedef unsigned (*retro_get_num_images_t)(void);
|
||||
//
|
||||
// Replaces the disk image associated with index.
|
||||
// Arguments to pass in info have same requirements as retro_load_game().
|
||||
// Virtual disk tray must be ejected when calling this.
|
||||
// Replacing a disk image with info = NULL will remove the disk image from the internal list.
|
||||
// As a result, calls to get_image_index() can change.
|
||||
//
|
||||
// E.g. replace_image_index(1, NULL), and previous get_image_index() returned 4 before.
|
||||
// Index 1 will be removed, and the new index is 3.
|
||||
struct retro_game_info;
|
||||
typedef bool (*retro_replace_image_index_t)(unsigned index, const struct retro_game_info *info);
|
||||
// Adds a new valid index (get_num_images()) to the internal disk list.
|
||||
// This will increment subsequent return values from get_num_images() by 1.
|
||||
// This image index cannot be used until a disk image has been set with replace_image_index.
|
||||
typedef bool (*retro_add_image_index_t)(void);
|
||||
|
||||
struct retro_disk_control_callback
|
||||
{
|
||||
retro_set_eject_state_t set_eject_state;
|
||||
retro_get_eject_state_t get_eject_state;
|
||||
|
||||
retro_get_image_index_t get_image_index;
|
||||
retro_set_image_index_t set_image_index;
|
||||
retro_get_num_images_t get_num_images;
|
||||
|
||||
retro_replace_image_index_t replace_image_index;
|
||||
retro_add_image_index_t add_image_index;
|
||||
};
|
||||
|
||||
enum retro_pixel_format
|
||||
{
|
||||
// 0RGB1555, native endian. 0 bit must be set to 0.
|
||||
// This pixel format is default for compatibility concerns only.
|
||||
// If a 15/16-bit pixel format is desired, consider using RGB565.
|
||||
RETRO_PIXEL_FORMAT_0RGB1555 = 0,
|
||||
|
||||
// XRGB8888, native endian. X bits are ignored.
|
||||
RETRO_PIXEL_FORMAT_XRGB8888 = 1,
|
||||
|
||||
// RGB565, native endian. This pixel format is the recommended format to use if a 15/16-bit format is desired
|
||||
// as it is the pixel format that is typically available on a wide range of low-power devices.
|
||||
// It is also natively supported in APIs like OpenGL ES.
|
||||
RETRO_PIXEL_FORMAT_RGB565 = 2,
|
||||
|
||||
// Ensure sizeof() == sizeof(int).
|
||||
RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX
|
||||
};
|
||||
|
||||
struct retro_message
|
||||
{
|
||||
const char *msg; // Message to be displayed.
|
||||
unsigned frames; // Duration in frames of message.
|
||||
};
|
||||
|
||||
// Describes how the libretro implementation maps a libretro input bind
|
||||
// to its internal input system through a human readable string.
|
||||
// This string can be used to better let a user configure input.
|
||||
struct retro_input_descriptor
|
||||
{
|
||||
// Associates given parameters with a description.
|
||||
unsigned port;
|
||||
unsigned device;
|
||||
unsigned index;
|
||||
unsigned id;
|
||||
|
||||
const char *description; // Human readable description for parameters.
|
||||
// The pointer must remain valid until retro_unload_game() is called.
|
||||
};
|
||||
|
||||
struct retro_system_info
|
||||
{
|
||||
// All pointers are owned by libretro implementation, and pointers must remain valid until retro_deinit() is called.
|
||||
|
||||
const char *library_name; // Descriptive name of library. Should not contain any version numbers, etc.
|
||||
const char *library_version; // Descriptive version of core.
|
||||
|
||||
const char *valid_extensions; // A string listing probably rom extensions the core will be able to load, separated with pipe.
|
||||
// I.e. "bin|rom|iso".
|
||||
// Typically used for a GUI to filter out extensions.
|
||||
|
||||
bool need_fullpath; // If true, retro_load_game() is guaranteed to provide a valid pathname in retro_game_info::path.
|
||||
// ::data and ::size are both invalid.
|
||||
// If false, ::data and ::size are guaranteed to be valid, but ::path might not be valid.
|
||||
// This is typically set to true for libretro implementations that must load from file.
|
||||
// Implementations should strive for setting this to false, as it allows the frontend to perform patching, etc.
|
||||
|
||||
bool block_extract; // If true, the frontend is not allowed to extract any archives before loading the real ROM.
|
||||
// Necessary for certain libretro implementations that load games from zipped archives.
|
||||
};
|
||||
|
||||
struct retro_game_geometry
|
||||
{
|
||||
unsigned base_width; // Nominal video width of game.
|
||||
unsigned base_height; // Nominal video height of game.
|
||||
unsigned max_width; // Maximum possible width of game.
|
||||
unsigned max_height; // Maximum possible height of game.
|
||||
|
||||
float aspect_ratio; // Nominal aspect ratio of game. If aspect_ratio is <= 0.0,
|
||||
// an aspect ratio of base_width / base_height is assumed.
|
||||
// A frontend could override this setting if desired.
|
||||
};
|
||||
|
||||
struct retro_system_timing
|
||||
{
|
||||
double fps; // FPS of video content.
|
||||
double sample_rate; // Sampling rate of audio.
|
||||
};
|
||||
|
||||
struct retro_system_av_info
|
||||
{
|
||||
struct retro_game_geometry geometry;
|
||||
struct retro_system_timing timing;
|
||||
};
|
||||
|
||||
struct retro_variable
|
||||
{
|
||||
const char *key; // Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE.
|
||||
// If NULL, obtains the complete environment string if more complex parsing is necessary.
|
||||
// The environment string is formatted as key-value pairs delimited by semicolons as so:
|
||||
// "key1=value1;key2=value2;..."
|
||||
const char *value; // Value to be obtained. If key does not exist, it is set to NULL.
|
||||
};
|
||||
|
||||
struct retro_game_info
|
||||
{
|
||||
const char *path; // Path to game, UTF-8 encoded. Usually used as a reference.
|
||||
// May be NULL if rom was loaded from stdin or similar.
|
||||
// retro_system_info::need_fullpath guaranteed that this path is valid.
|
||||
const void *data; // Memory buffer of loaded game. Will be NULL if need_fullpath was set.
|
||||
size_t size; // Size of memory buffer.
|
||||
const char *meta; // String of implementation specific meta-data.
|
||||
};
|
||||
|
||||
// Callbacks
|
||||
//
|
||||
// Environment callback. Gives implementations a way of performing uncommon tasks. Extensible.
|
||||
typedef bool (*retro_environment_t)(unsigned cmd, void *data);
|
||||
|
||||
// Render a frame. Pixel format is 15-bit 0RGB1555 native endian unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT).
|
||||
// Width and height specify dimensions of buffer.
|
||||
// Pitch specifices length in bytes between two lines in buffer.
|
||||
// For performance reasons, it is highly recommended to have a frame that is packed in memory, i.e. pitch == width * byte_per_pixel.
|
||||
// Certain graphic APIs, such as OpenGL ES, do not like textures that are not packed in memory.
|
||||
typedef void (*retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch);
|
||||
|
||||
// Renders a single audio frame. Should only be used if implementation generates a single sample at a time.
|
||||
// Format is signed 16-bit native endian.
|
||||
typedef void (*retro_audio_sample_t)(int16_t left, int16_t right);
|
||||
// Renders multiple audio frames in one go. One frame is defined as a sample of left and right channels, interleaved.
|
||||
// I.e. int16_t buf[4] = { l, r, l, r }; would be 2 frames.
|
||||
// Only one of the audio callbacks must ever be used.
|
||||
typedef size_t (*retro_audio_sample_batch_t)(const int16_t *data, size_t frames);
|
||||
|
||||
// Polls input.
|
||||
typedef void (*retro_input_poll_t)(void);
|
||||
// Queries for input for player 'port'. device will be masked with RETRO_DEVICE_MASK.
|
||||
// Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that have been set with retro_set_controller_port_device()
|
||||
// will still use the higher level RETRO_DEVICE_JOYPAD to request input.
|
||||
typedef int16_t (*retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id);
|
||||
|
||||
// Sets callbacks. retro_set_environment() is guaranteed to be called before retro_init().
|
||||
// The rest of the set_* functions are guaranteed to have been called before the first call to retro_run() is made.
|
||||
void retro_set_environment(retro_environment_t);
|
||||
void retro_set_video_refresh(retro_video_refresh_t);
|
||||
void retro_set_audio_sample(retro_audio_sample_t);
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t);
|
||||
void retro_set_input_poll(retro_input_poll_t);
|
||||
void retro_set_input_state(retro_input_state_t);
|
||||
|
||||
// Library global initialization/deinitialization.
|
||||
void retro_init(void);
|
||||
void retro_deinit(void);
|
||||
|
||||
// Must return RETRO_API_VERSION. Used to validate ABI compatibility when the API is revised.
|
||||
unsigned retro_api_version(void);
|
||||
|
||||
// Gets statically known system info. Pointers provided in *info must be statically allocated.
|
||||
// Can be called at any time, even before retro_init().
|
||||
void retro_get_system_info(struct retro_system_info *info);
|
||||
|
||||
// Gets information about system audio/video timings and geometry.
|
||||
// Can be called only after retro_load_game() has successfully completed.
|
||||
// NOTE: The implementation of this function might not initialize every variable if needed.
|
||||
// E.g. geom.aspect_ratio might not be initialized if core doesn't desire a particular aspect ratio.
|
||||
void retro_get_system_av_info(struct retro_system_av_info *info);
|
||||
|
||||
// Sets device to be used for player 'port'.
|
||||
void retro_set_controller_port_device(unsigned port, unsigned device);
|
||||
|
||||
// Resets the current game.
|
||||
void retro_reset(void);
|
||||
|
||||
// Runs the game for one video frame.
|
||||
// During retro_run(), input_poll callback must be called at least once.
|
||||
//
|
||||
// If a frame is not rendered for reasons where a game "dropped" a frame,
|
||||
// this still counts as a frame, and retro_run() should explicitly dupe a frame if GET_CAN_DUPE returns true.
|
||||
// In this case, the video callback can take a NULL argument for data.
|
||||
void retro_run(void);
|
||||
|
||||
// Returns the amount of data the implementation requires to serialize internal state (save states).
|
||||
// Beetween calls to retro_load_game() and retro_unload_game(), the returned size is never allowed to be larger than a previous returned value, to
|
||||
// ensure that the frontend can allocate a save state buffer once.
|
||||
size_t retro_serialize_size(void);
|
||||
|
||||
// Serializes internal state. If failed, or size is lower than retro_serialize_size(), it should return false, true otherwise.
|
||||
bool retro_serialize(void *data, size_t size);
|
||||
bool retro_unserialize(const void *data, size_t size);
|
||||
|
||||
void retro_cheat_reset(void);
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char *code);
|
||||
|
||||
// Loads a game.
|
||||
bool retro_load_game(const struct retro_game_info *game);
|
||||
|
||||
// Loads a "special" kind of game. Should not be used except in extreme cases.
|
||||
bool retro_load_game_special(
|
||||
unsigned game_type,
|
||||
const struct retro_game_info *info, size_t num_info
|
||||
);
|
||||
|
||||
// Unloads a currently loaded game.
|
||||
void retro_unload_game(void);
|
||||
|
||||
// Gets region of game.
|
||||
unsigned retro_get_region(void);
|
||||
|
||||
// Gets region of memory.
|
||||
void *retro_get_memory_data(unsigned id);
|
||||
size_t retro_get_memory_size(unsigned id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
global: retro_*;
|
||||
local: *;
|
||||
};
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
|
||||
// Blip_Buffer 0.4.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
int const buffer_extra = blip_widest_impulse_ + 2;
|
||||
|
||||
Blip_Buffer::Blip_Buffer()
|
||||
{
|
||||
factor_ = LONG_MAX;
|
||||
offset_ = 0;
|
||||
buffer_ = 0;
|
||||
buffer_size_ = 0;
|
||||
sample_rate_ = 0;
|
||||
reader_accum = 0;
|
||||
bass_shift = 0;
|
||||
clock_rate_ = 0;
|
||||
bass_freq_ = 16;
|
||||
length_ = 0;
|
||||
|
||||
// assumptions code makes about implementation-defined features
|
||||
#ifndef NDEBUG
|
||||
// right shift of negative value preserves sign
|
||||
buf_t_ i = -0x7FFFFFFE;
|
||||
assert( (i >> 1) == -0x3FFFFFFF );
|
||||
|
||||
// casting to short truncates to 16 bits and sign-extends
|
||||
i = 0x18000;
|
||||
assert( (short) i == -0x8000 );
|
||||
#endif
|
||||
}
|
||||
|
||||
Blip_Buffer::~Blip_Buffer()
|
||||
{
|
||||
free( buffer_ );
|
||||
}
|
||||
|
||||
void Blip_Buffer::clear( int entire_buffer )
|
||||
{
|
||||
offset_ = 0;
|
||||
reader_accum = 0;
|
||||
if ( buffer_ )
|
||||
{
|
||||
long count = (entire_buffer ? buffer_size_ : samples_avail());
|
||||
memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) );
|
||||
}
|
||||
}
|
||||
|
||||
Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec )
|
||||
{
|
||||
// start with maximum length that resampled time can represent
|
||||
long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64;
|
||||
if ( msec != blip_max_length )
|
||||
{
|
||||
long s = (new_rate * (msec + 1) + 999) / 1000;
|
||||
if ( s < new_size )
|
||||
new_size = s;
|
||||
else
|
||||
assert( 0 ); // fails if requested buffer length exceeds limit
|
||||
}
|
||||
|
||||
if ( buffer_size_ != new_size )
|
||||
{
|
||||
void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ );
|
||||
if ( !p )
|
||||
return "Out of memory";
|
||||
buffer_ = (buf_t_*) p;
|
||||
}
|
||||
|
||||
buffer_size_ = new_size;
|
||||
|
||||
// update things based on the sample rate
|
||||
sample_rate_ = new_rate;
|
||||
length_ = new_size * 1000 / new_rate - 1;
|
||||
if ( msec )
|
||||
assert( length_ == msec ); // ensure length is same as that passed in
|
||||
if ( clock_rate_ )
|
||||
clock_rate( clock_rate_ );
|
||||
bass_freq( bass_freq_ );
|
||||
|
||||
clear();
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const
|
||||
{
|
||||
double ratio = (double) sample_rate_ / clock_rate;
|
||||
long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 );
|
||||
assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large
|
||||
return (blip_resampled_time_t) factor;
|
||||
}
|
||||
|
||||
void Blip_Buffer::bass_freq( int freq )
|
||||
{
|
||||
bass_freq_ = freq;
|
||||
int shift = 31;
|
||||
if ( freq > 0 )
|
||||
{
|
||||
shift = 13;
|
||||
long f = (freq << 16) / sample_rate_;
|
||||
while ( (f >>= 1) && --shift ) { }
|
||||
}
|
||||
bass_shift = shift;
|
||||
}
|
||||
|
||||
void Blip_Buffer::end_frame( blip_time_t t )
|
||||
{
|
||||
offset_ += t * factor_;
|
||||
assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length
|
||||
}
|
||||
|
||||
void Blip_Buffer::remove_silence( long count )
|
||||
{
|
||||
assert( count <= samples_avail() ); // tried to remove more samples than available
|
||||
offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
|
||||
}
|
||||
|
||||
long Blip_Buffer::count_samples( blip_time_t t ) const
|
||||
{
|
||||
unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY;
|
||||
unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY;
|
||||
return (long) (last_sample - first_sample);
|
||||
}
|
||||
|
||||
blip_time_t Blip_Buffer::count_clocks( long count ) const
|
||||
{
|
||||
if ( count > buffer_size_ )
|
||||
count = buffer_size_;
|
||||
blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
|
||||
return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_);
|
||||
}
|
||||
|
||||
void Blip_Buffer::remove_samples( long count )
|
||||
{
|
||||
if ( count )
|
||||
{
|
||||
remove_silence( count );
|
||||
|
||||
// copy remaining samples to beginning and clear old samples
|
||||
long remain = samples_avail() + buffer_extra;
|
||||
memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ );
|
||||
memset( buffer_ + remain, 0, count * sizeof *buffer_ );
|
||||
}
|
||||
}
|
||||
|
||||
// Blip_Synth_
|
||||
|
||||
Blip_Synth_::Blip_Synth_( short* p, int w ) :
|
||||
impulses( p ),
|
||||
width( w )
|
||||
{
|
||||
volume_unit_ = 0.0;
|
||||
kernel_unit = 0;
|
||||
buf = 0;
|
||||
last_amp = 0;
|
||||
delta_factor = 0;
|
||||
}
|
||||
|
||||
// TODO: apparently this is defined elsewhere too
|
||||
#define pi my_pi
|
||||
static double const pi = 3.1415926535897932384626433832795029;
|
||||
|
||||
static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff )
|
||||
{
|
||||
if ( cutoff >= 0.999 )
|
||||
cutoff = 0.999;
|
||||
|
||||
if ( treble < -300.0 )
|
||||
treble = -300.0;
|
||||
if ( treble > 5.0 )
|
||||
treble = 5.0;
|
||||
|
||||
double const maxh = 4096.0;
|
||||
double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) );
|
||||
double const pow_a_n = pow( rolloff, maxh - maxh * cutoff );
|
||||
double const to_angle = pi / 2 / maxh / oversample;
|
||||
for ( int i = 0; i < count; i++ )
|
||||
{
|
||||
double angle = ((i - count) * 2 + 1) * to_angle;
|
||||
double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle );
|
||||
double cos_nc_angle = cos( maxh * cutoff * angle );
|
||||
double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle );
|
||||
double cos_angle = cos( angle );
|
||||
|
||||
c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle;
|
||||
double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle);
|
||||
double b = 2.0 - cos_angle - cos_angle;
|
||||
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle;
|
||||
|
||||
out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d
|
||||
}
|
||||
}
|
||||
|
||||
void blip_eq_t::generate( float* out, int count ) const
|
||||
{
|
||||
// lower cutoff freq for narrow kernels with their wider transition band
|
||||
// (8 points->1.49, 16 points->1.15)
|
||||
double oversample = blip_res * 2.25 / count + 0.85;
|
||||
double half_rate = sample_rate * 0.5;
|
||||
if ( cutoff_freq )
|
||||
oversample = half_rate / cutoff_freq;
|
||||
double cutoff = rolloff_freq * oversample / half_rate;
|
||||
|
||||
gen_sinc( out, count, blip_res * oversample, treble, cutoff );
|
||||
|
||||
// apply (half of) hamming window
|
||||
double to_fraction = pi / (count - 1);
|
||||
for ( int i = count; i--; )
|
||||
out [i] *= 0.54 - 0.46 * cos( i * to_fraction );
|
||||
}
|
||||
|
||||
void Blip_Synth_::adjust_impulse()
|
||||
{
|
||||
// sum pairs for each phase and add error correction to end of first half
|
||||
int const size = impulses_size();
|
||||
for ( int p = blip_res; p-- >= blip_res / 2; )
|
||||
{
|
||||
int p2 = blip_res - 2 - p;
|
||||
long error = kernel_unit;
|
||||
for ( int i = 1; i < size; i += blip_res )
|
||||
{
|
||||
error -= impulses [i + p ];
|
||||
error -= impulses [i + p2];
|
||||
}
|
||||
if ( p == p2 )
|
||||
error /= 2; // phase = 0.5 impulse uses same half for both sides
|
||||
impulses [size - blip_res + p] += error;
|
||||
//printf( "error: %ld\n", error );
|
||||
}
|
||||
|
||||
//for ( int i = blip_res; i--; printf( "\n" ) )
|
||||
// for ( int j = 0; j < width / 2; j++ )
|
||||
// printf( "%5ld,", impulses [j * blip_res + i + 1] );
|
||||
}
|
||||
|
||||
void Blip_Synth_::treble_eq( blip_eq_t const& eq )
|
||||
{
|
||||
float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2];
|
||||
|
||||
int const half_size = blip_res / 2 * (width - 1);
|
||||
eq.generate( &fimpulse [blip_res], half_size );
|
||||
|
||||
int i;
|
||||
|
||||
// need mirror slightly past center for calculation
|
||||
for ( i = blip_res; i--; )
|
||||
fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i];
|
||||
|
||||
// starts at 0
|
||||
for ( i = 0; i < blip_res; i++ )
|
||||
fimpulse [i] = 0.0f;
|
||||
|
||||
// find rescale factor
|
||||
double total = 0.0;
|
||||
for ( i = 0; i < half_size; i++ )
|
||||
total += fimpulse [blip_res + i];
|
||||
|
||||
//double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB
|
||||
//double const base_unit = 37888.0; // allows treble to +5 dB
|
||||
double const base_unit = 32768.0; // necessary for blip_unscaled to work
|
||||
double rescale = base_unit / 2 / total;
|
||||
kernel_unit = (long) base_unit;
|
||||
|
||||
// integrate, first difference, rescale, convert to int
|
||||
double sum = 0.0;
|
||||
double next = 0.0;
|
||||
int const impulses_size = this->impulses_size();
|
||||
for ( i = 0; i < impulses_size; i++ )
|
||||
{
|
||||
impulses [i] = (short) floor( (next - sum) * rescale + 0.5 );
|
||||
sum += fimpulse [i];
|
||||
next += fimpulse [i + blip_res];
|
||||
}
|
||||
adjust_impulse();
|
||||
|
||||
// volume might require rescaling
|
||||
double vol = volume_unit_;
|
||||
if ( vol )
|
||||
{
|
||||
volume_unit_ = 0.0;
|
||||
volume_unit( vol );
|
||||
}
|
||||
}
|
||||
|
||||
void Blip_Synth_::volume_unit( double new_unit )
|
||||
{
|
||||
if ( new_unit != volume_unit_ )
|
||||
{
|
||||
// use default eq if it hasn't been set yet
|
||||
if ( !kernel_unit )
|
||||
treble_eq( -8.0 );
|
||||
|
||||
volume_unit_ = new_unit;
|
||||
double factor = new_unit * (1L << blip_sample_bits) / kernel_unit;
|
||||
|
||||
if ( factor > 0.0 )
|
||||
{
|
||||
int shift = 0;
|
||||
|
||||
// if unit is really small, might need to attenuate kernel
|
||||
while ( factor < 2.0 )
|
||||
{
|
||||
shift++;
|
||||
factor *= 2.0;
|
||||
}
|
||||
|
||||
if ( shift )
|
||||
{
|
||||
kernel_unit >>= shift;
|
||||
assert( kernel_unit > 0 ); // fails if volume unit is too low
|
||||
|
||||
// keep values positive to avoid round-towards-zero of sign-preserving
|
||||
// right shift for negative values
|
||||
long offset = 0x8000 + (1 << (shift - 1));
|
||||
long offset2 = 0x8000 >> shift;
|
||||
for ( int i = impulses_size(); i--; )
|
||||
impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2);
|
||||
adjust_impulse();
|
||||
}
|
||||
}
|
||||
delta_factor = (int) floor( factor + 0.5 );
|
||||
//printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit );
|
||||
}
|
||||
}
|
||||
|
||||
long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo )
|
||||
{
|
||||
long count = samples_avail();
|
||||
if ( count > max_samples )
|
||||
count = max_samples;
|
||||
|
||||
if ( count )
|
||||
{
|
||||
int const sample_shift = blip_sample_bits - 16;
|
||||
int const bass_shift = this->bass_shift;
|
||||
long accum = reader_accum;
|
||||
buf_t_* in = buffer_;
|
||||
|
||||
if ( !stereo )
|
||||
{
|
||||
for ( long n = count; n--; )
|
||||
{
|
||||
long s = accum >> sample_shift;
|
||||
accum -= accum >> bass_shift;
|
||||
accum += *in++;
|
||||
*out++ = (blip_sample_t) s;
|
||||
|
||||
// clamp sample
|
||||
if ( (blip_sample_t) s != s )
|
||||
out [-1] = (blip_sample_t) (0x7FFF - (s >> 24));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( long n = count; n--; )
|
||||
{
|
||||
long s = accum >> sample_shift;
|
||||
accum -= accum >> bass_shift;
|
||||
accum += *in++;
|
||||
*out = (blip_sample_t) s;
|
||||
out += 2;
|
||||
|
||||
// clamp sample
|
||||
if ( (blip_sample_t) s != s )
|
||||
out [-2] = (blip_sample_t) (0x7FFF - (s >> 24));
|
||||
}
|
||||
}
|
||||
|
||||
reader_accum = accum;
|
||||
remove_samples( count );
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Blip_Buffer::mix_samples( blip_sample_t const* in, long count )
|
||||
{
|
||||
buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2;
|
||||
|
||||
int const sample_shift = blip_sample_bits - 16;
|
||||
int prev = 0;
|
||||
while ( count-- )
|
||||
{
|
||||
long s = (long) *in++ << sample_shift;
|
||||
*out += s - prev;
|
||||
prev = s;
|
||||
++out;
|
||||
}
|
||||
*out -= prev;
|
||||
}
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
|
||||
// Band-limited sound synthesis and buffering
|
||||
|
||||
// Blip_Buffer 0.4.0
|
||||
|
||||
#ifndef BLIP_BUFFER_H
|
||||
#define BLIP_BUFFER_H
|
||||
|
||||
// Time unit at source clock rate
|
||||
typedef long blip_time_t;
|
||||
|
||||
// Output samples are 16-bit signed, with a range of -32768 to 32767
|
||||
typedef short blip_sample_t;
|
||||
enum { blip_sample_max = 32767 };
|
||||
|
||||
class Blip_Buffer {
|
||||
public:
|
||||
typedef const char* blargg_err_t;
|
||||
|
||||
// Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults
|
||||
// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there
|
||||
// isn't enough memory, returns error without affecting current buffer setup.
|
||||
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 );
|
||||
|
||||
// Set number of source time units per second
|
||||
void clock_rate( long );
|
||||
|
||||
// End current time frame of specified duration and make its samples available
|
||||
// (along with any still-unread samples) for reading with read_samples(). Begins
|
||||
// a new time frame at the end of the current frame.
|
||||
void end_frame( blip_time_t time );
|
||||
|
||||
// Read at most 'max_samples' out of buffer into 'dest', removing them from from
|
||||
// the buffer. Returns number of samples actually read and removed. If stereo is
|
||||
// true, increments 'dest' one extra time after writing each sample, to allow
|
||||
// easy interleving of two channels into a stereo output buffer.
|
||||
long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 );
|
||||
|
||||
// Additional optional features
|
||||
|
||||
// Current output sample rate
|
||||
long sample_rate() const;
|
||||
|
||||
// Length of buffer, in milliseconds
|
||||
int length() const;
|
||||
|
||||
// Number of source time units per second
|
||||
long clock_rate() const;
|
||||
|
||||
// Set frequency high-pass filter frequency, where higher values reduce bass more
|
||||
void bass_freq( int frequency );
|
||||
|
||||
// Number of samples delay from synthesis to samples read out
|
||||
int output_latency() const;
|
||||
|
||||
// Remove all available samples and clear buffer to silence. If 'entire_buffer' is
|
||||
// false, just clears out any samples waiting rather than the entire buffer.
|
||||
void clear( int entire_buffer = 1 );
|
||||
|
||||
// Number of samples available for reading with read_samples()
|
||||
long samples_avail() const;
|
||||
|
||||
// Remove 'count' samples from those waiting to be read
|
||||
void remove_samples( long count );
|
||||
|
||||
// Experimental features
|
||||
|
||||
// Number of raw samples that can be mixed within frame of specified duration.
|
||||
long count_samples( blip_time_t duration ) const;
|
||||
|
||||
// Mix 'count' samples from 'buf' into buffer.
|
||||
void mix_samples( blip_sample_t const* buf, long count );
|
||||
|
||||
// Count number of clocks needed until 'count' samples will be available.
|
||||
// If buffer can't even hold 'count' samples, returns number of clocks until
|
||||
// buffer becomes full.
|
||||
blip_time_t count_clocks( long count ) const;
|
||||
|
||||
// not documented yet
|
||||
typedef unsigned long blip_resampled_time_t;
|
||||
void remove_silence( long count );
|
||||
blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; }
|
||||
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; }
|
||||
blip_resampled_time_t clock_rate_factor( long clock_rate ) const;
|
||||
public:
|
||||
Blip_Buffer();
|
||||
~Blip_Buffer();
|
||||
|
||||
// Deprecated
|
||||
typedef blip_resampled_time_t resampled_time_t;
|
||||
blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); }
|
||||
blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); }
|
||||
private:
|
||||
// noncopyable
|
||||
Blip_Buffer( const Blip_Buffer& );
|
||||
Blip_Buffer& operator = ( const Blip_Buffer& );
|
||||
public:
|
||||
typedef long buf_t_;
|
||||
unsigned long factor_;
|
||||
blip_resampled_time_t offset_;
|
||||
buf_t_* buffer_;
|
||||
long buffer_size_;
|
||||
private:
|
||||
long reader_accum;
|
||||
int bass_shift;
|
||||
long sample_rate_;
|
||||
long clock_rate_;
|
||||
int bass_freq_;
|
||||
int length_;
|
||||
friend class Blip_Reader;
|
||||
};
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
// Number of bits in resample ratio fraction. Higher values give a more accurate ratio
|
||||
// but reduce maximum buffer size.
|
||||
#ifndef BLIP_BUFFER_ACCURACY
|
||||
#define BLIP_BUFFER_ACCURACY 16
|
||||
#endif
|
||||
|
||||
// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in
|
||||
// noticeable broadband noise when synthesizing high frequency square waves.
|
||||
// Affects size of Blip_Synth objects since they store the waveform directly.
|
||||
#ifndef BLIP_PHASE_BITS
|
||||
#define BLIP_PHASE_BITS 6
|
||||
#endif
|
||||
|
||||
// Internal
|
||||
typedef unsigned long blip_resampled_time_t;
|
||||
int const blip_widest_impulse_ = 16;
|
||||
int const blip_res = 1 << BLIP_PHASE_BITS;
|
||||
class blip_eq_t;
|
||||
|
||||
class Blip_Synth_ {
|
||||
double volume_unit_;
|
||||
short* const impulses;
|
||||
int const width;
|
||||
long kernel_unit;
|
||||
int impulses_size() const { return blip_res / 2 * width + 1; }
|
||||
void adjust_impulse();
|
||||
public:
|
||||
Blip_Buffer* buf;
|
||||
int last_amp;
|
||||
int delta_factor;
|
||||
|
||||
Blip_Synth_( short* impulses, int width );
|
||||
void treble_eq( blip_eq_t const& );
|
||||
void volume_unit( double );
|
||||
};
|
||||
|
||||
// Quality level. Start with blip_good_quality.
|
||||
const int blip_med_quality = 8;
|
||||
const int blip_good_quality = 12;
|
||||
const int blip_high_quality = 16;
|
||||
|
||||
// Range specifies the greatest expected change in amplitude. Calculate it
|
||||
// by finding the difference between the maximum and minimum expected
|
||||
// amplitudes (max - min).
|
||||
template<int quality,int range>
|
||||
class Blip_Synth {
|
||||
public:
|
||||
// Set overall volume of waveform
|
||||
void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); }
|
||||
|
||||
// Configure low-pass filter (see notes.txt)
|
||||
void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); }
|
||||
|
||||
// Get/set Blip_Buffer used for output
|
||||
Blip_Buffer* output() const { return impl.buf; }
|
||||
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
|
||||
|
||||
// Update amplitude of waveform at given time. Using this requires a separate
|
||||
// Blip_Synth for each waveform.
|
||||
void update( blip_time_t time, int amplitude );
|
||||
|
||||
// Low-level interface
|
||||
|
||||
// Add an amplitude transition of specified delta, optionally into specified buffer
|
||||
// rather than the one set with output(). Delta can be positive or negative.
|
||||
// The actual change in amplitude is delta * (volume / range)
|
||||
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
|
||||
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); }
|
||||
|
||||
// Works directly in terms of fractional output samples. Contact author for more.
|
||||
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
|
||||
|
||||
// Same as offset(), except code is inlined for higher performance
|
||||
void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const {
|
||||
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
|
||||
}
|
||||
void offset_inline( blip_time_t t, int delta ) const {
|
||||
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
|
||||
}
|
||||
|
||||
public:
|
||||
Blip_Synth() : impl( impulses, quality ) { }
|
||||
private:
|
||||
typedef short imp_t;
|
||||
imp_t impulses [blip_res * (quality / 2) + 1];
|
||||
Blip_Synth_ impl;
|
||||
};
|
||||
|
||||
// Low-pass equalization parameters
|
||||
class blip_eq_t {
|
||||
public:
|
||||
// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce
|
||||
// treble, small positive values (0 to 5.0) increase treble.
|
||||
blip_eq_t( double treble_db = 0 );
|
||||
|
||||
// See notes.txt
|
||||
blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 );
|
||||
|
||||
private:
|
||||
double treble;
|
||||
long rolloff_freq;
|
||||
long sample_rate;
|
||||
long cutoff_freq;
|
||||
void generate( float* out, int count ) const;
|
||||
friend class Blip_Synth_;
|
||||
};
|
||||
|
||||
int const blip_sample_bits = 30;
|
||||
|
||||
// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples
|
||||
class Blip_Reader {
|
||||
public:
|
||||
// Begin reading samples from buffer. Returns value to pass to next() (can
|
||||
// be ignored if default bass_freq is acceptable).
|
||||
int begin( Blip_Buffer& );
|
||||
|
||||
// Current sample
|
||||
long read() const { return accum >> (blip_sample_bits - 16); }
|
||||
|
||||
// Current raw sample in full internal resolution
|
||||
long read_raw() const { return accum; }
|
||||
|
||||
// Advance to next sample
|
||||
void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); }
|
||||
|
||||
// End reading samples from buffer. The number of samples read must now be removed
|
||||
// using Blip_Buffer::remove_samples().
|
||||
void end( Blip_Buffer& b ) { b.reader_accum = accum; }
|
||||
|
||||
private:
|
||||
const Blip_Buffer::buf_t_* buf;
|
||||
long accum;
|
||||
};
|
||||
|
||||
|
||||
// End of public interface
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// Compatibility with older version
|
||||
const long blip_unscaled = 65535;
|
||||
const int blip_low_quality = blip_med_quality;
|
||||
const int blip_best_quality = blip_high_quality;
|
||||
|
||||
#define BLIP_FWD( i ) { \
|
||||
long t0 = i0 * delta + buf [fwd + i]; \
|
||||
long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \
|
||||
i0 = imp [blip_res * (i + 2)]; \
|
||||
buf [fwd + i] = t0; \
|
||||
buf [fwd + 1 + i] = t1; }
|
||||
|
||||
#define BLIP_REV( r ) { \
|
||||
long t0 = i0 * delta + buf [rev - r]; \
|
||||
long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \
|
||||
i0 = imp [blip_res * (r - 1)]; \
|
||||
buf [rev - r] = t0; \
|
||||
buf [rev + 1 - r] = t1; }
|
||||
|
||||
template<int quality,int range>
|
||||
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
|
||||
int delta, Blip_Buffer* blip_buf ) const
|
||||
{
|
||||
// Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the
|
||||
// need for a longer buffer as set by set_sample_rate().
|
||||
assert( (long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ );
|
||||
delta *= impl.delta_factor;
|
||||
int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1));
|
||||
imp_t const* imp = impulses + blip_res - phase;
|
||||
long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY);
|
||||
long i0 = *imp;
|
||||
|
||||
int const fwd = (blip_widest_impulse_ - quality) / 2;
|
||||
int const rev = fwd + quality - 2;
|
||||
|
||||
BLIP_FWD( 0 )
|
||||
if ( quality > 8 ) BLIP_FWD( 2 )
|
||||
if ( quality > 12 ) BLIP_FWD( 4 )
|
||||
{
|
||||
int const mid = quality / 2 - 1;
|
||||
long t0 = i0 * delta + buf [fwd + mid - 1];
|
||||
long t1 = imp [blip_res * mid] * delta + buf [fwd + mid];
|
||||
imp = impulses + phase;
|
||||
i0 = imp [blip_res * mid];
|
||||
buf [fwd + mid - 1] = t0;
|
||||
buf [fwd + mid] = t1;
|
||||
}
|
||||
if ( quality > 12 ) BLIP_REV( 6 )
|
||||
if ( quality > 8 ) BLIP_REV( 4 )
|
||||
BLIP_REV( 2 )
|
||||
|
||||
long t0 = i0 * delta + buf [rev];
|
||||
long t1 = *imp * delta + buf [rev + 1];
|
||||
buf [rev] = t0;
|
||||
buf [rev + 1] = t1;
|
||||
}
|
||||
|
||||
#undef BLIP_FWD
|
||||
#undef BLIP_REV
|
||||
|
||||
template<int quality,int range>
|
||||
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
|
||||
{
|
||||
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
|
||||
}
|
||||
|
||||
template<int quality,int range>
|
||||
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
|
||||
{
|
||||
int delta = amp - impl.last_amp;
|
||||
impl.last_amp = amp;
|
||||
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
|
||||
}
|
||||
|
||||
inline blip_eq_t::blip_eq_t( double t ) :
|
||||
treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
|
||||
inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) :
|
||||
treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
|
||||
|
||||
inline int Blip_Buffer::length() const { return length_; }
|
||||
inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); }
|
||||
inline long Blip_Buffer::sample_rate() const { return sample_rate_; }
|
||||
inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; }
|
||||
inline long Blip_Buffer::clock_rate() const { return clock_rate_; }
|
||||
inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
|
||||
|
||||
inline int Blip_Reader::begin( Blip_Buffer& blip_buf )
|
||||
{
|
||||
buf = blip_buf.buffer_;
|
||||
accum = blip_buf.reader_accum;
|
||||
return blip_buf.bass_shift;
|
||||
}
|
||||
|
||||
int const blip_max_length = 0;
|
||||
int const blip_default_length = 250;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
|
||||
// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding
|
||||
// waveforms to a Blip_Buffer.
|
||||
|
||||
// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
|
||||
|
||||
#ifndef BLIP_SYNTH_H
|
||||
#define BLIP_SYNTH_H
|
||||
|
||||
#ifndef BLIP_BUFFER_H
|
||||
#include "Blip_Buffer.h"
|
||||
#endif
|
||||
|
||||
// Quality level. Higher levels are slower, and worse in a few cases.
|
||||
// Use blip_good_quality as a starting point.
|
||||
const int blip_low_quality = 1;
|
||||
const int blip_med_quality = 2;
|
||||
const int blip_good_quality = 3;
|
||||
const int blip_high_quality = 4;
|
||||
|
||||
// Blip_Synth is a transition waveform synthesizer which adds band-limited
|
||||
// offsets (transitions) into a Blip_Buffer. For a simpler interface, use
|
||||
// Blip_Wave (below).
|
||||
//
|
||||
// Range specifies the greatest expected offset that will occur. For a
|
||||
// waveform that goes between +amp and -amp, range should be amp * 2 (half
|
||||
// that if it only goes between +amp and 0). When range is large, a higher
|
||||
// accuracy scheme is used; to force this even when range is small, pass
|
||||
// the negative of range (i.e. -range).
|
||||
template<int quality,int range>
|
||||
class Blip_Synth {
|
||||
BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 );
|
||||
BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 );
|
||||
enum {
|
||||
abs_range = (range < 0) ? -range : range,
|
||||
fine_mode = (range > 512 || range < 0),
|
||||
width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_),
|
||||
res = 1 << blip_res_bits_,
|
||||
impulse_size = width / 2 * (fine_mode + 1),
|
||||
base_impulses_size = width / 2 * (res / 2 + 1),
|
||||
fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 :
|
||||
abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 :
|
||||
abs_range <= 2048 ? 7 : 8) : 0)
|
||||
};
|
||||
blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size];
|
||||
Blip_Impulse_ impulse;
|
||||
void init() { impulse.init( impulses, width, res, fine_bits ); }
|
||||
public:
|
||||
Blip_Synth() { init(); }
|
||||
Blip_Synth( double volume ) { init(); this->volume( volume ); }
|
||||
|
||||
// Configure low-pass filter (see notes.txt). Not optimized for real-time control
|
||||
void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); }
|
||||
|
||||
// Set volume of a transition at amplitude 'range' by setting volume_unit
|
||||
// to v / range
|
||||
void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); }
|
||||
|
||||
// Set base volume unit of transitions, where 1.0 is a full swing between the
|
||||
// positive and negative extremes. Not optimized for real-time control.
|
||||
void volume_unit( double unit ) { impulse.volume_unit( unit ); }
|
||||
|
||||
// Default Blip_Buffer used for output when none is specified for a given call
|
||||
Blip_Buffer* output() const { return impulse.buf; }
|
||||
void output( Blip_Buffer* b ) { impulse.buf = b; }
|
||||
|
||||
// Add an amplitude offset (transition) with a magnitude of delta * volume_unit
|
||||
// into the specified buffer (default buffer if none specified) at the
|
||||
// specified source time. Delta can be positive or negative. To increase
|
||||
// performance by inlining code at the call site, use offset_inline().
|
||||
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
|
||||
|
||||
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
|
||||
void offset_resampled( blip_resampled_time_t t, int o ) const {
|
||||
offset_resampled( t, o, impulse.buf );
|
||||
}
|
||||
void offset( blip_time_t t, int delta ) const {
|
||||
offset( t, delta, impulse.buf );
|
||||
}
|
||||
void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const {
|
||||
offset_resampled( time * buf->factor_ + buf->offset_, delta, buf );
|
||||
}
|
||||
void offset_inline( blip_time_t time, int delta ) const {
|
||||
offset_inline( time, delta, impulse.buf );
|
||||
}
|
||||
};
|
||||
|
||||
// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer.
|
||||
// A wave is built from a series of delays and new amplitudes. This provides a
|
||||
// simpler interface than Blip_Synth, nothing more.
|
||||
template<int quality,int range>
|
||||
class Blip_Wave {
|
||||
Blip_Synth<quality,range> synth;
|
||||
blip_time_t time_;
|
||||
int last_amp;
|
||||
void init() { time_ = 0; last_amp = 0; }
|
||||
public:
|
||||
// Start wave at time 0 and amplitude 0
|
||||
Blip_Wave() { init(); }
|
||||
Blip_Wave( double volume ) { init(); this->volume( volume ); }
|
||||
|
||||
// See Blip_Synth for description
|
||||
void volume( double v ) { synth.volume( v ); }
|
||||
void volume_unit( double v ) { synth.volume_unit( v ); }
|
||||
void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); }
|
||||
Blip_Buffer* output() const { return synth.output(); }
|
||||
void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; }
|
||||
|
||||
// Current time in frame
|
||||
blip_time_t time() const { return time_; }
|
||||
void time( blip_time_t t ) { time_ = t; }
|
||||
|
||||
// Current amplitude of wave
|
||||
int amplitude() const { return last_amp; }
|
||||
void amplitude( int );
|
||||
|
||||
// Move forward by 't' time units
|
||||
void delay( blip_time_t t ) { time_ += t; }
|
||||
|
||||
// End time frame of specified duration. Localize time to new frame.
|
||||
// If wave hadn't been run to end of frame, start it at beginning of new frame.
|
||||
void end_frame( blip_time_t duration )
|
||||
{
|
||||
time_ -= duration;
|
||||
if ( time_ < 0 )
|
||||
time_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// End of public interface
|
||||
|
||||
template<int quality,int range>
|
||||
void Blip_Wave<quality,range>::amplitude( int amp ) {
|
||||
int delta = amp - last_amp;
|
||||
last_amp = amp;
|
||||
synth.offset_inline( time_, delta );
|
||||
}
|
||||
|
||||
template<int quality,int range>
|
||||
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
|
||||
int delta, Blip_Buffer* blip_buf ) const
|
||||
{
|
||||
typedef blip_pair_t_ pair_t;
|
||||
|
||||
unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1;
|
||||
assert(( "Blip_Synth/Blip_wave: Went past end of buffer",
|
||||
sample_index < blip_buf->buffer_size_ ));
|
||||
enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 };
|
||||
pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index];
|
||||
|
||||
enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ };
|
||||
enum { mask = res * 2 - 1 };
|
||||
const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size];
|
||||
|
||||
pair_t offset = impulse.offset * delta;
|
||||
|
||||
if ( !fine_bits )
|
||||
{
|
||||
// normal mode
|
||||
for ( int n = width / 4; n; --n )
|
||||
{
|
||||
pair_t t0 = buf [0] - offset;
|
||||
pair_t t1 = buf [1] - offset;
|
||||
|
||||
t0 += imp [0] * delta;
|
||||
t1 += imp [1] * delta;
|
||||
imp += 2;
|
||||
|
||||
buf [0] = t0;
|
||||
buf [1] = t1;
|
||||
buf += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// fine mode
|
||||
enum { sub_range = 1 << fine_bits };
|
||||
delta += sub_range / 2;
|
||||
int delta2 = (delta & (sub_range - 1)) - sub_range / 2;
|
||||
delta >>= fine_bits;
|
||||
|
||||
for ( int n = width / 4; n; --n )
|
||||
{
|
||||
pair_t t0 = buf [0] - offset;
|
||||
pair_t t1 = buf [1] - offset;
|
||||
|
||||
t0 += imp [0] * delta2;
|
||||
t0 += imp [1] * delta;
|
||||
|
||||
t1 += imp [2] * delta2;
|
||||
t1 += imp [3] * delta;
|
||||
|
||||
imp += 4;
|
||||
|
||||
buf [0] = t0;
|
||||
buf [1] = t1;
|
||||
buf += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int quality,int range>
|
||||
void Blip_Synth<quality,range>::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const {
|
||||
offset_resampled( time * buf->factor_ + buf->offset_, delta, buf );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,518 @@
|
|||
|
||||
// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Effects_Buffer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
typedef long fixed_t;
|
||||
|
||||
#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5)
|
||||
#define FMUL( x, y ) (((x) * (y)) >> 15)
|
||||
|
||||
const unsigned echo_size = 4096;
|
||||
const unsigned echo_mask = echo_size - 1;
|
||||
BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2
|
||||
|
||||
const unsigned reverb_size = 8192 * 2;
|
||||
const unsigned reverb_mask = reverb_size - 1;
|
||||
BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2
|
||||
|
||||
Effects_Buffer::config_t::config_t()
|
||||
{
|
||||
pan_1 = -0.15f;
|
||||
pan_2 = 0.15f;
|
||||
reverb_delay = 88.0f;
|
||||
reverb_level = 0.12f;
|
||||
echo_delay = 61.0f;
|
||||
echo_level = 0.10f;
|
||||
delay_variance = 18.0f;
|
||||
effects_enabled = false;
|
||||
}
|
||||
|
||||
void Effects_Buffer::set_depth( double d )
|
||||
{
|
||||
float f = (float) d;
|
||||
config_t c;
|
||||
c.pan_1 = -0.6f * f;
|
||||
c.pan_2 = 0.6f * f;
|
||||
c.reverb_delay = 880 * 0.1f;
|
||||
c.echo_delay = 610 * 0.1f;
|
||||
if ( f > 0.5 )
|
||||
f = 0.5; // TODO: more linear reduction of extreme reverb/echo
|
||||
c.reverb_level = 0.5f * f;
|
||||
c.echo_level = 0.30f * f;
|
||||
c.delay_variance = 180 * 0.1f;
|
||||
c.effects_enabled = (d > 0.0f);
|
||||
config( c );
|
||||
}
|
||||
|
||||
Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 )
|
||||
{
|
||||
buf_count = center_only ? max_buf_count - 4 : max_buf_count;
|
||||
|
||||
echo_buf = NULL;
|
||||
echo_pos = 0;
|
||||
|
||||
reverb_buf = NULL;
|
||||
reverb_pos = 0;
|
||||
|
||||
stereo_remain = 0;
|
||||
effect_remain = 0;
|
||||
effects_enabled = false;
|
||||
set_depth( 0 );
|
||||
}
|
||||
|
||||
Effects_Buffer::~Effects_Buffer()
|
||||
{
|
||||
delete [] echo_buf;
|
||||
delete [] reverb_buf;
|
||||
}
|
||||
|
||||
blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
if ( !echo_buf )
|
||||
{
|
||||
echo_buf = BLARGG_NEW blip_sample_t [echo_size];
|
||||
CHECK_ALLOC( echo_buf );
|
||||
}
|
||||
|
||||
if ( !reverb_buf )
|
||||
{
|
||||
reverb_buf = BLARGG_NEW blip_sample_t [reverb_size];
|
||||
CHECK_ALLOC( reverb_buf );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
|
||||
|
||||
config( config_ );
|
||||
clear();
|
||||
|
||||
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
|
||||
}
|
||||
|
||||
void Effects_Buffer::clock_rate( long rate )
|
||||
{
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].clock_rate( rate );
|
||||
}
|
||||
|
||||
void Effects_Buffer::bass_freq( int freq )
|
||||
{
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].bass_freq( freq );
|
||||
}
|
||||
|
||||
void Effects_Buffer::clear()
|
||||
{
|
||||
stereo_remain = 0;
|
||||
effect_remain = 0;
|
||||
if ( echo_buf )
|
||||
memset( echo_buf, 0, echo_size * sizeof *echo_buf );
|
||||
if ( reverb_buf )
|
||||
memset( reverb_buf, 0, reverb_size * sizeof *reverb_buf );
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].clear();
|
||||
}
|
||||
|
||||
inline int pin_range( int n, int max, int min = 0 )
|
||||
{
|
||||
if ( n < min )
|
||||
return min;
|
||||
if ( n > max )
|
||||
return max;
|
||||
return n;
|
||||
}
|
||||
|
||||
void Effects_Buffer::config( const config_t& cfg )
|
||||
{
|
||||
channels_changed();
|
||||
|
||||
// clear echo and reverb buffers
|
||||
if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf )
|
||||
{
|
||||
memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) );
|
||||
memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) );
|
||||
}
|
||||
|
||||
config_ = cfg;
|
||||
|
||||
if ( config_.effects_enabled )
|
||||
{
|
||||
// convert to internal format
|
||||
|
||||
chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 );
|
||||
chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0];
|
||||
|
||||
chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 );
|
||||
chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0];
|
||||
|
||||
chans.reverb_level = TO_FIXED( config_.reverb_level );
|
||||
chans.echo_level = TO_FIXED( config_.echo_level );
|
||||
|
||||
int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate());
|
||||
|
||||
int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate());
|
||||
chans.reverb_delay_l = pin_range( reverb_size -
|
||||
(reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 );
|
||||
chans.reverb_delay_r = pin_range( reverb_size + 1 -
|
||||
(reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 );
|
||||
|
||||
int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate());
|
||||
chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset),
|
||||
echo_size - 1 );
|
||||
chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset),
|
||||
echo_size - 1 );
|
||||
|
||||
// set up outputs
|
||||
for ( unsigned i = 0; i < chan_count; i++ )
|
||||
{
|
||||
channel_t& o = channels [i];
|
||||
if ( i < 2 )
|
||||
{
|
||||
o.center = &bufs [i];
|
||||
o.left = &bufs [3];
|
||||
o.right = &bufs [4];
|
||||
}
|
||||
else
|
||||
{
|
||||
o.center = &bufs [2];
|
||||
o.left = &bufs [5];
|
||||
o.right = &bufs [6];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// set up outputs
|
||||
for ( unsigned i = 0; i < chan_count; i++ )
|
||||
{
|
||||
channel_t& o = channels [i];
|
||||
o.center = &bufs [0];
|
||||
o.left = &bufs [1];
|
||||
o.right = &bufs [2];
|
||||
}
|
||||
}
|
||||
|
||||
if ( buf_count < max_buf_count )
|
||||
{
|
||||
for ( unsigned i = 0; i < chan_count; i++ )
|
||||
{
|
||||
channel_t& o = channels [i];
|
||||
o.left = o.center;
|
||||
o.right = o.center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo )
|
||||
{
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].end_frame( clock_count );
|
||||
|
||||
if ( stereo && buf_count == max_buf_count )
|
||||
stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency();
|
||||
|
||||
if ( effects_enabled || config_.effects_enabled )
|
||||
effect_remain = bufs [0].samples_avail() + bufs [0].output_latency();
|
||||
|
||||
effects_enabled = config_.effects_enabled;
|
||||
}
|
||||
|
||||
long Effects_Buffer::samples_avail() const
|
||||
{
|
||||
return bufs [0].samples_avail() * 2;
|
||||
}
|
||||
|
||||
long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples )
|
||||
{
|
||||
require( total_samples % 2 == 0 ); // count must be even
|
||||
|
||||
long remain = bufs [0].samples_avail();
|
||||
if ( remain > (total_samples >> 1) )
|
||||
remain = (total_samples >> 1);
|
||||
total_samples = remain;
|
||||
while ( remain )
|
||||
{
|
||||
int active_bufs = buf_count;
|
||||
long count = remain;
|
||||
|
||||
// optimizing mixing to skip any channels which had nothing added
|
||||
if ( effect_remain )
|
||||
{
|
||||
if ( count > effect_remain )
|
||||
count = effect_remain;
|
||||
|
||||
if ( stereo_remain )
|
||||
{
|
||||
mix_enhanced( out, count );
|
||||
}
|
||||
else
|
||||
{
|
||||
mix_mono_enhanced( out, count );
|
||||
active_bufs = 3;
|
||||
}
|
||||
}
|
||||
else if ( stereo_remain )
|
||||
{
|
||||
mix_stereo( out, count );
|
||||
active_bufs = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
mix_mono( out, count );
|
||||
active_bufs = 1;
|
||||
}
|
||||
|
||||
out += count * 2;
|
||||
remain -= count;
|
||||
|
||||
stereo_remain -= count;
|
||||
if ( stereo_remain < 0 )
|
||||
stereo_remain = 0;
|
||||
|
||||
effect_remain -= count;
|
||||
if ( effect_remain < 0 )
|
||||
effect_remain = 0;
|
||||
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
{
|
||||
if ( i < active_bufs )
|
||||
bufs [i].remove_samples( count );
|
||||
else
|
||||
bufs [i].remove_silence( count ); // keep time synchronized
|
||||
}
|
||||
}
|
||||
|
||||
return total_samples * 2;
|
||||
}
|
||||
|
||||
void Effects_Buffer::mix_mono( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader c;
|
||||
int shift = c.begin( bufs [0] );
|
||||
|
||||
// unrolled loop
|
||||
for ( long n = count >> 1; n--; )
|
||||
{
|
||||
long cs0 = c.read();
|
||||
c.next( shift );
|
||||
|
||||
long cs1 = c.read();
|
||||
c.next( shift );
|
||||
|
||||
if ( (BOOST::int16_t) cs0 != cs0 )
|
||||
cs0 = 0x7FFF - (cs0 >> 24);
|
||||
((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16);
|
||||
|
||||
if ( (BOOST::int16_t) cs1 != cs1 )
|
||||
cs1 = 0x7FFF - (cs1 >> 24);
|
||||
((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16);
|
||||
out += 4;
|
||||
}
|
||||
|
||||
if ( count & 1 )
|
||||
{
|
||||
int s = c.read();
|
||||
c.next( shift );
|
||||
out [0] = s;
|
||||
out [1] = s;
|
||||
if ( (BOOST::int16_t) s != s )
|
||||
{
|
||||
s = 0x7FFF - (s >> 24);
|
||||
out [0] = s;
|
||||
out [1] = s;
|
||||
}
|
||||
}
|
||||
|
||||
c.end( bufs [0] );
|
||||
}
|
||||
|
||||
void Effects_Buffer::mix_stereo( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader l; l.begin( bufs [1] );
|
||||
Blip_Reader r; r.begin( bufs [2] );
|
||||
Blip_Reader c;
|
||||
int shift = c.begin( bufs [0] );
|
||||
|
||||
while ( count-- )
|
||||
{
|
||||
int cs = c.read();
|
||||
c.next( shift );
|
||||
int left = cs + l.read();
|
||||
int right = cs + r.read();
|
||||
l.next( shift );
|
||||
r.next( shift );
|
||||
|
||||
if ( (BOOST::int16_t) left != left )
|
||||
left = 0x7FFF - (left >> 24);
|
||||
|
||||
out [0] = left;
|
||||
out [1] = right;
|
||||
|
||||
out += 2;
|
||||
|
||||
if ( (BOOST::int16_t) right != right )
|
||||
out [-1] = 0x7FFF - (right >> 24);
|
||||
}
|
||||
|
||||
c.end( bufs [0] );
|
||||
r.end( bufs [2] );
|
||||
l.end( bufs [1] );
|
||||
}
|
||||
|
||||
void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader sq1; sq1.begin( bufs [0] );
|
||||
Blip_Reader sq2; sq2.begin( bufs [1] );
|
||||
Blip_Reader center;
|
||||
int shift = center.begin( bufs [2] );
|
||||
|
||||
int echo_pos = this->echo_pos;
|
||||
int reverb_pos = this->reverb_pos;
|
||||
|
||||
while ( count-- )
|
||||
{
|
||||
int sum1_s = sq1.read();
|
||||
int sum2_s = sq2.read();
|
||||
|
||||
sq1.next( shift );
|
||||
sq2.next( shift );
|
||||
|
||||
int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) +
|
||||
FMUL( sum2_s, chans.pan_2_levels [0] ) +
|
||||
reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask];
|
||||
|
||||
int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) +
|
||||
FMUL( sum2_s, chans.pan_2_levels [1] ) +
|
||||
reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask];
|
||||
|
||||
fixed_t reverb_level = chans.reverb_level;
|
||||
reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level );
|
||||
reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level );
|
||||
reverb_pos = (reverb_pos + 2) & reverb_mask;
|
||||
|
||||
int sum3_s = center.read();
|
||||
center.next( shift );
|
||||
|
||||
int left = new_reverb_l + sum3_s + FMUL( chans.echo_level,
|
||||
echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] );
|
||||
int right = new_reverb_r + sum3_s + FMUL( chans.echo_level,
|
||||
echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] );
|
||||
|
||||
echo_buf [echo_pos] = sum3_s;
|
||||
echo_pos = (echo_pos + 1) & echo_mask;
|
||||
|
||||
if ( (BOOST::int16_t) left != left )
|
||||
left = 0x7FFF - (left >> 24);
|
||||
|
||||
out [0] = left;
|
||||
out [1] = right;
|
||||
|
||||
out += 2;
|
||||
|
||||
if ( (BOOST::int16_t) right != right )
|
||||
out [-1] = 0x7FFF - (right >> 24);
|
||||
}
|
||||
this->reverb_pos = reverb_pos;
|
||||
this->echo_pos = echo_pos;
|
||||
|
||||
sq1.end( bufs [0] );
|
||||
sq2.end( bufs [1] );
|
||||
center.end( bufs [2] );
|
||||
}
|
||||
|
||||
void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader l1; l1.begin( bufs [3] );
|
||||
Blip_Reader r1; r1.begin( bufs [4] );
|
||||
Blip_Reader l2; l2.begin( bufs [5] );
|
||||
Blip_Reader r2; r2.begin( bufs [6] );
|
||||
Blip_Reader sq1; sq1.begin( bufs [0] );
|
||||
Blip_Reader sq2; sq2.begin( bufs [1] );
|
||||
Blip_Reader center;
|
||||
int shift = center.begin( bufs [2] );
|
||||
|
||||
int echo_pos = this->echo_pos;
|
||||
int reverb_pos = this->reverb_pos;
|
||||
|
||||
while ( count-- )
|
||||
{
|
||||
int sum1_s = sq1.read();
|
||||
int sum2_s = sq2.read();
|
||||
|
||||
sq1.next( shift );
|
||||
sq2.next( shift );
|
||||
|
||||
int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) +
|
||||
FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() +
|
||||
reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask];
|
||||
|
||||
int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) +
|
||||
FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() +
|
||||
reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask];
|
||||
|
||||
l1.next( shift );
|
||||
r1.next( shift );
|
||||
|
||||
fixed_t reverb_level = chans.reverb_level;
|
||||
reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level );
|
||||
reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level );
|
||||
reverb_pos = (reverb_pos + 2) & reverb_mask;
|
||||
|
||||
int sum3_s = center.read();
|
||||
center.next( shift );
|
||||
|
||||
int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level,
|
||||
echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] );
|
||||
int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level,
|
||||
echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] );
|
||||
|
||||
l2.next( shift );
|
||||
r2.next( shift );
|
||||
|
||||
echo_buf [echo_pos] = sum3_s;
|
||||
echo_pos = (echo_pos + 1) & echo_mask;
|
||||
|
||||
if ( (BOOST::int16_t) left != left )
|
||||
left = 0x7FFF - (left >> 24);
|
||||
|
||||
out [0] = left;
|
||||
out [1] = right;
|
||||
|
||||
out += 2;
|
||||
|
||||
if ( (BOOST::int16_t) right != right )
|
||||
out [-1] = 0x7FFF - (right >> 24);
|
||||
}
|
||||
this->reverb_pos = reverb_pos;
|
||||
this->echo_pos = echo_pos;
|
||||
|
||||
sq1.end( bufs [0] );
|
||||
sq2.end( bufs [1] );
|
||||
center.end( bufs [2] );
|
||||
l1.end( bufs [3] );
|
||||
r1.end( bufs [4] );
|
||||
l2.end( bufs [5] );
|
||||
r2.end( bufs [6] );
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
// Multi-channel effects buffer with panning, echo and reverb
|
||||
|
||||
// Game_Music_Emu 0.3.0
|
||||
|
||||
#ifndef EFFECTS_BUFFER_H
|
||||
#define EFFECTS_BUFFER_H
|
||||
|
||||
#include "Multi_Buffer.h"
|
||||
|
||||
// Effects_Buffer uses several buffers and outputs stereo sample pairs.
|
||||
class Effects_Buffer : public Multi_Buffer {
|
||||
public:
|
||||
// If center_only is true, only center buffers are created and
|
||||
// less memory is used.
|
||||
Effects_Buffer( bool center_only = false );
|
||||
|
||||
// Channel Effect Center Pan
|
||||
// ---------------------------------
|
||||
// 0,5 reverb pan_1
|
||||
// 1,6 reverb pan_2
|
||||
// 2,7 echo -
|
||||
// 3 echo -
|
||||
// 4 echo -
|
||||
|
||||
// Channel configuration
|
||||
struct config_t {
|
||||
double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right
|
||||
double pan_2;
|
||||
double echo_delay; // msec
|
||||
double echo_level; // 0.0 to 1.0
|
||||
double reverb_delay; // msec
|
||||
double delay_variance; // difference between left/right delays (msec)
|
||||
double reverb_level; // 0.0 to 1.0
|
||||
bool effects_enabled; // if false, use optimized simple mixer
|
||||
config_t();
|
||||
};
|
||||
|
||||
// Set configuration of buffer
|
||||
virtual void config( const config_t& );
|
||||
void set_depth( double );
|
||||
|
||||
public:
|
||||
~Effects_Buffer();
|
||||
blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length );
|
||||
void clock_rate( long );
|
||||
void bass_freq( int );
|
||||
void clear();
|
||||
channel_t channel( int );
|
||||
void end_frame( blip_time_t, bool was_stereo = true );
|
||||
long read_samples( blip_sample_t*, long );
|
||||
long samples_avail() const;
|
||||
private:
|
||||
typedef long fixed_t;
|
||||
|
||||
enum { max_buf_count = 7 };
|
||||
Blip_Buffer bufs [max_buf_count];
|
||||
enum { chan_count = 5 };
|
||||
channel_t channels [chan_count];
|
||||
config_t config_;
|
||||
long stereo_remain;
|
||||
long effect_remain;
|
||||
int buf_count;
|
||||
bool effects_enabled;
|
||||
|
||||
blip_sample_t* reverb_buf;
|
||||
blip_sample_t* echo_buf;
|
||||
int reverb_pos;
|
||||
int echo_pos;
|
||||
|
||||
struct {
|
||||
fixed_t pan_1_levels [2];
|
||||
fixed_t pan_2_levels [2];
|
||||
int echo_delay_l;
|
||||
int echo_delay_r;
|
||||
fixed_t echo_level;
|
||||
int reverb_delay_l;
|
||||
int reverb_delay_r;
|
||||
fixed_t reverb_level;
|
||||
} chans;
|
||||
|
||||
void mix_mono( blip_sample_t*, long );
|
||||
void mix_stereo( blip_sample_t*, long );
|
||||
void mix_enhanced( blip_sample_t*, long );
|
||||
void mix_mono_enhanced( blip_sample_t*, long );
|
||||
};
|
||||
|
||||
inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) {
|
||||
return channels [i % chan_count];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
// Sunsoft FME-07 mapper
|
||||
|
||||
// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
#include "Nes_Fme07_Apu.h"
|
||||
|
||||
/* Copyright (C) 2005 Chris Moeller */
|
||||
/* Copyright (C) 2005 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
struct fme07_state_t
|
||||
{
|
||||
// first 16 bytes in register order
|
||||
BOOST::uint8_t regs [13];
|
||||
BOOST::uint8_t irq_mode;
|
||||
BOOST::uint16_t irq_count;
|
||||
|
||||
BOOST::uint8_t command;
|
||||
BOOST::uint8_t irq_pending;
|
||||
fme07_snapshot_t sound_state; // only used when saving/restoring state
|
||||
|
||||
void swap();
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (fme07_state_t) == 18 + sizeof (fme07_snapshot_t) );
|
||||
|
||||
void fme07_state_t::swap()
|
||||
{
|
||||
set_le16( &irq_count, irq_count );
|
||||
for ( int i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ )
|
||||
set_le16( &sound_state.delays [i], sound_state.delays [i] );
|
||||
}
|
||||
|
||||
class Mapper_Fme07 : public Nes_Mapper, fme07_state_t {
|
||||
nes_time_t last_time;
|
||||
Nes_Fme07_Apu sound;
|
||||
public:
|
||||
Mapper_Fme07()
|
||||
{
|
||||
fme07_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual int channel_count() const { return sound.osc_count; }
|
||||
|
||||
virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); }
|
||||
|
||||
virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); }
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
regs [8] = 0x40; // wram disabled
|
||||
irq_count = 0xFFFF;
|
||||
sound.reset();
|
||||
}
|
||||
|
||||
virtual void save_state( mapper_state_t& out )
|
||||
{
|
||||
sound.save_snapshot( &sound_state );
|
||||
fme07_state_t::swap();
|
||||
Nes_Mapper::save_state( out );
|
||||
fme07_state_t::swap(); // to do: kind of hacky to swap in place
|
||||
}
|
||||
|
||||
virtual void read_state( mapper_state_t const& in )
|
||||
{
|
||||
Nes_Mapper::read_state( in );
|
||||
fme07_state_t::swap();
|
||||
sound.load_snapshot( sound_state );
|
||||
}
|
||||
|
||||
void write_register( int index, int data );
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
last_time = 0;
|
||||
for ( int i = 0; i < sizeof regs; i++ )
|
||||
write_register( i, regs [i] );
|
||||
}
|
||||
|
||||
virtual void run_until( nes_time_t end_time )
|
||||
{
|
||||
int new_count = irq_count - (end_time - last_time);
|
||||
last_time = end_time;
|
||||
|
||||
if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 )
|
||||
irq_pending = true;
|
||||
|
||||
if ( irq_mode & 0x01 )
|
||||
irq_count = new_count & 0xFFFF;
|
||||
}
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t present )
|
||||
{
|
||||
if ( irq_pending )
|
||||
return 0;
|
||||
|
||||
if ( (irq_mode & 0x81) == 0x81 )
|
||||
return last_time + irq_count + 1;
|
||||
|
||||
return no_irq;
|
||||
}
|
||||
|
||||
virtual void end_frame( nes_time_t end_time )
|
||||
{
|
||||
if ( end_time > last_time )
|
||||
run_until( end_time );
|
||||
|
||||
last_time -= end_time;
|
||||
assert( last_time >= 0 );
|
||||
|
||||
sound.end_frame( end_time );
|
||||
}
|
||||
|
||||
void write_irq( nes_time_t, int index, int data );
|
||||
|
||||
virtual void write( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
switch ( addr & 0xE000 )
|
||||
{
|
||||
case 0x8000:
|
||||
command = data & 0x0F;
|
||||
break;
|
||||
|
||||
case 0xA000:
|
||||
if ( command < 0x0D )
|
||||
write_register( command, data );
|
||||
else
|
||||
write_irq( time, command, data );
|
||||
break;
|
||||
|
||||
case 0xC000:
|
||||
sound.write_latch( data );
|
||||
break;
|
||||
|
||||
case 0xE000:
|
||||
sound.write_data( time, data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Mapper_Fme07::write_irq( nes_time_t time, int index, int data )
|
||||
{
|
||||
run_until( time );
|
||||
switch ( index )
|
||||
{
|
||||
case 0x0D:
|
||||
irq_mode = data;
|
||||
if ( (irq_mode & 0x81) != 0x81 )
|
||||
irq_pending = false;
|
||||
break;
|
||||
|
||||
case 0x0E:
|
||||
irq_count = (irq_count & 0xFF00) | data;
|
||||
break;
|
||||
|
||||
case 0x0F:
|
||||
irq_count = data << 8 | (irq_count & 0xFF);
|
||||
break;
|
||||
}
|
||||
|
||||
if ( (irq_mode & 0x81) == 0x81 )
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
void Mapper_Fme07::write_register( int index, int data )
|
||||
{
|
||||
regs [index] = data;
|
||||
int prg_bank = index - 0x09;
|
||||
if ( (unsigned) prg_bank < 3 ) // most common
|
||||
{
|
||||
set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data );
|
||||
}
|
||||
else if ( index == 0x08 )
|
||||
{
|
||||
enable_sram( (data & 0xC0) == 0xC0 );
|
||||
if ( !(data & 0xC0) )
|
||||
set_prg_bank( 0x6000, bank_8k, data & 0x3F );
|
||||
}
|
||||
else if ( index < 0x08 )
|
||||
{
|
||||
set_chr_bank( index * 0x400, bank_1k, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( index == 0x0C );
|
||||
if ( data & 2 )
|
||||
mirror_single( data & 1 );
|
||||
else if ( data & 1 )
|
||||
mirror_horiz();
|
||||
else
|
||||
mirror_vert();
|
||||
}
|
||||
}
|
||||
|
||||
void register_fme07_mapper();
|
||||
void register_fme07_mapper()
|
||||
{
|
||||
register_mapper<Mapper_Fme07>( 69 );
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
// Sunsoft FME-7 mapper
|
||||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
#include "Nes_Fme7_Apu.h"
|
||||
|
||||
/* Copyright (C) 2005 Chris Moeller */
|
||||
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
struct fme7_state_t
|
||||
{
|
||||
// first 16 bytes in register order
|
||||
BOOST::uint8_t regs [13];
|
||||
BOOST::uint8_t irq_mode;
|
||||
BOOST::uint16_t irq_count;
|
||||
|
||||
BOOST::uint8_t command;
|
||||
BOOST::uint8_t irq_pending;
|
||||
fme7_apu_state_t sound_state; // only used when saving/restoring state
|
||||
|
||||
void swap();
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) );
|
||||
|
||||
void fme7_state_t::swap()
|
||||
{
|
||||
set_le16( &irq_count, irq_count );
|
||||
for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ )
|
||||
set_le16( &sound_state.delays [i], sound_state.delays [i] );
|
||||
}
|
||||
|
||||
class Mapper_Fme7 : public Nes_Mapper, fme7_state_t {
|
||||
nes_time_t last_time;
|
||||
Nes_Fme7_Apu sound;
|
||||
public:
|
||||
Mapper_Fme7()
|
||||
{
|
||||
fme7_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual int channel_count() const { return sound.osc_count; }
|
||||
|
||||
virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); }
|
||||
|
||||
virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); }
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
regs [8] = 0x40; // wram disabled
|
||||
irq_count = 0xFFFF;
|
||||
sound.reset();
|
||||
}
|
||||
|
||||
virtual void save_state( mapper_state_t& out )
|
||||
{
|
||||
sound.save_state( &sound_state );
|
||||
fme7_state_t::swap();
|
||||
Nes_Mapper::save_state( out );
|
||||
fme7_state_t::swap(); // to do: kind of hacky to swap in place
|
||||
}
|
||||
|
||||
virtual void read_state( mapper_state_t const& in )
|
||||
{
|
||||
Nes_Mapper::read_state( in );
|
||||
fme7_state_t::swap();
|
||||
sound.load_state( sound_state );
|
||||
}
|
||||
|
||||
void write_register( int index, int data );
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
last_time = 0;
|
||||
for ( int i = 0; i < (int) sizeof regs; i++ )
|
||||
write_register( i, regs [i] );
|
||||
}
|
||||
|
||||
virtual void run_until( nes_time_t end_time )
|
||||
{
|
||||
int new_count = irq_count - (end_time - last_time);
|
||||
last_time = end_time;
|
||||
|
||||
if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 )
|
||||
irq_pending = true;
|
||||
|
||||
if ( irq_mode & 0x01 )
|
||||
irq_count = new_count & 0xFFFF;
|
||||
}
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t )
|
||||
{
|
||||
if ( irq_pending )
|
||||
return 0;
|
||||
|
||||
if ( (irq_mode & 0x81) == 0x81 )
|
||||
return last_time + irq_count + 1;
|
||||
|
||||
return no_irq;
|
||||
}
|
||||
|
||||
virtual void end_frame( nes_time_t end_time )
|
||||
{
|
||||
if ( end_time > last_time )
|
||||
run_until( end_time );
|
||||
|
||||
last_time -= end_time;
|
||||
assert( last_time >= 0 );
|
||||
|
||||
sound.end_frame( end_time );
|
||||
}
|
||||
|
||||
void write_irq( nes_time_t, int index, int data );
|
||||
|
||||
virtual void write( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
switch ( addr & 0xE000 )
|
||||
{
|
||||
case 0x8000:
|
||||
command = data & 0x0F;
|
||||
break;
|
||||
|
||||
case 0xA000:
|
||||
if ( command < 0x0D )
|
||||
write_register( command, data );
|
||||
else
|
||||
write_irq( time, command, data );
|
||||
break;
|
||||
|
||||
case 0xC000:
|
||||
sound.write_latch( data );
|
||||
break;
|
||||
|
||||
case 0xE000:
|
||||
sound.write_data( time, data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Mapper_Fme7::write_irq( nes_time_t time, int index, int data )
|
||||
{
|
||||
run_until( time );
|
||||
switch ( index )
|
||||
{
|
||||
case 0x0D:
|
||||
irq_mode = data;
|
||||
if ( (irq_mode & 0x81) != 0x81 )
|
||||
irq_pending = false;
|
||||
break;
|
||||
|
||||
case 0x0E:
|
||||
irq_count = (irq_count & 0xFF00) | data;
|
||||
break;
|
||||
|
||||
case 0x0F:
|
||||
irq_count = data << 8 | (irq_count & 0xFF);
|
||||
break;
|
||||
}
|
||||
|
||||
if ( (irq_mode & 0x81) == 0x81 )
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
void Mapper_Fme7::write_register( int index, int data )
|
||||
{
|
||||
regs [index] = data;
|
||||
int prg_bank = index - 0x09;
|
||||
if ( (unsigned) prg_bank < 3 ) // most common
|
||||
{
|
||||
set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data );
|
||||
}
|
||||
else if ( index == 0x08 )
|
||||
{
|
||||
enable_sram( (data & 0xC0) == 0xC0 );
|
||||
if ( !(data & 0xC0) )
|
||||
set_prg_bank( 0x6000, bank_8k, data & 0x3F );
|
||||
}
|
||||
else if ( index < 0x08 )
|
||||
{
|
||||
set_chr_bank( index * 0x400, bank_1k, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( index == 0x0C );
|
||||
if ( data & 2 )
|
||||
mirror_single( data & 1 );
|
||||
else if ( data & 1 )
|
||||
mirror_horiz();
|
||||
else
|
||||
mirror_vert();
|
||||
}
|
||||
}
|
||||
|
||||
void register_fme7_mapper();
|
||||
void register_fme7_mapper()
|
||||
{
|
||||
register_mapper<Mapper_Fme7>( 69 );
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
|
||||
// NES MMC5 mapper, currently only tailored for Castlevania 3 (U)
|
||||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include "Nes_Core.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
struct mmc5_state_t
|
||||
{
|
||||
enum { reg_count = 0x30 };
|
||||
byte regs [0x30];
|
||||
byte irq_enabled;
|
||||
};
|
||||
// to do: finalize state format
|
||||
BOOST_STATIC_ASSERT( sizeof (mmc5_state_t) == 0x31 );
|
||||
|
||||
class Mapper_Mmc5 : public Nes_Mapper, mmc5_state_t {
|
||||
nes_time_t irq_time;
|
||||
public:
|
||||
Mapper_Mmc5()
|
||||
{
|
||||
mmc5_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
irq_time = no_irq;
|
||||
regs [0x00] = 2;
|
||||
regs [0x01] = 3;
|
||||
regs [0x14] = 0x7f;
|
||||
regs [0x15] = 0x7f;
|
||||
regs [0x16] = 0x7f;
|
||||
regs [0x17] = 0x7f;
|
||||
}
|
||||
|
||||
virtual void read_state( mapper_state_t const& in )
|
||||
{
|
||||
Nes_Mapper::read_state( in );
|
||||
irq_time = no_irq;
|
||||
}
|
||||
|
||||
enum { regs_addr = 0x5100 };
|
||||
|
||||
virtual void apply_mapping();
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t )
|
||||
{
|
||||
if ( irq_enabled & 0x80 )
|
||||
return irq_time;
|
||||
|
||||
return no_irq;
|
||||
}
|
||||
|
||||
virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
int reg = addr - regs_addr;
|
||||
if ( (unsigned) reg < reg_count )
|
||||
{
|
||||
regs [reg] = data;
|
||||
switch ( reg )
|
||||
{
|
||||
case 0x05:
|
||||
mirror_manual( data & 3, data >> 2 & 3,
|
||||
data >> 4 & 3, data >> 6 & 3 );
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
set_prg_bank( 0x8000, bank_16k, data >> 1 & 0x3f );
|
||||
break;
|
||||
|
||||
case 0x16:
|
||||
set_prg_bank( 0xC000, bank_8k, data & 0x7f );
|
||||
break;
|
||||
|
||||
case 0x17:
|
||||
set_prg_bank( 0xE000, bank_8k, data & 0x7f );
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
case 0x21:
|
||||
case 0x22:
|
||||
case 0x23:
|
||||
case 0x28:
|
||||
case 0x29:
|
||||
case 0x2a:
|
||||
case 0x2b:
|
||||
set_chr_bank( ((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data );
|
||||
break;
|
||||
}
|
||||
check( (regs [0x00] & 3) == 2 );
|
||||
check( (regs [0x01] & 3) == 3 );
|
||||
}
|
||||
else if ( addr == 0x5203 )
|
||||
{
|
||||
irq_time = no_irq;
|
||||
if ( data && data < 240 )
|
||||
{
|
||||
irq_time = (341 * 21 + 128 + (data * 341)) / 3;
|
||||
if ( irq_time < time )
|
||||
irq_time = no_irq;
|
||||
}
|
||||
irq_changed();
|
||||
}
|
||||
else if ( addr == 0x5204 )
|
||||
{
|
||||
irq_enabled = data;
|
||||
irq_changed();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void write( nes_time_t, nes_addr_t, int ) { }
|
||||
};
|
||||
|
||||
void Mapper_Mmc5::apply_mapping()
|
||||
{
|
||||
static unsigned char list [] = {
|
||||
0x05, 0x15, 0x16, 0x17,
|
||||
0x20, 0x21, 0x22, 0x23,
|
||||
0x28, 0x29, 0x2a, 0x2b
|
||||
};
|
||||
|
||||
for ( int i = 0; i < (int) sizeof list; i++ )
|
||||
write_intercepted( 0, regs_addr + list [i], regs [list [i]] );
|
||||
intercept_writes( 0x5100, 0x200 );
|
||||
}
|
||||
|
||||
void register_mmc5_mapper();
|
||||
void register_mmc5_mapper()
|
||||
{
|
||||
register_mapper<Mapper_Mmc5>( 5 );
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
|
||||
// Namco 106 mapper
|
||||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include "Nes_Namco_Apu.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// to do: CHR mapping and nametable handling needs work
|
||||
|
||||
struct namco106_state_t
|
||||
{
|
||||
BOOST::uint8_t regs [16];
|
||||
BOOST::uint16_t irq_ctr;
|
||||
BOOST::uint8_t irq_pending;
|
||||
BOOST::uint8_t unused1 [1];
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (namco106_state_t) == 20 );
|
||||
|
||||
class Mapper_Namco106 : public Nes_Mapper, namco106_state_t {
|
||||
Nes_Namco_Apu sound;
|
||||
nes_time_t last_time;
|
||||
public:
|
||||
Mapper_Namco106()
|
||||
{
|
||||
namco106_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual int channel_count() const { return sound.osc_count; }
|
||||
|
||||
virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); }
|
||||
|
||||
virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); }
|
||||
|
||||
void reset_state()
|
||||
{
|
||||
regs [12] = 0;
|
||||
regs [13] = 1;
|
||||
regs [14] = last_bank - 1;
|
||||
sound.reset();
|
||||
}
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
last_time = 0;
|
||||
enable_sram();
|
||||
intercept_writes( 0x4800, 1 );
|
||||
intercept_reads ( 0x4800, 1 );
|
||||
|
||||
intercept_writes( 0x5000, 0x1000 );
|
||||
intercept_reads ( 0x5000, 0x1000 );
|
||||
|
||||
for ( int i = 0; i < (int) sizeof regs; i++ )
|
||||
write( 0, 0x8000 + i * 0x800, regs [i] );
|
||||
}
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t time )
|
||||
{
|
||||
if ( irq_pending )
|
||||
return time;
|
||||
|
||||
if ( !(irq_ctr & 0x8000) )
|
||||
return no_irq;
|
||||
|
||||
return 0x10000 - irq_ctr + last_time;
|
||||
}
|
||||
|
||||
virtual void run_until( nes_time_t end_time )
|
||||
{
|
||||
long count = irq_ctr + (end_time - last_time);
|
||||
if ( irq_ctr & 0x8000 )
|
||||
{
|
||||
if ( count > 0xffff )
|
||||
{
|
||||
count = 0xffff;
|
||||
irq_pending = true;
|
||||
}
|
||||
}
|
||||
else if ( count > 0x7fff )
|
||||
{
|
||||
count = 0x7fff;
|
||||
}
|
||||
|
||||
irq_ctr = count;
|
||||
last_time = end_time;
|
||||
}
|
||||
|
||||
virtual void end_frame( nes_time_t end_time )
|
||||
{
|
||||
if ( end_time > last_time )
|
||||
run_until( end_time );
|
||||
last_time -= end_time;
|
||||
assert( last_time >= 0 );
|
||||
sound.end_frame( end_time );
|
||||
}
|
||||
|
||||
void write_bank( nes_addr_t, int data );
|
||||
void write_irq( nes_time_t, nes_addr_t, int data );
|
||||
|
||||
virtual int read( nes_time_t time, nes_addr_t addr )
|
||||
{
|
||||
if ( addr == 0x4800 )
|
||||
return sound.read_data();
|
||||
|
||||
if ( addr == 0x5000 )
|
||||
{
|
||||
irq_pending = false;
|
||||
return irq_ctr & 0xff;
|
||||
}
|
||||
|
||||
if ( addr == 0x5800 )
|
||||
{
|
||||
irq_pending = false;
|
||||
return irq_ctr >> 8;
|
||||
}
|
||||
|
||||
return Nes_Mapper::read( time, addr );
|
||||
}
|
||||
|
||||
virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
if ( addr == 0x4800 )
|
||||
{
|
||||
sound.write_data( time, data );
|
||||
}
|
||||
else if ( addr == 0x5000 )
|
||||
{
|
||||
irq_ctr = (irq_ctr & 0xff00) | data;
|
||||
irq_pending = false;
|
||||
irq_changed();
|
||||
}
|
||||
else if ( addr == 0x5800 )
|
||||
{
|
||||
irq_ctr = (data << 8) | (irq_ctr & 0xff);
|
||||
irq_pending = false;
|
||||
irq_changed();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void write( nes_time_t, nes_addr_t addr, int data )
|
||||
{
|
||||
int reg = addr >> 11 & 0x0F;
|
||||
regs [reg] = data;
|
||||
|
||||
int prg_bank = reg - 0x0c;
|
||||
if ( (unsigned) prg_bank < 3 )
|
||||
{
|
||||
if ( prg_bank == 0 && (data & 0x40) )
|
||||
mirror_vert();
|
||||
set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data & 0x3F );
|
||||
}
|
||||
else if ( reg < 8 )
|
||||
{
|
||||
set_chr_bank( reg * 0x400, bank_1k, data );
|
||||
}
|
||||
else if ( reg < 0x0c )
|
||||
{
|
||||
mirror_manual( regs [8] & 1, regs [9] & 1, regs [10] & 1, regs [11] & 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
sound.write_addr( data );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void register_namco106_mapper();
|
||||
void register_namco106_mapper()
|
||||
{
|
||||
register_mapper<Mapper_Namco106>( 19 );
|
||||
}
|
||||
|
||||
// in the most obscure place in case crappy linker is used
|
||||
void register_optional_mappers();
|
||||
void register_optional_mappers()
|
||||
{
|
||||
extern void register_misc_mappers();
|
||||
register_misc_mappers();
|
||||
|
||||
extern void register_vrc6_mapper();
|
||||
register_vrc6_mapper();
|
||||
|
||||
extern void register_mmc5_mapper();
|
||||
register_mmc5_mapper();
|
||||
|
||||
extern void register_fme7_mapper();
|
||||
register_fme7_mapper();
|
||||
|
||||
extern void register_namco106_mapper();
|
||||
register_namco106_mapper();
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
|
||||
// Konami VRC6 mapper
|
||||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_Vrc6_Apu.h"
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
struct vrc6_state_t
|
||||
{
|
||||
// written registers
|
||||
byte prg_16k_bank;
|
||||
// could move sound regs int and out of vrc6_apu_state_t for state saving,
|
||||
// allowing them to be stored here
|
||||
byte old_sound_regs [3] [3]; // to do: eliminate this duplicate
|
||||
byte mirroring;
|
||||
byte prg_8k_bank;
|
||||
byte chr_banks [8];
|
||||
byte irq_reload;
|
||||
byte irq_mode;
|
||||
|
||||
// internal state
|
||||
BOOST::uint16_t next_time;
|
||||
byte irq_pending;
|
||||
byte unused;
|
||||
|
||||
vrc6_apu_state_t sound_state;
|
||||
|
||||
void swap();
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (vrc6_state_t) == 26 + sizeof (vrc6_apu_state_t) );
|
||||
|
||||
void vrc6_state_t::swap()
|
||||
{
|
||||
set_le16( &next_time, next_time );
|
||||
for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ )
|
||||
set_le16( &sound_state.delays [i], sound_state.delays [i] );
|
||||
}
|
||||
|
||||
class Mapper_Vrc6 : public Nes_Mapper, vrc6_state_t {
|
||||
int swap_mask;
|
||||
Nes_Vrc6_Apu sound;
|
||||
enum { timer_period = 113 * 4 + 3 };
|
||||
public:
|
||||
Mapper_Vrc6( int sm )
|
||||
{
|
||||
swap_mask = sm;
|
||||
vrc6_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual int channel_count() const { return sound.osc_count; }
|
||||
|
||||
virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); }
|
||||
|
||||
virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); }
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
prg_8k_bank = last_bank - 1;
|
||||
sound.reset();
|
||||
}
|
||||
|
||||
virtual void save_state( mapper_state_t& out )
|
||||
{
|
||||
sound.save_state( &sound_state );
|
||||
vrc6_state_t::swap();
|
||||
Nes_Mapper::save_state( out );
|
||||
vrc6_state_t::swap(); // to do: kind of hacky to swap in place
|
||||
}
|
||||
|
||||
virtual void read_state( mapper_state_t const& in );
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
enable_sram();
|
||||
set_prg_bank( 0x8000, bank_16k, prg_16k_bank );
|
||||
set_prg_bank( 0xC000, bank_8k, prg_8k_bank );
|
||||
|
||||
for ( int i = 0; i < (int) sizeof chr_banks; i++ )
|
||||
set_chr_bank( i * 0x400, bank_1k, chr_banks [i] );
|
||||
|
||||
write_bank( 0xb003, mirroring );
|
||||
}
|
||||
|
||||
void reset_timer( nes_time_t present )
|
||||
{
|
||||
next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4;
|
||||
}
|
||||
|
||||
virtual void run_until( nes_time_t end_time )
|
||||
{
|
||||
if ( irq_mode & 2 )
|
||||
{
|
||||
while ( next_time < end_time )
|
||||
{
|
||||
//dprintf( "%d timer expired\n", next_time );
|
||||
irq_pending = true;
|
||||
reset_timer( next_time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void end_frame( nes_time_t end_time )
|
||||
{
|
||||
run_until( end_time );
|
||||
|
||||
// to do: next_time might go negative if IRQ is disabled
|
||||
next_time -= end_time;
|
||||
|
||||
sound.end_frame( end_time );
|
||||
}
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t present )
|
||||
{
|
||||
if ( irq_pending )
|
||||
return present;
|
||||
|
||||
if ( irq_mode & 2 )
|
||||
return next_time + 1;
|
||||
|
||||
return no_irq;
|
||||
}
|
||||
|
||||
void write_bank( nes_addr_t, int data );
|
||||
void write_irq( nes_time_t, nes_addr_t, int data );
|
||||
|
||||
virtual void write( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
int osc = unsigned (addr - sound.base_addr) / sound.addr_step;
|
||||
|
||||
if ( (addr + 1) & 2 ) // optionally swap 1 and 2
|
||||
addr ^= swap_mask;
|
||||
|
||||
int reg = addr & 3;
|
||||
if ( (unsigned) osc < sound.osc_count && reg < sound.reg_count )
|
||||
sound.write_osc( time, osc, reg, data );
|
||||
else if ( addr < 0xf000 )
|
||||
write_bank( addr, data );
|
||||
else
|
||||
write_irq( time, addr, data );
|
||||
}
|
||||
};
|
||||
|
||||
void Mapper_Vrc6::read_state( mapper_state_t const& in )
|
||||
{
|
||||
Nes_Mapper::read_state( in );
|
||||
vrc6_state_t::swap();
|
||||
|
||||
// to do: eliminate when format is updated
|
||||
// old-style registers
|
||||
static char zero [sizeof old_sound_regs] = { 0 };
|
||||
if ( 0 != memcmp( old_sound_regs, zero, sizeof zero ) )
|
||||
{
|
||||
dprintf( "Using old VRC6 sound register format\n" );
|
||||
memcpy( sound_state.regs, old_sound_regs, sizeof sound_state.regs );
|
||||
memset( old_sound_regs, 0, sizeof old_sound_regs );
|
||||
}
|
||||
|
||||
sound.load_state( sound_state );
|
||||
}
|
||||
|
||||
void Mapper_Vrc6::write_irq( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
// IRQ
|
||||
run_until( time );
|
||||
//dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data );
|
||||
switch ( addr & 3 )
|
||||
{
|
||||
case 0:
|
||||
irq_reload = data;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
irq_pending = false;
|
||||
irq_mode = data;
|
||||
if ( data & 2 )
|
||||
reset_timer( time );
|
||||
break;
|
||||
|
||||
case 2:
|
||||
irq_pending = false;
|
||||
irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2);
|
||||
break;
|
||||
}
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
void Mapper_Vrc6::write_bank( nes_addr_t addr, int data )
|
||||
{
|
||||
switch ( addr & 0xf003 )
|
||||
{
|
||||
case 0x8000:
|
||||
prg_16k_bank = data;
|
||||
set_prg_bank( 0x8000, bank_16k, data );
|
||||
break;
|
||||
|
||||
case 0xb003: {
|
||||
mirroring = data;
|
||||
|
||||
//dprintf( "Change mirroring %d\n", data );
|
||||
// emu()->enable_sram( data & 0x80 ); // to do: needed?
|
||||
int page = data >> 5 & 1;
|
||||
if ( data & 8 )
|
||||
mirror_single( ((data >> 2) ^ page) & 1 );
|
||||
else if ( data & 4 )
|
||||
mirror_horiz( page );
|
||||
else
|
||||
mirror_vert( page );
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xc000:
|
||||
prg_8k_bank = data;
|
||||
set_prg_bank( 0xC000, bank_8k, data );
|
||||
break;
|
||||
|
||||
default:
|
||||
int bank = (addr >> 11 & 4) | (addr & 3);
|
||||
if ( addr >= 0xd000 )
|
||||
{
|
||||
//dprintf( "change chr bank %d\n", bank );
|
||||
chr_banks [bank] = data;
|
||||
set_chr_bank( bank * 0x400, bank_1k, data );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Nes_Mapper* make_vrc6a()
|
||||
{
|
||||
return BLARGG_NEW Mapper_Vrc6( 0 );
|
||||
}
|
||||
|
||||
static Nes_Mapper* make_vrc6b()
|
||||
{
|
||||
return BLARGG_NEW Mapper_Vrc6( 3 );
|
||||
}
|
||||
|
||||
void register_vrc6_mapper();
|
||||
void register_vrc6_mapper()
|
||||
{
|
||||
Nes_Mapper::register_mapper( 24, make_vrc6a );
|
||||
Nes_Mapper::register_mapper( 26, make_vrc6b );
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
|
||||
// Blip_Buffer 0.4.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Multi_Buffer.h"
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
|
||||
{
|
||||
length_ = 0;
|
||||
sample_rate_ = 0;
|
||||
channels_changed_count_ = 1;
|
||||
}
|
||||
|
||||
blargg_err_t Multi_Buffer::set_channel_count( int )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
|
||||
{
|
||||
}
|
||||
|
||||
Mono_Buffer::~Mono_Buffer()
|
||||
{
|
||||
}
|
||||
|
||||
blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
RETURN_ERR( buf.set_sample_rate( rate, msec ) );
|
||||
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
|
||||
}
|
||||
|
||||
// Silent_Buffer
|
||||
|
||||
Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse
|
||||
{
|
||||
chan.left = NULL;
|
||||
chan.center = NULL;
|
||||
chan.right = NULL;
|
||||
}
|
||||
|
||||
// Mono_Buffer
|
||||
|
||||
Mono_Buffer::channel_t Mono_Buffer::channel( int )
|
||||
{
|
||||
channel_t ch;
|
||||
ch.center = &buf;
|
||||
ch.left = &buf;
|
||||
ch.right = &buf;
|
||||
return ch;
|
||||
}
|
||||
|
||||
void Mono_Buffer::end_frame( blip_time_t t, bool )
|
||||
{
|
||||
buf.end_frame( t );
|
||||
}
|
||||
|
||||
// Stereo_Buffer
|
||||
|
||||
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
|
||||
{
|
||||
chan.center = &bufs [0];
|
||||
chan.left = &bufs [1];
|
||||
chan.right = &bufs [2];
|
||||
}
|
||||
|
||||
Stereo_Buffer::~Stereo_Buffer()
|
||||
{
|
||||
}
|
||||
|
||||
blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
|
||||
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
|
||||
}
|
||||
|
||||
void Stereo_Buffer::clock_rate( long rate )
|
||||
{
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].clock_rate( rate );
|
||||
}
|
||||
|
||||
void Stereo_Buffer::bass_freq( int bass )
|
||||
{
|
||||
for ( unsigned i = 0; i < buf_count; i++ )
|
||||
bufs [i].bass_freq( bass );
|
||||
}
|
||||
|
||||
void Stereo_Buffer::clear()
|
||||
{
|
||||
stereo_added = false;
|
||||
was_stereo = false;
|
||||
for ( int i = 0; i < buf_count; i++ )
|
||||
bufs [i].clear();
|
||||
}
|
||||
|
||||
void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo )
|
||||
{
|
||||
for ( unsigned i = 0; i < buf_count; i++ )
|
||||
bufs [i].end_frame( clock_count );
|
||||
|
||||
stereo_added |= stereo;
|
||||
}
|
||||
|
||||
long Stereo_Buffer::read_samples( blip_sample_t* out, long count )
|
||||
{
|
||||
require( !(count & 1) ); // count must be even
|
||||
count = (unsigned) count / 2;
|
||||
|
||||
long avail = bufs [0].samples_avail();
|
||||
if ( count > avail )
|
||||
count = avail;
|
||||
if ( count )
|
||||
{
|
||||
if ( stereo_added || was_stereo )
|
||||
{
|
||||
mix_stereo( out, count );
|
||||
|
||||
bufs [0].remove_samples( count );
|
||||
bufs [1].remove_samples( count );
|
||||
bufs [2].remove_samples( count );
|
||||
}
|
||||
else
|
||||
{
|
||||
mix_mono( out, count );
|
||||
|
||||
bufs [0].remove_samples( count );
|
||||
|
||||
bufs [1].remove_silence( count );
|
||||
bufs [2].remove_silence( count );
|
||||
}
|
||||
|
||||
// to do: this might miss opportunities for optimization
|
||||
if ( !bufs [0].samples_avail() ) {
|
||||
was_stereo = stereo_added;
|
||||
stereo_added = false;
|
||||
}
|
||||
}
|
||||
|
||||
return count * 2;
|
||||
}
|
||||
|
||||
void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader left;
|
||||
Blip_Reader right;
|
||||
Blip_Reader center;
|
||||
|
||||
left.begin( bufs [1] );
|
||||
right.begin( bufs [2] );
|
||||
int bass = center.begin( bufs [0] );
|
||||
|
||||
while ( count-- )
|
||||
{
|
||||
int c = center.read();
|
||||
long l = c + left.read();
|
||||
long r = c + right.read();
|
||||
center.next( bass );
|
||||
out [0] = l;
|
||||
out [1] = r;
|
||||
out += 2;
|
||||
|
||||
if ( (BOOST::int16_t) l != l )
|
||||
out [-2] = 0x7FFF - (l >> 24);
|
||||
|
||||
left.next( bass );
|
||||
right.next( bass );
|
||||
|
||||
if ( (BOOST::int16_t) r != r )
|
||||
out [-1] = 0x7FFF - (r >> 24);
|
||||
}
|
||||
|
||||
center.end( bufs [0] );
|
||||
right.end( bufs [2] );
|
||||
left.end( bufs [1] );
|
||||
}
|
||||
|
||||
void Stereo_Buffer::mix_mono( blip_sample_t* out, long count )
|
||||
{
|
||||
Blip_Reader in;
|
||||
int bass = in.begin( bufs [0] );
|
||||
|
||||
while ( count-- )
|
||||
{
|
||||
long s = in.read();
|
||||
in.next( bass );
|
||||
out [0] = s;
|
||||
out [1] = s;
|
||||
out += 2;
|
||||
|
||||
if ( (BOOST::int16_t) s != s ) {
|
||||
s = 0x7FFF - (s >> 24);
|
||||
out [-2] = s;
|
||||
out [-1] = s;
|
||||
}
|
||||
}
|
||||
|
||||
in.end( bufs [0] );
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
// Multi-channel sound buffer interface, and basic mono and stereo buffers
|
||||
|
||||
// Blip_Buffer 0.4.0
|
||||
|
||||
#ifndef MULTI_BUFFER_H
|
||||
#define MULTI_BUFFER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
// Interface to one or more Blip_Buffers mapped to one or more channels
|
||||
// consisting of left, center, and right buffers.
|
||||
class Multi_Buffer {
|
||||
public:
|
||||
Multi_Buffer( int samples_per_frame );
|
||||
virtual ~Multi_Buffer() { }
|
||||
|
||||
// Set the number of channels available
|
||||
virtual blargg_err_t set_channel_count( int );
|
||||
|
||||
// Get indexed channel, from 0 to channel count - 1
|
||||
struct channel_t {
|
||||
Blip_Buffer* center;
|
||||
Blip_Buffer* left;
|
||||
Blip_Buffer* right;
|
||||
};
|
||||
virtual channel_t channel( int index ) = 0;
|
||||
|
||||
// See Blip_Buffer.h
|
||||
virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0;
|
||||
virtual void clock_rate( long ) = 0;
|
||||
virtual void bass_freq( int ) = 0;
|
||||
virtual void clear() = 0;
|
||||
long sample_rate() const;
|
||||
|
||||
// Length of buffer, in milliseconds
|
||||
int length() const;
|
||||
|
||||
// See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo'
|
||||
// if nothing was added to the left and right buffers of any channel for
|
||||
// this time frame.
|
||||
virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0;
|
||||
|
||||
// Number of samples per output frame (1 = mono, 2 = stereo)
|
||||
int samples_per_frame() const;
|
||||
|
||||
// Count of changes to channel configuration. Incremented whenever
|
||||
// a change is made to any of the Blip_Buffers for any channel.
|
||||
unsigned channels_changed_count() { return channels_changed_count_; }
|
||||
|
||||
// See Blip_Buffer.h
|
||||
virtual long read_samples( blip_sample_t*, long ) = 0;
|
||||
virtual long samples_avail() const = 0;
|
||||
|
||||
protected:
|
||||
void channels_changed() { channels_changed_count_++; }
|
||||
private:
|
||||
// noncopyable
|
||||
Multi_Buffer( const Multi_Buffer& );
|
||||
Multi_Buffer& operator = ( const Multi_Buffer& );
|
||||
|
||||
unsigned channels_changed_count_;
|
||||
long sample_rate_;
|
||||
int length_;
|
||||
int const samples_per_frame_;
|
||||
};
|
||||
|
||||
// Uses a single buffer and outputs mono samples.
|
||||
class Mono_Buffer : public Multi_Buffer {
|
||||
Blip_Buffer buf;
|
||||
public:
|
||||
Mono_Buffer();
|
||||
~Mono_Buffer();
|
||||
|
||||
// Buffer used for all channels
|
||||
Blip_Buffer* center() { return &buf; }
|
||||
|
||||
// See Multi_Buffer
|
||||
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
|
||||
void clock_rate( long );
|
||||
void bass_freq( int );
|
||||
void clear();
|
||||
channel_t channel( int );
|
||||
void end_frame( blip_time_t, bool unused = true );
|
||||
long samples_avail() const;
|
||||
long read_samples( blip_sample_t*, long );
|
||||
};
|
||||
|
||||
// Uses three buffers (one for center) and outputs stereo sample pairs.
|
||||
class Stereo_Buffer : public Multi_Buffer {
|
||||
public:
|
||||
Stereo_Buffer();
|
||||
~Stereo_Buffer();
|
||||
|
||||
// Buffers used for all channels
|
||||
Blip_Buffer* center() { return &bufs [0]; }
|
||||
Blip_Buffer* left() { return &bufs [1]; }
|
||||
Blip_Buffer* right() { return &bufs [2]; }
|
||||
|
||||
// See Multi_Buffer
|
||||
blargg_err_t set_sample_rate( long, int msec = blip_default_length );
|
||||
void clock_rate( long );
|
||||
void bass_freq( int );
|
||||
void clear();
|
||||
channel_t channel( int index );
|
||||
void end_frame( blip_time_t, bool added_stereo = true );
|
||||
|
||||
long samples_avail() const;
|
||||
long read_samples( blip_sample_t*, long );
|
||||
|
||||
private:
|
||||
enum { buf_count = 3 };
|
||||
Blip_Buffer bufs [buf_count];
|
||||
channel_t chan;
|
||||
bool stereo_added;
|
||||
bool was_stereo;
|
||||
|
||||
void mix_stereo( blip_sample_t*, long );
|
||||
void mix_mono( blip_sample_t*, long );
|
||||
};
|
||||
|
||||
// Silent_Buffer generates no samples, useful where no sound is wanted
|
||||
class Silent_Buffer : public Multi_Buffer {
|
||||
channel_t chan;
|
||||
public:
|
||||
Silent_Buffer();
|
||||
|
||||
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
|
||||
void clock_rate( long ) { }
|
||||
void bass_freq( int ) { }
|
||||
void clear() { }
|
||||
channel_t channel( int ) { return chan; }
|
||||
void end_frame( blip_time_t, bool unused = true ) { }
|
||||
long samples_avail() const { return 0; }
|
||||
long read_samples( blip_sample_t*, long ) { return 0; }
|
||||
};
|
||||
|
||||
|
||||
// End of public interface
|
||||
|
||||
inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
sample_rate_ = rate;
|
||||
length_ = msec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
return Multi_Buffer::set_sample_rate( rate, msec );
|
||||
}
|
||||
|
||||
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
|
||||
|
||||
inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; }
|
||||
|
||||
inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; }
|
||||
|
||||
inline long Multi_Buffer::sample_rate() const { return sample_rate_; }
|
||||
|
||||
inline int Multi_Buffer::length() const { return length_; }
|
||||
|
||||
inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); }
|
||||
|
||||
inline void Mono_Buffer::clear() { buf.clear(); }
|
||||
|
||||
inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); }
|
||||
|
||||
inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); }
|
||||
|
||||
inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); }
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
|
||||
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const amp_range = 15;
|
||||
|
||||
Nes_Apu::Nes_Apu() :
|
||||
square1( &square_synth ),
|
||||
square2( &square_synth )
|
||||
{
|
||||
dmc.apu = this;
|
||||
dmc.prg_reader = NULL;
|
||||
irq_notifier_ = NULL;
|
||||
|
||||
oscs [0] = &square1;
|
||||
oscs [1] = &square2;
|
||||
oscs [2] = ▵
|
||||
oscs [3] = &noise;
|
||||
oscs [4] = &dmc;
|
||||
|
||||
output( NULL );
|
||||
volume( 1.0 );
|
||||
reset( false );
|
||||
}
|
||||
|
||||
Nes_Apu::~Nes_Apu()
|
||||
{
|
||||
}
|
||||
|
||||
void Nes_Apu::treble_eq( const blip_eq_t& eq )
|
||||
{
|
||||
square_synth.treble_eq( eq );
|
||||
triangle.synth.treble_eq( eq );
|
||||
noise.synth.treble_eq( eq );
|
||||
dmc.synth.treble_eq( eq );
|
||||
}
|
||||
|
||||
void Nes_Apu::enable_nonlinear( double v )
|
||||
{
|
||||
dmc.nonlinear = true;
|
||||
square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v );
|
||||
|
||||
const double tnd = 0.48 / 202 * nonlinear_tnd_gain();
|
||||
triangle.synth.volume( 3.0 * tnd );
|
||||
noise.synth.volume( 2.0 * tnd );
|
||||
dmc.synth.volume( tnd );
|
||||
|
||||
square1 .last_amp = 0;
|
||||
square2 .last_amp = 0;
|
||||
triangle.last_amp = 0;
|
||||
noise .last_amp = 0;
|
||||
dmc .last_amp = 0;
|
||||
}
|
||||
|
||||
void Nes_Apu::volume( double v )
|
||||
{
|
||||
dmc.nonlinear = false;
|
||||
square_synth.volume( 0.1128 / amp_range * v );
|
||||
triangle.synth.volume( 0.12765 / amp_range * v );
|
||||
noise.synth.volume( 0.0741 / amp_range * v );
|
||||
dmc.synth.volume( 0.42545 / 127 * v );
|
||||
}
|
||||
|
||||
void Nes_Apu::output( Blip_Buffer* buffer )
|
||||
{
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
osc_output( i, buffer );
|
||||
}
|
||||
|
||||
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
|
||||
{
|
||||
// to do: time pal frame periods exactly
|
||||
frame_period = pal_mode ? 8314 : 7458;
|
||||
dmc.pal_mode = pal_mode;
|
||||
|
||||
square1.reset();
|
||||
square2.reset();
|
||||
triangle.reset();
|
||||
noise.reset();
|
||||
dmc.reset();
|
||||
|
||||
last_time = 0;
|
||||
last_dmc_time = 0;
|
||||
osc_enables = 0;
|
||||
irq_flag = false;
|
||||
earliest_irq_ = no_irq;
|
||||
frame_delay = 1;
|
||||
write_register( 0, 0x4017, 0x00 );
|
||||
write_register( 0, 0x4015, 0x00 );
|
||||
|
||||
for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ )
|
||||
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
|
||||
|
||||
dmc.dac = initial_dmc_dac;
|
||||
if ( !dmc.nonlinear )
|
||||
triangle.last_amp = 15;
|
||||
//if ( !dmc.nonlinear ) // to do: remove?
|
||||
// dmc.last_amp = initial_dmc_dac; // prevent output transition
|
||||
}
|
||||
|
||||
void Nes_Apu::irq_changed()
|
||||
{
|
||||
nes_time_t new_irq = dmc.next_irq;
|
||||
if ( dmc.irq_flag | irq_flag ) {
|
||||
new_irq = 0;
|
||||
}
|
||||
else if ( new_irq > next_irq ) {
|
||||
new_irq = next_irq;
|
||||
}
|
||||
|
||||
if ( new_irq != earliest_irq_ ) {
|
||||
earliest_irq_ = new_irq;
|
||||
if ( irq_notifier_ )
|
||||
irq_notifier_( irq_data );
|
||||
}
|
||||
}
|
||||
|
||||
// frames
|
||||
|
||||
void Nes_Apu::run_until( nes_time_t end_time )
|
||||
{
|
||||
require( end_time >= last_dmc_time );
|
||||
if ( end_time > next_dmc_read_time() )
|
||||
{
|
||||
nes_time_t start = last_dmc_time;
|
||||
last_dmc_time = end_time;
|
||||
dmc.run( start, end_time );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Apu::run_until_( nes_time_t end_time )
|
||||
{
|
||||
require( end_time >= last_time );
|
||||
|
||||
if ( end_time == last_time )
|
||||
return;
|
||||
|
||||
if ( last_dmc_time < end_time )
|
||||
{
|
||||
nes_time_t start = last_dmc_time;
|
||||
last_dmc_time = end_time;
|
||||
dmc.run( start, end_time );
|
||||
}
|
||||
|
||||
while ( true )
|
||||
{
|
||||
// earlier of next frame time or end time
|
||||
nes_time_t time = last_time + frame_delay;
|
||||
if ( time > end_time )
|
||||
time = end_time;
|
||||
frame_delay -= time - last_time;
|
||||
|
||||
// run oscs to present
|
||||
square1.run( last_time, time );
|
||||
square2.run( last_time, time );
|
||||
triangle.run( last_time, time );
|
||||
noise.run( last_time, time );
|
||||
last_time = time;
|
||||
|
||||
if ( time == end_time )
|
||||
break; // no more frames to run
|
||||
|
||||
// take frame-specific actions
|
||||
frame_delay = frame_period;
|
||||
switch ( frame++ )
|
||||
{
|
||||
case 0:
|
||||
if ( !(frame_mode & 0xc0) ) {
|
||||
next_irq = time + frame_period * 4 + 1;
|
||||
irq_flag = true;
|
||||
}
|
||||
// fall through
|
||||
case 2:
|
||||
// clock length and sweep on frames 0 and 2
|
||||
square1.clock_length( 0x20 );
|
||||
square2.clock_length( 0x20 );
|
||||
noise.clock_length( 0x20 );
|
||||
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
|
||||
|
||||
square1.clock_sweep( -1 );
|
||||
square2.clock_sweep( 0 );
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// frame 1 is slightly shorter
|
||||
frame_delay -= 2;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
frame = 0;
|
||||
|
||||
// frame 3 is almost twice as long in mode 1
|
||||
if ( frame_mode & 0x80 )
|
||||
frame_delay += frame_period - 6;
|
||||
break;
|
||||
}
|
||||
|
||||
// clock envelopes and linear counter every frame
|
||||
triangle.clock_linear_counter();
|
||||
square1.clock_envelope();
|
||||
square2.clock_envelope();
|
||||
noise.clock_envelope();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void zero_apu_osc( T* osc, nes_time_t time )
|
||||
{
|
||||
Blip_Buffer* output = osc->output;
|
||||
int last_amp = osc->last_amp;
|
||||
osc->last_amp = 0;
|
||||
if ( output && last_amp )
|
||||
osc->synth.offset( time, -last_amp, output );
|
||||
}
|
||||
|
||||
void Nes_Apu::end_frame( nes_time_t end_time )
|
||||
{
|
||||
if ( end_time > last_time )
|
||||
run_until_( end_time );
|
||||
|
||||
if ( dmc.nonlinear )
|
||||
{
|
||||
zero_apu_osc( &square1, last_time );
|
||||
zero_apu_osc( &square2, last_time );
|
||||
zero_apu_osc( &triangle, last_time );
|
||||
zero_apu_osc( &noise, last_time );
|
||||
zero_apu_osc( &dmc, last_time );
|
||||
}
|
||||
|
||||
// make times relative to new frame
|
||||
last_time -= end_time;
|
||||
require( last_time >= 0 );
|
||||
|
||||
last_dmc_time -= end_time;
|
||||
require( last_dmc_time >= 0 );
|
||||
|
||||
if ( next_irq != no_irq ) {
|
||||
next_irq -= end_time;
|
||||
assert( next_irq >= 0 );
|
||||
}
|
||||
if ( dmc.next_irq != no_irq ) {
|
||||
dmc.next_irq -= end_time;
|
||||
assert( dmc.next_irq >= 0 );
|
||||
}
|
||||
if ( earliest_irq_ != no_irq ) {
|
||||
earliest_irq_ -= end_time;
|
||||
if ( earliest_irq_ < 0 )
|
||||
earliest_irq_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// registers
|
||||
|
||||
static const unsigned char length_table [0x20] = {
|
||||
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
|
||||
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
|
||||
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
|
||||
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
|
||||
};
|
||||
|
||||
void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
|
||||
require( (unsigned) data <= 0xff );
|
||||
|
||||
// Ignore addresses outside range
|
||||
if ( addr < start_addr || end_addr < addr )
|
||||
return;
|
||||
|
||||
run_until_( time );
|
||||
|
||||
if ( addr < 0x4014 )
|
||||
{
|
||||
// Write to channel
|
||||
int osc_index = (addr - start_addr) >> 2;
|
||||
Nes_Osc* osc = oscs [osc_index];
|
||||
|
||||
int reg = addr & 3;
|
||||
osc->regs [reg] = data;
|
||||
osc->reg_written [reg] = true;
|
||||
|
||||
if ( osc_index == 4 )
|
||||
{
|
||||
// handle DMC specially
|
||||
dmc.write_register( reg, data );
|
||||
}
|
||||
else if ( reg == 3 )
|
||||
{
|
||||
// load length counter
|
||||
if ( (osc_enables >> osc_index) & 1 )
|
||||
osc->length_counter = length_table [(data >> 3) & 0x1f];
|
||||
|
||||
// reset square phase
|
||||
if ( osc_index < 2 )
|
||||
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
|
||||
}
|
||||
}
|
||||
else if ( addr == 0x4015 )
|
||||
{
|
||||
// Channel enables
|
||||
for ( int i = osc_count; i--; )
|
||||
if ( !((data >> i) & 1) )
|
||||
oscs [i]->length_counter = 0;
|
||||
|
||||
bool recalc_irq = dmc.irq_flag;
|
||||
dmc.irq_flag = false;
|
||||
|
||||
int old_enables = osc_enables;
|
||||
osc_enables = data;
|
||||
if ( !(data & 0x10) ) {
|
||||
dmc.next_irq = no_irq;
|
||||
recalc_irq = true;
|
||||
}
|
||||
else if ( !(old_enables & 0x10) ) {
|
||||
dmc.start(); // dmc just enabled
|
||||
}
|
||||
|
||||
if ( recalc_irq )
|
||||
irq_changed();
|
||||
}
|
||||
else if ( addr == 0x4017 )
|
||||
{
|
||||
// Frame mode
|
||||
frame_mode = data;
|
||||
|
||||
bool irq_enabled = !(data & 0x40);
|
||||
irq_flag &= irq_enabled;
|
||||
next_irq = no_irq;
|
||||
|
||||
// mode 1
|
||||
frame_delay = (frame_delay & 1);
|
||||
frame = 0;
|
||||
|
||||
if ( !(data & 0x80) )
|
||||
{
|
||||
// mode 0
|
||||
frame = 1;
|
||||
frame_delay += frame_period;
|
||||
if ( irq_enabled )
|
||||
next_irq = time + frame_delay + frame_period * 3;
|
||||
}
|
||||
|
||||
irq_changed();
|
||||
}
|
||||
}
|
||||
|
||||
int Nes_Apu::read_status( nes_time_t time )
|
||||
{
|
||||
run_until_( time - 1 );
|
||||
|
||||
int result = (dmc.irq_flag << 7) | (irq_flag << 6);
|
||||
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
if ( oscs [i]->length_counter )
|
||||
result |= 1 << i;
|
||||
|
||||
run_until_( time );
|
||||
|
||||
if ( irq_flag ) {
|
||||
irq_flag = false;
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
// NES 2A03 APU sound chip emulator
|
||||
|
||||
// Nes_Snd_Emu 0.1.7
|
||||
|
||||
#ifndef NES_APU_H
|
||||
#define NES_APU_H
|
||||
|
||||
typedef long nes_time_t; // CPU clock cycle count
|
||||
typedef unsigned nes_addr_t; // 16-bit memory address
|
||||
|
||||
#include "Nes_Oscs.h"
|
||||
|
||||
struct apu_state_t;
|
||||
class Nes_Buffer;
|
||||
|
||||
class Nes_Apu {
|
||||
public:
|
||||
Nes_Apu();
|
||||
~Nes_Apu();
|
||||
|
||||
// Set buffer to generate all sound into, or disable sound if NULL
|
||||
void output( Blip_Buffer* );
|
||||
|
||||
// Set memory reader callback used by DMC oscillator to fetch samples.
|
||||
// When callback is invoked, 'user_data' is passed unchanged as the
|
||||
// first parameter.
|
||||
void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL );
|
||||
|
||||
// All time values are the number of CPU clock cycles relative to the
|
||||
// beginning of the current time frame. Before resetting the CPU clock
|
||||
// count, call end_frame( last_cpu_time ).
|
||||
|
||||
// Write to register (0x4000-0x4017, except 0x4014 and 0x4016)
|
||||
enum { start_addr = 0x4000 };
|
||||
enum { end_addr = 0x4017 };
|
||||
void write_register( nes_time_t, nes_addr_t, int data );
|
||||
|
||||
// Read from status register at 0x4015
|
||||
enum { status_addr = 0x4015 };
|
||||
int read_status( nes_time_t );
|
||||
|
||||
// Run all oscillators up to specified time, end current time frame, then
|
||||
// start a new time frame at time 0. Time frames have no effect on emulation
|
||||
// and each can be whatever length is convenient.
|
||||
void end_frame( nes_time_t );
|
||||
|
||||
// Additional optional features (can be ignored without any problem)
|
||||
|
||||
// Reset internal frame counter, registers, and all oscillators.
|
||||
// Use PAL timing if pal_timing is true, otherwise use NTSC timing.
|
||||
// Set the DMC oscillator's initial DAC value to initial_dmc_dac without
|
||||
// any audible click.
|
||||
void reset( bool pal_timing = false, int initial_dmc_dac = 0 );
|
||||
|
||||
// Save/load exact emulation state
|
||||
void save_state( apu_state_t* out ) const;
|
||||
void load_state( apu_state_t const& );
|
||||
|
||||
// Set overall volume (default is 1.0)
|
||||
void volume( double );
|
||||
|
||||
// Set treble equalization (see notes.txt)
|
||||
void treble_eq( const blip_eq_t& );
|
||||
|
||||
// Set sound output of specific oscillator to buffer. If buffer is NULL,
|
||||
// the specified oscillator is muted and emulation accuracy is reduced.
|
||||
// The oscillators are indexed as follows: 0) Square 1, 1) Square 2,
|
||||
// 2) Triangle, 3) Noise, 4) DMC.
|
||||
enum { osc_count = 5 };
|
||||
void osc_output( int index, Blip_Buffer* buffer );
|
||||
|
||||
// Set IRQ time callback that is invoked when the time of earliest IRQ
|
||||
// may have changed, or NULL to disable. When callback is invoked,
|
||||
// 'user_data' is passed unchanged as the first parameter.
|
||||
void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
|
||||
|
||||
// Get time that APU-generated IRQ will occur if no further register reads
|
||||
// or writes occur. If IRQ is already pending, returns irq_waiting. If no
|
||||
// IRQ will occur, returns no_irq.
|
||||
enum { no_irq = LONG_MAX / 2 + 1 };
|
||||
enum { irq_waiting = 0 };
|
||||
nes_time_t earliest_irq( nes_time_t ) const;
|
||||
|
||||
// Count number of DMC reads that would occur if 'run_until( t )' were executed.
|
||||
// If last_read is not NULL, set *last_read to the earliest time that
|
||||
// 'count_dmc_reads( time )' would result in the same result.
|
||||
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
|
||||
|
||||
// Time when next DMC memory read will occur
|
||||
nes_time_t next_dmc_read_time() const;
|
||||
|
||||
// Run DMC until specified time, so that any DMC memory reads can be
|
||||
// accounted for (i.e. inserting CPU wait states).
|
||||
void run_until( nes_time_t );
|
||||
|
||||
// End of public interface.
|
||||
private:
|
||||
friend class Nes_Nonlinearizer;
|
||||
void enable_nonlinear( double volume );
|
||||
static double nonlinear_tnd_gain() { return 0.75; }
|
||||
private:
|
||||
friend struct Nes_Dmc;
|
||||
|
||||
// noncopyable
|
||||
Nes_Apu( const Nes_Apu& );
|
||||
Nes_Apu& operator = ( const Nes_Apu& );
|
||||
|
||||
Nes_Osc* oscs [osc_count];
|
||||
Nes_Square square1;
|
||||
Nes_Square square2;
|
||||
Nes_Noise noise;
|
||||
Nes_Triangle triangle;
|
||||
Nes_Dmc dmc;
|
||||
|
||||
nes_time_t last_time; // has been run until this time in current frame
|
||||
nes_time_t last_dmc_time;
|
||||
nes_time_t earliest_irq_;
|
||||
nes_time_t next_irq;
|
||||
int frame_period;
|
||||
int frame_delay; // cycles until frame counter runs next
|
||||
int frame; // current frame (0-3)
|
||||
int osc_enables;
|
||||
int frame_mode;
|
||||
bool irq_flag;
|
||||
void (*irq_notifier_)( void* user_data );
|
||||
void* irq_data;
|
||||
Nes_Square::Synth square_synth; // shared by squares
|
||||
|
||||
void irq_changed();
|
||||
void state_restored();
|
||||
void run_until_( nes_time_t );
|
||||
|
||||
// TODO: remove
|
||||
friend class Nes_Core;
|
||||
};
|
||||
|
||||
inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) osc < osc_count );
|
||||
oscs [osc]->output = buf;
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
|
||||
{
|
||||
return earliest_irq_;
|
||||
}
|
||||
|
||||
inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data )
|
||||
{
|
||||
dmc.prg_reader_data = user_data;
|
||||
dmc.prg_reader = func;
|
||||
}
|
||||
|
||||
inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data )
|
||||
{
|
||||
irq_notifier_ = func;
|
||||
irq_data = user_data;
|
||||
}
|
||||
|
||||
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
|
||||
{
|
||||
return dmc.count_reads( time, last_read );
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Dmc::next_read_time() const
|
||||
{
|
||||
if ( length_counter == 0 )
|
||||
return Nes_Apu::no_irq; // not reading
|
||||
|
||||
return apu->last_dmc_time + delay + long (bits_remain - 1) * period;
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
|
||||
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
#include "Nes_State.h"
|
||||
|
||||
/* Copyright (C) 2003-2005 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
template<int mode>
|
||||
struct apu_reflection
|
||||
{
|
||||
#define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu))
|
||||
|
||||
static void reflect_env( apu_state_t::env_t& state, Nes_Envelope& osc )
|
||||
{
|
||||
REFLECT( state [0], osc.env_delay );
|
||||
REFLECT( state [1], osc.envelope );
|
||||
REFLECT( state [2], osc.reg_written [3] );
|
||||
}
|
||||
|
||||
static void reflect_square( apu_state_t::square_t& state, Nes_Square& osc )
|
||||
{
|
||||
reflect_env( state.env, osc );
|
||||
REFLECT( state.delay, osc.delay );
|
||||
REFLECT( state.length, osc.length_counter );
|
||||
REFLECT( state.phase, osc.phase );
|
||||
REFLECT( state.swp_delay, osc.sweep_delay );
|
||||
REFLECT( state.swp_reset, osc.reg_written [1] );
|
||||
}
|
||||
|
||||
static void reflect_triangle( apu_state_t::triangle_t& state, Nes_Triangle& osc )
|
||||
{
|
||||
REFLECT( state.delay, osc.delay );
|
||||
REFLECT( state.length, osc.length_counter );
|
||||
REFLECT( state.linear_counter, osc.linear_counter );
|
||||
REFLECT( state.linear_mode, osc.reg_written [3] );
|
||||
}
|
||||
|
||||
static void reflect_noise( apu_state_t::noise_t& state, Nes_Noise& osc )
|
||||
{
|
||||
reflect_env( state.env, osc );
|
||||
REFLECT( state.delay, osc.delay );
|
||||
REFLECT( state.length, osc.length_counter );
|
||||
REFLECT( state.shift_reg, osc.noise );
|
||||
}
|
||||
|
||||
static void reflect_dmc( apu_state_t::dmc_t& state, Nes_Dmc& osc )
|
||||
{
|
||||
REFLECT( state.delay, osc.delay );
|
||||
REFLECT( state.remain, osc.length_counter );
|
||||
REFLECT( state.buf, osc.buf );
|
||||
REFLECT( state.bits_remain, osc.bits_remain );
|
||||
REFLECT( state.bits, osc.bits );
|
||||
REFLECT( state.buf_empty, osc.buf_empty );
|
||||
REFLECT( state.silence, osc.silence );
|
||||
REFLECT( state.irq_flag, osc.irq_flag );
|
||||
if ( mode )
|
||||
state.addr = osc.address | 0x8000;
|
||||
else
|
||||
osc.address = state.addr & 0x7fff;
|
||||
}
|
||||
};
|
||||
|
||||
void Nes_Apu::get_state( apu_state_t* state ) const
|
||||
{
|
||||
for ( int i = 0; i < osc_count * 4; i++ )
|
||||
state->w40xx [i] = oscs [i >> 2]->regs [i & 3];
|
||||
state->w40xx [0x11] = dmc.dac;
|
||||
|
||||
state->w4015 = osc_enables;
|
||||
state->w4017 = frame_mode;
|
||||
state->delay = frame_delay;
|
||||
state->step = frame;
|
||||
state->irq_flag = irq_flag;
|
||||
|
||||
typedef apu_reflection<1> refl;
|
||||
Nes_Apu& apu = *(Nes_Apu*) this; // const_cast
|
||||
refl::reflect_square ( state->square1, apu.square1 );
|
||||
refl::reflect_square ( state->square2, apu.square2 );
|
||||
refl::reflect_triangle( state->triangle, apu.triangle );
|
||||
refl::reflect_noise ( state->noise, apu.noise );
|
||||
refl::reflect_dmc ( state->dmc, apu.dmc );
|
||||
}
|
||||
|
||||
void Nes_Apu::set_state( apu_state_t const& state )
|
||||
{
|
||||
reset();
|
||||
|
||||
write_register( 0, 0x4017, state.w4017 );
|
||||
write_register( 0, 0x4015, state.w4015 );
|
||||
|
||||
for ( int i = 0; i < osc_count * 4; i++ )
|
||||
{
|
||||
int n = state.w40xx [i];
|
||||
oscs [i >> 2]->regs [i & 3] = n;
|
||||
write_register( 0, 0x4000 + i, n );
|
||||
}
|
||||
|
||||
frame_delay = state.delay;
|
||||
frame = state.step;
|
||||
irq_flag = state.irq_flag;
|
||||
|
||||
typedef apu_reflection<0> refl;
|
||||
apu_state_t& st = (apu_state_t&) state; // const_cast
|
||||
refl::reflect_square ( st.square1, square1 );
|
||||
refl::reflect_square ( st.square2, square2 );
|
||||
refl::reflect_triangle( st.triangle, triangle );
|
||||
refl::reflect_noise ( st.noise, noise );
|
||||
refl::reflect_dmc ( st.dmc, dmc );
|
||||
dmc.recalc_irq();
|
||||
dmc.last_amp = dmc.dac;
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
|
||||
#include "Nes_Blitter.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifndef NES_BLITTER_OUT_DEPTH
|
||||
#define NES_BLITTER_OUT_DEPTH 16
|
||||
#endif
|
||||
|
||||
Nes_Blitter::Nes_Blitter() { ntsc = 0; }
|
||||
|
||||
Nes_Blitter::~Nes_Blitter() { free( ntsc ); }
|
||||
|
||||
blargg_err_t Nes_Blitter::init()
|
||||
{
|
||||
assert( !ntsc );
|
||||
CHECK_ALLOC( ntsc = (nes_ntsc_emph_t*) malloc( sizeof *ntsc ) );
|
||||
static setup_t const s = { };
|
||||
setup_ = s;
|
||||
setup_.ntsc = nes_ntsc_composite;
|
||||
return setup( setup_ );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Blitter::setup( setup_t const& s )
|
||||
{
|
||||
setup_ = s;
|
||||
chunk_count = ((Nes_Emu::image_width - setup_.crop.left - setup_.crop.right) + 5) / 6;
|
||||
height = Nes_Emu::image_height - setup_.crop.top - setup_.crop.bottom;
|
||||
nes_ntsc_init_emph( ntsc, &setup_.ntsc );
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Blitter::blit( Nes_Emu& emu, void* out, long out_pitch )
|
||||
{
|
||||
short const* palette = emu.frame().palette;
|
||||
int burst_phase = (setup_.ntsc.merge_fields ? 0 : emu.frame().burst_phase);
|
||||
long in_pitch = emu.frame().pitch;
|
||||
unsigned char* in = emu.frame().pixels + setup_.crop.top * in_pitch + setup_.crop.left;
|
||||
|
||||
for ( int n = height; n; --n )
|
||||
{
|
||||
unsigned char* line_in = in;
|
||||
in += in_pitch;
|
||||
|
||||
BOOST::uint32_t* line_out = (BOOST::uint32_t*) out;
|
||||
out = (char*) out + out_pitch;
|
||||
|
||||
NES_NTSC_BEGIN_ROW( ntsc, burst_phase,
|
||||
nes_ntsc_black, nes_ntsc_black, palette [*line_in] );
|
||||
|
||||
line_in [256] = 252; // loop reads 3 extra pixels, so set them to black
|
||||
line_in [257] = 252;
|
||||
line_in [258] = 252;
|
||||
line_in++;
|
||||
|
||||
burst_phase = (burst_phase + 1) % nes_ntsc_burst_count;
|
||||
|
||||
// assemble two 16-bit pixels into a 32-bit int for better performance
|
||||
#if BLARGG_BIG_ENDIAN
|
||||
#define COMBINE_PIXELS right |= left << 16;
|
||||
#else
|
||||
#define COMBINE_PIXELS right <<= 16; right |= left;
|
||||
#endif
|
||||
|
||||
#define OUT_PIXEL( i ) \
|
||||
if ( !(i & 1) ) {\
|
||||
NES_NTSC_RGB_OUT( (i % 7), left, NES_BLITTER_OUT_DEPTH );\
|
||||
if ( i > 1 ) line_out [(i/2)-1] = right;\
|
||||
}\
|
||||
else {\
|
||||
NES_NTSC_RGB_OUT( (i % 7), right, NES_BLITTER_OUT_DEPTH );\
|
||||
COMBINE_PIXELS;\
|
||||
}
|
||||
|
||||
for ( int n = chunk_count; n; --n )
|
||||
{
|
||||
unsigned long left, right;
|
||||
|
||||
NES_NTSC_COLOR_IN( 0, palette [line_in [0]] );
|
||||
OUT_PIXEL( 0 );
|
||||
OUT_PIXEL( 1 );
|
||||
|
||||
NES_NTSC_COLOR_IN( 1, palette [line_in [1]] );
|
||||
OUT_PIXEL( 2 );
|
||||
OUT_PIXEL( 3 );
|
||||
|
||||
NES_NTSC_COLOR_IN( 2, palette [line_in [2]] );
|
||||
OUT_PIXEL( 4 );
|
||||
OUT_PIXEL( 5 );
|
||||
OUT_PIXEL( 6 );
|
||||
|
||||
NES_NTSC_COLOR_IN( 0, palette [line_in [3]] );
|
||||
OUT_PIXEL( 7 );
|
||||
OUT_PIXEL( 8 );
|
||||
|
||||
NES_NTSC_COLOR_IN( 1, palette [line_in [4]] );
|
||||
OUT_PIXEL( 9 );
|
||||
OUT_PIXEL( 10);
|
||||
|
||||
NES_NTSC_COLOR_IN( 2, palette [line_in [5]] );
|
||||
line_in += 6;
|
||||
OUT_PIXEL( 11 );
|
||||
OUT_PIXEL( 12 );
|
||||
OUT_PIXEL( 13 );
|
||||
|
||||
line_out [6] = right;
|
||||
line_out += 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
// NTSC filter for use with Nes_Emu
|
||||
|
||||
#ifndef NES_BLITTER_H
|
||||
#define NES_BLITTER_H
|
||||
|
||||
#include "nes_emu/Nes_Emu.h"
|
||||
#include "nes_emu/nes_ntsc.h"
|
||||
|
||||
class Nes_Blitter {
|
||||
public:
|
||||
Nes_Blitter();
|
||||
~Nes_Blitter();
|
||||
|
||||
blargg_err_t init();
|
||||
|
||||
struct setup_t
|
||||
{
|
||||
nes_ntsc_setup_t ntsc;
|
||||
struct {
|
||||
// Number of NES source pixels to remove from each border.
|
||||
// Resulting width will be rounded to multiple of 6 pixels due
|
||||
// to internal limitations.
|
||||
int left, top, right, bottom;
|
||||
} crop;
|
||||
};
|
||||
setup_t const& setup() const { return setup_; }
|
||||
blargg_err_t setup( setup_t const& );
|
||||
|
||||
// size of output image generated by blit()
|
||||
int out_width() const { return chunk_count * 14; }
|
||||
int out_height() const { return height; }
|
||||
|
||||
// Generate NTSC filtered image to 16-bit 565 RGB output pixels
|
||||
void blit( Nes_Emu&, void* out, long pitch );
|
||||
|
||||
private:
|
||||
setup_t setup_;
|
||||
nes_ntsc_emph_t* ntsc;
|
||||
int chunk_count;
|
||||
int height;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Buffer.h"
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
/* Library Copyright (C) 2003-2006 Shay Green. This library is free software;
|
||||
you can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
// Nes_Buffer
|
||||
|
||||
Nes_Buffer::Nes_Buffer() : Multi_Buffer( 1 ) { }
|
||||
|
||||
Nes_Buffer::~Nes_Buffer() { }
|
||||
|
||||
Multi_Buffer* set_apu( Nes_Buffer* buf, Nes_Apu* apu )
|
||||
{
|
||||
buf->set_apu( apu );
|
||||
return buf;
|
||||
}
|
||||
|
||||
void Nes_Buffer::enable_nonlinearity( bool b )
|
||||
{
|
||||
if ( b )
|
||||
clear();
|
||||
|
||||
Nes_Apu* apu = nonlin.enable( b, &tnd );
|
||||
apu->osc_output( 0, &buf );
|
||||
apu->osc_output( 1, &buf );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
enable_nonlinearity( nonlin.enabled ); // reapply
|
||||
RETURN_ERR( buf.set_sample_rate( rate, msec ) );
|
||||
RETURN_ERR( tnd.set_sample_rate( rate, msec ) );
|
||||
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
|
||||
}
|
||||
|
||||
void Nes_Buffer::clock_rate( long rate )
|
||||
{
|
||||
buf.clock_rate( rate );
|
||||
tnd.clock_rate( rate );
|
||||
}
|
||||
|
||||
void Nes_Buffer::bass_freq( int freq )
|
||||
{
|
||||
buf.bass_freq( freq );
|
||||
tnd.bass_freq( freq );
|
||||
}
|
||||
|
||||
void Nes_Buffer::clear()
|
||||
{
|
||||
nonlin.clear();
|
||||
buf.clear();
|
||||
tnd.clear();
|
||||
}
|
||||
|
||||
Nes_Buffer::channel_t Nes_Buffer::channel( int i )
|
||||
{
|
||||
channel_t c;
|
||||
c.center = &buf;
|
||||
if ( 2 <= i && i <= 4 )
|
||||
c.center = &tnd; // only use for triangle, noise, and dmc
|
||||
c.left = c.center;
|
||||
c.right = c.center;
|
||||
return c;
|
||||
}
|
||||
|
||||
void Nes_Buffer::end_frame( blip_time_t length, bool )
|
||||
{
|
||||
buf.end_frame( length );
|
||||
tnd.end_frame( length );
|
||||
}
|
||||
|
||||
long Nes_Buffer::samples_avail() const
|
||||
{
|
||||
return buf.samples_avail();
|
||||
}
|
||||
|
||||
long Nes_Buffer::read_samples( blip_sample_t* out, long count )
|
||||
{
|
||||
count = nonlin.make_nonlinear( tnd, count );
|
||||
if ( count )
|
||||
{
|
||||
Blip_Reader lin;
|
||||
Blip_Reader nonlin;
|
||||
|
||||
int lin_bass = lin.begin( buf );
|
||||
int nonlin_bass = nonlin.begin( tnd );
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
int s = lin.read() + nonlin.read();
|
||||
lin.next( lin_bass );
|
||||
nonlin.next( nonlin_bass );
|
||||
*out++ = s;
|
||||
|
||||
if ( (BOOST::int16_t) s != s )
|
||||
out [-1] = 0x7FFF - (s >> 24);
|
||||
}
|
||||
|
||||
lin.end( buf );
|
||||
nonlin.end( tnd );
|
||||
|
||||
buf.remove_samples( count );
|
||||
tnd.remove_samples( count );
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Nes_Nonlinearizer
|
||||
|
||||
Nes_Nonlinearizer::Nes_Nonlinearizer()
|
||||
{
|
||||
apu = NULL;
|
||||
enabled = true;
|
||||
|
||||
float const gain = 0x7fff * 1.3f;
|
||||
// don't use entire range, so any overflow will stay within table
|
||||
int const range = (int) (table_size * Nes_Apu::nonlinear_tnd_gain());
|
||||
for ( int i = 0; i < table_size; i++ )
|
||||
{
|
||||
int const offset = table_size - range;
|
||||
int j = i - offset;
|
||||
float n = 202.0f / (range - 1) * j;
|
||||
float d = gain * 163.67f / (24329.0f / n + 100.0f);
|
||||
int out = (int) d;
|
||||
//out = j << (15 - table_bits); // make table linear for testing
|
||||
assert( out < 0x8000 );
|
||||
table [j & (table_size - 1)] = out;
|
||||
}
|
||||
}
|
||||
|
||||
Nes_Apu* Nes_Nonlinearizer::enable( bool b, Blip_Buffer* buf )
|
||||
{
|
||||
require( apu );
|
||||
apu->osc_output( 2, buf );
|
||||
apu->osc_output( 3, buf );
|
||||
apu->osc_output( 4, buf );
|
||||
enabled = b;
|
||||
if ( b )
|
||||
apu->enable_nonlinear( 1.0 );
|
||||
else
|
||||
apu->volume( 1.0 );
|
||||
return apu;
|
||||
}
|
||||
|
||||
#define ENTRY( s ) table [(s) >> (blip_sample_bits - table_bits - 1) & (table_size - 1)]
|
||||
|
||||
long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count )
|
||||
{
|
||||
require( apu );
|
||||
long avail = buf.samples_avail();
|
||||
if ( count > avail )
|
||||
count = avail;
|
||||
if ( count && enabled )
|
||||
{
|
||||
|
||||
Blip_Buffer::buf_t_* p = buf.buffer_;
|
||||
long accum = this->accum;
|
||||
long prev = this->prev;
|
||||
for ( unsigned n = count; n; --n )
|
||||
{
|
||||
long entry = ENTRY( accum );
|
||||
check( (entry >= 0) == (accum >= 0) );
|
||||
accum += *p;
|
||||
*p++ = (entry - prev) << (blip_sample_bits - 16);
|
||||
prev = entry;
|
||||
}
|
||||
|
||||
this->prev = prev;
|
||||
this->accum = accum;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::clear()
|
||||
{
|
||||
accum = 0;
|
||||
prev = ENTRY( 86016000 ); // avoid thump due to APU's triangle dc bias
|
||||
// TODO: still results in slight clicks and thumps
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
// NES non-linear audio buffer
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_BUFFER_H
|
||||
#define NES_BUFFER_H
|
||||
|
||||
#include "Multi_Buffer.h"
|
||||
class Nes_Apu;
|
||||
|
||||
class Nes_Nonlinearizer {
|
||||
private:
|
||||
enum { table_bits = 11 };
|
||||
enum { table_size = 1 << table_bits };
|
||||
BOOST::int16_t table [table_size];
|
||||
Nes_Apu* apu;
|
||||
long accum;
|
||||
long prev;
|
||||
|
||||
public:
|
||||
Nes_Nonlinearizer();
|
||||
bool enabled;
|
||||
void clear();
|
||||
void set_apu( Nes_Apu* a ) { apu = a; }
|
||||
Nes_Apu* enable( bool, Blip_Buffer* tnd );
|
||||
long make_nonlinear( Blip_Buffer& buf, long count );
|
||||
};
|
||||
|
||||
class Nes_Buffer : public Multi_Buffer {
|
||||
public:
|
||||
Nes_Buffer();
|
||||
~Nes_Buffer();
|
||||
|
||||
// Setup APU for use with buffer, including setting its output to this buffer.
|
||||
// If you're using Nes_Emu, this is automatically called for you.
|
||||
void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); }
|
||||
|
||||
// Enable/disable non-linear output
|
||||
void enable_nonlinearity( bool = true );
|
||||
|
||||
// Blip_Buffer to output other sound chips to
|
||||
Blip_Buffer* buffer() { return &buf; }
|
||||
|
||||
// See Multi_Buffer.h
|
||||
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
|
||||
|
||||
#if 0 // What is this?
|
||||
Multi_Buffer::sample_rate;
|
||||
#endif
|
||||
|
||||
void clock_rate( long );
|
||||
void bass_freq( int );
|
||||
void clear();
|
||||
channel_t channel( int );
|
||||
void end_frame( blip_time_t, bool unused = true );
|
||||
long samples_avail() const;
|
||||
long read_samples( blip_sample_t*, long );
|
||||
|
||||
private:
|
||||
Blip_Buffer buf;
|
||||
Blip_Buffer tnd;
|
||||
Nes_Nonlinearizer nonlin;
|
||||
friend Multi_Buffer* set_apu( Nes_Buffer*, Nes_Apu* );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Cart.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
char const Nes_Cart::not_ines_file [] = "Not an iNES file";
|
||||
|
||||
Nes_Cart::Nes_Cart()
|
||||
{
|
||||
prg_ = NULL;
|
||||
chr_ = NULL;
|
||||
clear();
|
||||
}
|
||||
|
||||
Nes_Cart::~Nes_Cart()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void Nes_Cart::clear()
|
||||
{
|
||||
free( prg_ );
|
||||
prg_ = NULL;
|
||||
|
||||
free( chr_ );
|
||||
chr_ = NULL;
|
||||
|
||||
prg_size_ = 0;
|
||||
chr_size_ = 0;
|
||||
mapper = 0;
|
||||
}
|
||||
|
||||
long Nes_Cart::round_to_bank_size( long n )
|
||||
{
|
||||
n += bank_size - 1;
|
||||
return n - n % bank_size;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Cart::resize_prg( long size )
|
||||
{
|
||||
if ( size != prg_size_ )
|
||||
{
|
||||
// padding allows CPU to always read operands of instruction, which
|
||||
// might go past end of data
|
||||
void* p = realloc( prg_, round_to_bank_size( size ) + 2 );
|
||||
CHECK_ALLOC( p || !size );
|
||||
prg_ = (byte*) p;
|
||||
prg_size_ = size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Cart::resize_chr( long size )
|
||||
{
|
||||
if ( size != chr_size_ )
|
||||
{
|
||||
void* p = realloc( chr_, round_to_bank_size( size ) );
|
||||
CHECK_ALLOC( p || !size );
|
||||
chr_ = (byte*) p;
|
||||
chr_size_ = size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// iNES reading
|
||||
|
||||
struct ines_header_t {
|
||||
BOOST::uint8_t signature [4];
|
||||
BOOST::uint8_t prg_count; // number of 16K PRG banks
|
||||
BOOST::uint8_t chr_count; // number of 8K CHR banks
|
||||
BOOST::uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror
|
||||
BOOST::uint8_t flags2; // MMMM --XX Mapper high 4 bits
|
||||
BOOST::uint8_t zero [8]; // if zero [7] is non-zero, treat flags2 as zero
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (ines_header_t) == 16 );
|
||||
|
||||
blargg_err_t Nes_Cart::load_ines( Auto_File_Reader in )
|
||||
{
|
||||
RETURN_ERR( in.open() );
|
||||
|
||||
ines_header_t h;
|
||||
RETURN_ERR( in->read( &h, sizeof h ) );
|
||||
|
||||
if ( 0 != memcmp( h.signature, "NES\x1A", 4 ) )
|
||||
return not_ines_file;
|
||||
|
||||
if ( h.zero [7] ) // handle header defaced by a fucking idiot's handle
|
||||
h.flags2 = 0;
|
||||
|
||||
set_mapper( h.flags, h.flags2 );
|
||||
|
||||
if ( h.flags & 0x04 ) // skip trainer
|
||||
RETURN_ERR( in->skip( 512 ) );
|
||||
|
||||
RETURN_ERR( resize_prg( h.prg_count * 16 * 1024L ) );
|
||||
RETURN_ERR( resize_chr( h.chr_count * 8 * 1024L ) );
|
||||
|
||||
RETURN_ERR( in->read( prg(), prg_size() ) );
|
||||
RETURN_ERR( in->read( chr(), chr_size() ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// IPS patching
|
||||
|
||||
// IPS patch file format (integers are big-endian):
|
||||
// 5 "PATCH"
|
||||
// n blocks
|
||||
//
|
||||
// normal block:
|
||||
// 3 offset
|
||||
// 2 size
|
||||
// n data
|
||||
//
|
||||
// repeated byte block:
|
||||
// 3 offset
|
||||
// 2 0
|
||||
// 2 size
|
||||
// 1 fill value
|
||||
//
|
||||
// end block (optional):
|
||||
// 3 "EOF"
|
||||
//
|
||||
// A block can append data to the file by specifying an offset at the end of
|
||||
// the current file data.
|
||||
|
||||
typedef BOOST::uint8_t byte;
|
||||
static blargg_err_t apply_ips_patch( Data_Reader& patch, byte** file, long* file_size )
|
||||
{
|
||||
byte signature [5];
|
||||
RETURN_ERR( patch.read( signature, sizeof signature ) );
|
||||
if ( memcmp( signature, "PATCH", sizeof signature ) )
|
||||
return "Not an IPS patch file";
|
||||
|
||||
while ( patch.remain() )
|
||||
{
|
||||
// read offset
|
||||
byte buf [6];
|
||||
RETURN_ERR( patch.read( buf, 3 ) );
|
||||
long offset = buf [0] * 0x10000 + buf [1] * 0x100 + buf [2];
|
||||
if ( offset == 0x454F46 ) // 'EOF'
|
||||
break;
|
||||
|
||||
// read size
|
||||
RETURN_ERR( patch.read( buf, 2 ) );
|
||||
long size = buf [0] * 0x100 + buf [1];
|
||||
|
||||
// size = 0 signals a run of identical bytes
|
||||
int fill = -1;
|
||||
if ( size == 0 )
|
||||
{
|
||||
RETURN_ERR( patch.read( buf, 3 ) );
|
||||
size = buf [0] * 0x100 + buf [1];
|
||||
fill = buf [2];
|
||||
}
|
||||
|
||||
// expand file if new data is at exact end of file
|
||||
if ( offset == *file_size )
|
||||
{
|
||||
*file_size = offset + size;
|
||||
void* p = realloc( *file, *file_size );
|
||||
CHECK_ALLOC( p );
|
||||
*file = (byte*) p;
|
||||
}
|
||||
|
||||
//dprintf( "Patch offset: 0x%04X, size: 0x%04X\n", (int) offset, (int) size );
|
||||
|
||||
if ( offset < 0 || *file_size < offset + size )
|
||||
return "IPS tried to patch past end of file";
|
||||
|
||||
// read/fill data
|
||||
if ( fill < 0 )
|
||||
RETURN_ERR( patch.read( *file + offset, size ) );
|
||||
else
|
||||
memset( *file + offset, fill, size );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Cart::load_patched_ines( Auto_File_Reader in, Auto_File_Reader patch )
|
||||
{
|
||||
RETURN_ERR( in.open() );
|
||||
RETURN_ERR( patch.open() );
|
||||
|
||||
// read file into memory
|
||||
long size = in->remain();
|
||||
byte* ines = (byte*) malloc( size );
|
||||
CHECK_ALLOC( ines );
|
||||
const char* err = in->read( ines, size );
|
||||
|
||||
// apply patch
|
||||
if ( !err )
|
||||
err = apply_ips_patch( *patch, &ines, &size );
|
||||
|
||||
// load patched file
|
||||
if ( !err )
|
||||
{
|
||||
Mem_File_Reader patched( ines, size );
|
||||
err = load_ines( patched );
|
||||
}
|
||||
|
||||
free( ines );
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Cart::apply_ips_to_prg( Auto_File_Reader patch )
|
||||
{
|
||||
RETURN_ERR( patch.open() );
|
||||
|
||||
long size = prg_size();
|
||||
|
||||
byte* prg_copy = (byte*) malloc( size );
|
||||
CHECK_ALLOC( prg_copy );
|
||||
memcpy( prg_copy, prg(), size );
|
||||
|
||||
const char* err = apply_ips_patch( *patch, &prg_copy, &size );
|
||||
|
||||
if ( !err )
|
||||
{
|
||||
resize_prg( size );
|
||||
memcpy( prg(), prg_copy, size );
|
||||
}
|
||||
|
||||
free( prg_copy );
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Cart::apply_ips_to_chr( Auto_File_Reader patch )
|
||||
{
|
||||
RETURN_ERR( patch.open() );
|
||||
|
||||
long size = chr_size();
|
||||
|
||||
byte* chr_copy = (byte*) malloc( size );
|
||||
CHECK_ALLOC( chr_copy );
|
||||
memcpy( chr_copy, chr(), size );
|
||||
|
||||
const char* err = apply_ips_patch( *patch, &chr_copy, &size );
|
||||
|
||||
if ( !err )
|
||||
{
|
||||
resize_chr( size );
|
||||
memcpy( chr(), chr_copy, size );
|
||||
}
|
||||
|
||||
free( chr_copy );
|
||||
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
// NES cartridge data (PRG, CHR, mapper)
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_CART_H
|
||||
#define NES_CART_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "abstract_file.h"
|
||||
|
||||
class Nes_Cart {
|
||||
typedef BOOST::uint8_t byte;
|
||||
public:
|
||||
Nes_Cart();
|
||||
~Nes_Cart();
|
||||
|
||||
// Load iNES file
|
||||
blargg_err_t load_ines( Auto_File_Reader );
|
||||
static const char not_ines_file [];
|
||||
|
||||
// Load iNES file and apply IPS patch
|
||||
blargg_err_t load_patched_ines( Auto_File_Reader, Auto_File_Reader ips_patch );
|
||||
|
||||
// Apply IPS patches to specific parts
|
||||
blargg_err_t apply_ips_to_prg( Auto_File_Reader ips_patch );
|
||||
blargg_err_t apply_ips_to_chr( Auto_File_Reader ips_patch );
|
||||
|
||||
// to do: support UNIF?
|
||||
|
||||
// True if data is currently loaded
|
||||
bool loaded() const { return prg_ != NULL; }
|
||||
|
||||
// Free data
|
||||
void clear();
|
||||
|
||||
// True if cartridge claims to have battery-backed memory
|
||||
bool has_battery_ram() const;
|
||||
|
||||
// Size of PRG data
|
||||
long prg_size() const { return prg_size_; }
|
||||
|
||||
// Size of CHR data
|
||||
long chr_size() const { return chr_size_; }
|
||||
|
||||
// Change size of PRG (code) data
|
||||
blargg_err_t resize_prg( long );
|
||||
|
||||
// Change size of CHR (graphics) data
|
||||
blargg_err_t resize_chr( long );
|
||||
|
||||
// Set mapper and information bytes. LSB and MSB are the standard iNES header
|
||||
// bytes at offsets 6 and 7.
|
||||
void set_mapper( int mapper_lsb, int mapper_msb );
|
||||
|
||||
unsigned mapper_data() const { return mapper; }
|
||||
|
||||
// Initial mirroring setup
|
||||
int mirroring() const { return mapper & 0x09; }
|
||||
|
||||
// iNES mapper code
|
||||
int mapper_code() const;
|
||||
|
||||
// Pointer to beginning of PRG data
|
||||
byte * prg() { return prg_; }
|
||||
byte const* prg() const { return prg_; }
|
||||
|
||||
// Pointer to beginning of CHR data
|
||||
byte * chr() { return chr_; }
|
||||
byte const* chr() const { return chr_; }
|
||||
|
||||
// End of public interface
|
||||
private:
|
||||
enum { bank_size = 8 * 1024L }; // bank sizes must be a multiple of this
|
||||
byte* prg_;
|
||||
byte* chr_;
|
||||
long prg_size_;
|
||||
long chr_size_;
|
||||
unsigned mapper;
|
||||
long round_to_bank_size( long n );
|
||||
};
|
||||
|
||||
inline bool Nes_Cart::has_battery_ram() const { return mapper & 0x02; }
|
||||
|
||||
inline void Nes_Cart::set_mapper( int mapper_lsb, int mapper_msb )
|
||||
{
|
||||
mapper = mapper_msb * 0x100 + mapper_lsb;
|
||||
}
|
||||
|
||||
inline int Nes_Cart::mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); }
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,570 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Core.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_Mapper.h"
|
||||
#include "Nes_State.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
extern const char unsupported_mapper [] = "Unsupported mapper";
|
||||
|
||||
bool const wait_states_enabled = true;
|
||||
bool const single_instruction_mode = false; // for debugging irq/nmi timing issues
|
||||
|
||||
const int unmapped_fill = Nes_Cpu::page_wrap_opcode;
|
||||
|
||||
unsigned const low_ram_size = 0x800;
|
||||
unsigned const low_ram_end = 0x2000;
|
||||
unsigned const sram_end = 0x8000;
|
||||
|
||||
Nes_Core::Nes_Core() : ppu( this )
|
||||
{
|
||||
cart = NULL;
|
||||
impl = NULL;
|
||||
mapper = NULL;
|
||||
memset( &nes, 0, sizeof nes );
|
||||
memset( &joypad, 0, sizeof joypad );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Core::init()
|
||||
{
|
||||
if ( !impl )
|
||||
{
|
||||
CHECK_ALLOC( impl = BLARGG_NEW impl_t );
|
||||
impl->apu.dmc_reader( read_dmc, this );
|
||||
impl->apu.irq_notifier( apu_irq_changed, this );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Core::close()
|
||||
{
|
||||
// check that nothing modified unmapped page
|
||||
#ifndef NDEBUG
|
||||
//if ( cart && mem_differs( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ) )
|
||||
// dprintf( "Unmapped code page was written to\n" );
|
||||
#endif
|
||||
|
||||
cart = NULL;
|
||||
delete mapper;
|
||||
mapper = NULL;
|
||||
|
||||
ppu.close_chr();
|
||||
|
||||
disable_rendering();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Core::open( Nes_Cart const* new_cart )
|
||||
{
|
||||
close();
|
||||
|
||||
RETURN_ERR( init() );
|
||||
|
||||
mapper = Nes_Mapper::create( new_cart, this );
|
||||
if ( !mapper )
|
||||
return unsupported_mapper;
|
||||
|
||||
RETURN_ERR( ppu.open_chr( new_cart->chr(), new_cart->chr_size() ) );
|
||||
|
||||
cart = new_cart;
|
||||
memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page );
|
||||
reset( true, true );
|
||||
return 0;
|
||||
}
|
||||
|
||||
Nes_Core::~Nes_Core()
|
||||
{
|
||||
close();
|
||||
delete impl;
|
||||
}
|
||||
|
||||
void Nes_Core::save_state( Nes_State_* out ) const
|
||||
{
|
||||
out->clear();
|
||||
|
||||
out->nes = nes;
|
||||
out->nes_valid = true;
|
||||
|
||||
*out->cpu = cpu::r;
|
||||
out->cpu_valid = true;
|
||||
|
||||
*out->joypad = joypad;
|
||||
out->joypad_valid = true;
|
||||
|
||||
impl->apu.save_state( out->apu );
|
||||
out->apu_valid = true;
|
||||
|
||||
ppu.save_state( out );
|
||||
|
||||
memcpy( out->ram, cpu::low_mem, out->ram_size );
|
||||
out->ram_valid = true;
|
||||
|
||||
out->sram_size = 0;
|
||||
if ( sram_present )
|
||||
{
|
||||
out->sram_size = sizeof impl->sram;
|
||||
memcpy( out->sram, impl->sram, out->sram_size );
|
||||
}
|
||||
|
||||
out->mapper->size = 0;
|
||||
mapper->save_state( *out->mapper );
|
||||
out->mapper_valid = true;
|
||||
}
|
||||
|
||||
void Nes_Core::save_state( Nes_State* out ) const
|
||||
{
|
||||
save_state( reinterpret_cast<Nes_State_*>(out) );
|
||||
}
|
||||
|
||||
void Nes_Core::load_state( Nes_State_ const& in )
|
||||
{
|
||||
require( cart );
|
||||
|
||||
disable_rendering();
|
||||
error_count = 0;
|
||||
|
||||
if ( in.nes_valid )
|
||||
nes = in.nes;
|
||||
|
||||
// always use frame count
|
||||
ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over
|
||||
nes.frame_count = in.nes.frame_count;
|
||||
if ( (frame_count_t) nes.frame_count == invalid_frame_count )
|
||||
nes.frame_count = 0;
|
||||
|
||||
if ( in.cpu_valid )
|
||||
cpu::r = *in.cpu;
|
||||
|
||||
if ( in.joypad_valid )
|
||||
joypad = *in.joypad;
|
||||
|
||||
if ( in.apu_valid )
|
||||
{
|
||||
impl->apu.load_state( *in.apu );
|
||||
// prevent apu from running extra at beginning of frame
|
||||
impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock );
|
||||
}
|
||||
else
|
||||
{
|
||||
impl->apu.reset();
|
||||
}
|
||||
|
||||
ppu.load_state( in );
|
||||
|
||||
if ( in.ram_valid )
|
||||
memcpy( cpu::low_mem, in.ram, in.ram_size );
|
||||
|
||||
sram_present = false;
|
||||
if ( in.sram_size )
|
||||
{
|
||||
sram_present = true;
|
||||
memcpy( impl->sram, in.sram, min( (int) in.sram_size, (int) sizeof impl->sram ) );
|
||||
enable_sram( true ); // mapper can override (read-only, unmapped, etc.)
|
||||
}
|
||||
|
||||
if ( in.mapper_valid ) // restore last since it might reconfigure things
|
||||
mapper->load_state( *in.mapper );
|
||||
}
|
||||
|
||||
void Nes_Core::enable_prg_6000()
|
||||
{
|
||||
sram_writable = 0;
|
||||
sram_readable = 0;
|
||||
lrom_readable = 0x8000;
|
||||
}
|
||||
|
||||
void Nes_Core::enable_sram( bool b, bool read_only )
|
||||
{
|
||||
sram_writable = 0;
|
||||
if ( b )
|
||||
{
|
||||
if ( !sram_present )
|
||||
{
|
||||
sram_present = true;
|
||||
memset( impl->sram, 0xFF, impl->sram_size );
|
||||
}
|
||||
sram_readable = sram_end;
|
||||
if ( !read_only )
|
||||
sram_writable = sram_end;
|
||||
cpu::map_code( 0x6000, impl->sram_size, impl->sram );
|
||||
}
|
||||
else
|
||||
{
|
||||
sram_readable = 0;
|
||||
for ( int i = 0; i < impl->sram_size; i += cpu::page_size )
|
||||
cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page );
|
||||
}
|
||||
}
|
||||
|
||||
// Unmapped memory
|
||||
|
||||
static nes_addr_t last_unmapped_addr;
|
||||
|
||||
void Nes_Core::log_unmapped( nes_addr_t addr, int data )
|
||||
{
|
||||
#if !defined (NDEBUG) && 0
|
||||
if ( last_unmapped_addr != addr )
|
||||
{
|
||||
last_unmapped_addr = addr;
|
||||
if ( data < 0 )
|
||||
dprintf( "Read unmapped %04X\n", addr );
|
||||
else
|
||||
dprintf( "Write unmapped %04X <- %02X\n", addr, data );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Nes_Core::cpu_adjust_time( int n )
|
||||
{
|
||||
ppu_2002_time -= n;
|
||||
cpu_time_offset += n;
|
||||
cpu::reduce_limit( n );
|
||||
}
|
||||
|
||||
// I/O and sound
|
||||
|
||||
int Nes_Core::read_dmc( void* data, nes_addr_t addr )
|
||||
{
|
||||
Nes_Core* emu = (Nes_Core*) data;
|
||||
int result = *emu->cpu::get_code( addr );
|
||||
if ( wait_states_enabled )
|
||||
emu->cpu_adjust_time( 4 );
|
||||
return result;
|
||||
}
|
||||
|
||||
void Nes_Core::apu_irq_changed( void* emu )
|
||||
{
|
||||
((Nes_Core*) emu)->irq_changed();
|
||||
}
|
||||
|
||||
void Nes_Core::write_io( nes_addr_t addr, int data )
|
||||
{
|
||||
// sprite dma
|
||||
if ( addr == 0x4014 )
|
||||
{
|
||||
ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) );
|
||||
cpu_adjust_time( 513 );
|
||||
return;
|
||||
}
|
||||
|
||||
// joypad strobe
|
||||
if ( addr == 0x4016 )
|
||||
{
|
||||
// if strobe goes low, latch data
|
||||
if ( joypad.w4016 & 1 & ~data )
|
||||
{
|
||||
joypad_read_count++;
|
||||
joypad.joypad_latches [0] = current_joypad [0];
|
||||
joypad.joypad_latches [1] = current_joypad [1];
|
||||
}
|
||||
joypad.w4016 = data;
|
||||
return;
|
||||
}
|
||||
|
||||
// apu
|
||||
if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr )
|
||||
{
|
||||
impl->apu.write_register( clock(), addr, data );
|
||||
if ( wait_states_enabled )
|
||||
{
|
||||
if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) )
|
||||
{
|
||||
impl->apu.run_until( clock() + 1 );
|
||||
event_changed();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
log_unmapped( addr, data );
|
||||
#endif
|
||||
}
|
||||
|
||||
int Nes_Core::read_io( nes_addr_t addr )
|
||||
{
|
||||
if ( (addr & 0xFFFE) == 0x4016 )
|
||||
{
|
||||
// to do: to aid with recording, doesn't emulate transparent latch,
|
||||
// so a game that held strobe at 1 and read $4016 or $4017 would not get
|
||||
// the current A status as occurs on a NES
|
||||
unsigned long result = joypad.joypad_latches [addr & 1];
|
||||
if ( !(joypad.w4016 & 1) )
|
||||
joypad.joypad_latches [addr & 1] = (result >> 1) | 0x80000000;
|
||||
return result & 1;
|
||||
}
|
||||
|
||||
if ( addr == Nes_Apu::status_addr )
|
||||
return impl->apu.read_status( clock() );
|
||||
|
||||
#ifndef NDEBUG
|
||||
log_unmapped( addr );
|
||||
#endif
|
||||
|
||||
return addr >> 8; // simulate open bus
|
||||
}
|
||||
|
||||
// CPU
|
||||
|
||||
const int irq_inhibit_mask = 0x04;
|
||||
|
||||
nes_addr_t Nes_Core::read_vector( nes_addr_t addr )
|
||||
{
|
||||
byte const* p = cpu::get_code( addr );
|
||||
return p [1] * 0x100 + p [0];
|
||||
}
|
||||
|
||||
void Nes_Core::reset( bool full_reset, bool erase_battery_ram )
|
||||
{
|
||||
require( cart );
|
||||
|
||||
if ( full_reset )
|
||||
{
|
||||
cpu::reset( impl->unmapped_page );
|
||||
cpu_time_offset = -1;
|
||||
clock_ = 0;
|
||||
|
||||
// Low RAM
|
||||
memset( cpu::low_mem, 0xFF, low_ram_size );
|
||||
cpu::low_mem [8] = 0xf7;
|
||||
cpu::low_mem [9] = 0xef;
|
||||
cpu::low_mem [10] = 0xdf;
|
||||
cpu::low_mem [15] = 0xbf;
|
||||
|
||||
// SRAM
|
||||
lrom_readable = 0;
|
||||
sram_present = false;
|
||||
enable_sram( false );
|
||||
if ( !cart->has_battery_ram() || erase_battery_ram )
|
||||
memset( impl->sram, 0xFF, impl->sram_size );
|
||||
|
||||
joypad.joypad_latches [0] = 0;
|
||||
joypad.joypad_latches [1] = 0;
|
||||
|
||||
nes.frame_count = 0;
|
||||
}
|
||||
|
||||
// to do: emulate partial reset
|
||||
|
||||
ppu.reset( full_reset );
|
||||
impl->apu.reset();
|
||||
|
||||
mapper->reset();
|
||||
|
||||
cpu::r.pc = read_vector( 0xFFFC );
|
||||
cpu::r.sp = 0xfd;
|
||||
cpu::r.a = 0;
|
||||
cpu::r.x = 0;
|
||||
cpu::r.y = 0;
|
||||
cpu::r.status = irq_inhibit_mask;
|
||||
nes.timestamp = 0;
|
||||
error_count = 0;
|
||||
}
|
||||
|
||||
void Nes_Core::vector_interrupt( nes_addr_t vector )
|
||||
{
|
||||
cpu::push_byte( cpu::r.pc >> 8 );
|
||||
cpu::push_byte( cpu::r.pc & 0xFF );
|
||||
cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set
|
||||
|
||||
cpu_adjust_time( 7 );
|
||||
cpu::r.status |= irq_inhibit_mask;
|
||||
cpu::r.pc = read_vector( vector );
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Core::earliest_irq( nes_time_t present )
|
||||
{
|
||||
return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) );
|
||||
}
|
||||
|
||||
void Nes_Core::irq_changed()
|
||||
{
|
||||
cpu_set_irq_time( earliest_irq( cpu_time() ) );
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Core::ppu_frame_length( nes_time_t present )
|
||||
{
|
||||
nes_time_t t = ppu.frame_length();
|
||||
if ( t > present )
|
||||
return t;
|
||||
|
||||
ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present?
|
||||
return ppu.frame_length();
|
||||
}
|
||||
|
||||
inline nes_time_t Nes_Core::earliest_event( nes_time_t present )
|
||||
{
|
||||
// PPU frame
|
||||
nes_time_t t = ppu_frame_length( present );
|
||||
|
||||
// DMC
|
||||
if ( wait_states_enabled )
|
||||
t = min( t, impl->apu.next_dmc_read_time() + 1 );
|
||||
|
||||
// NMI
|
||||
t = min( t, ppu.nmi_time() );
|
||||
|
||||
if ( single_instruction_mode )
|
||||
t = min( t, present + 1 );
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void Nes_Core::event_changed()
|
||||
{
|
||||
cpu_set_end_time( earliest_event( cpu_time() ) );
|
||||
}
|
||||
|
||||
#undef NES_EMU_CPU_HOOK
|
||||
#ifndef NES_EMU_CPU_HOOK
|
||||
#define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time )
|
||||
#endif
|
||||
|
||||
nes_time_t Nes_Core::emulate_frame_()
|
||||
{
|
||||
Nes_Cpu::result_t last_result = cpu::result_cycles;
|
||||
int extra_instructions = 0;
|
||||
while ( true )
|
||||
{
|
||||
// Add DMC wait-states to CPU time
|
||||
if ( wait_states_enabled )
|
||||
{
|
||||
impl->apu.run_until( cpu_time() );
|
||||
clock_ = cpu_time_offset;
|
||||
}
|
||||
|
||||
nes_time_t present = cpu_time();
|
||||
if ( present >= ppu_frame_length( present ) )
|
||||
{
|
||||
if ( ppu.nmi_time() <= present )
|
||||
{
|
||||
// NMI will occur next, so delayed CLI and SEI don't need to be handled.
|
||||
// If NMI will occur normally ($2000.7 and $2002.7 set), let it occur
|
||||
// next frame, otherwise vector it now.
|
||||
|
||||
if ( !(ppu.w2000 & 0x80 & ppu.r2002) )
|
||||
{
|
||||
dprintf( "vectored NMI at end of frame\n" );
|
||||
vector_interrupt( 0xFFFA );
|
||||
present += 7;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( extra_instructions > 2 )
|
||||
{
|
||||
check( last_result != cpu::result_sei && last_result != cpu::result_cli );
|
||||
check( ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002) );
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( last_result != cpu::result_cli && last_result != cpu::result_sei &&
|
||||
(ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) )
|
||||
return present;
|
||||
|
||||
dprintf( "Executing extra instructions for frame\n" );
|
||||
extra_instructions++; // execute one more instruction
|
||||
}
|
||||
|
||||
// NMI
|
||||
if ( present >= ppu.nmi_time() )
|
||||
{
|
||||
ppu.acknowledge_nmi();
|
||||
vector_interrupt( 0xFFFA );
|
||||
last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now
|
||||
}
|
||||
|
||||
// IRQ
|
||||
nes_time_t irq_time = earliest_irq( present );
|
||||
cpu_set_irq_time( irq_time );
|
||||
if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) ||
|
||||
last_result == cpu::result_sei) )
|
||||
{
|
||||
if ( last_result != cpu::result_cli )
|
||||
{
|
||||
//dprintf( "%6d IRQ vectored\n", present );
|
||||
mapper->run_until( present );
|
||||
vector_interrupt( 0xFFFE );
|
||||
}
|
||||
else
|
||||
{
|
||||
// CLI delays IRQ
|
||||
cpu_set_irq_time( present + 1 );
|
||||
check( false ); // rare event
|
||||
}
|
||||
}
|
||||
|
||||
// CPU
|
||||
nes_time_t end_time = earliest_event( present );
|
||||
if ( extra_instructions )
|
||||
end_time = present + 1;
|
||||
unsigned long cpu_error_count = cpu::error_count();
|
||||
last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 );
|
||||
cpu_adjust_time( cpu::time() );
|
||||
clock_ = cpu_time_offset;
|
||||
error_count += cpu::error_count() - cpu_error_count;
|
||||
}
|
||||
}
|
||||
|
||||
nes_time_t Nes_Core::emulate_frame()
|
||||
{
|
||||
require( cart );
|
||||
|
||||
joypad_read_count = 0;
|
||||
|
||||
cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1;
|
||||
ppu_2002_time = 0;
|
||||
clock_ = cpu_time_offset;
|
||||
|
||||
check( cpu_time() == (int) nes.timestamp / ppu_overclock );
|
||||
check( 1 && impl->apu.last_time == cpu_time() );
|
||||
|
||||
// TODO: clean this fucking mess up
|
||||
impl->apu.run_until_( emulate_frame_() );
|
||||
clock_ = cpu_time_offset;
|
||||
impl->apu.run_until_( cpu_time() );
|
||||
check( 2 && clock_ == cpu_time_offset );
|
||||
check( 3 && impl->apu.last_time == cpu_time() );
|
||||
|
||||
nes_time_t ppu_frame_length = ppu.frame_length();
|
||||
nes_time_t length = cpu_time();
|
||||
nes.timestamp = ppu.end_frame( length );
|
||||
mapper->end_frame( length );
|
||||
impl->apu.end_frame( ppu_frame_length );
|
||||
check( 4 && cpu_time() == length );
|
||||
|
||||
check( 5 && impl->apu.last_time == length - ppu_frame_length );
|
||||
|
||||
disable_rendering();
|
||||
nes.frame_count++;
|
||||
|
||||
return ppu_frame_length;
|
||||
}
|
||||
|
||||
void Nes_Core::add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write )
|
||||
{
|
||||
require( addr >= 0x4000 );
|
||||
require( addr + size <= 0x10000 );
|
||||
int end = (addr + size + (page_size - 1)) >> page_bits;
|
||||
for ( int page = addr >> page_bits; page < end; page++ )
|
||||
{
|
||||
data_reader_mapped [page] |= read;
|
||||
data_writer_mapped [page] |= write;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
// Internal NES emulator
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_CORE_H
|
||||
#define NES_CORE_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Nes_Apu.h"
|
||||
#include "Nes_Cpu.h"
|
||||
#include "Nes_Ppu.h"
|
||||
class Nes_Mapper;
|
||||
class Nes_Cart;
|
||||
class Nes_State;
|
||||
|
||||
class Nes_Core : private Nes_Cpu {
|
||||
typedef Nes_Cpu cpu;
|
||||
public:
|
||||
Nes_Core();
|
||||
~Nes_Core();
|
||||
|
||||
blargg_err_t init();
|
||||
blargg_err_t open( Nes_Cart const* );
|
||||
void reset( bool full_reset = true, bool erase_battery_ram = false );
|
||||
blip_time_t emulate_frame();
|
||||
void close();
|
||||
|
||||
void save_state( Nes_State* ) const;
|
||||
void save_state( Nes_State_* ) const;
|
||||
void load_state( Nes_State_ const& );
|
||||
|
||||
void irq_changed();
|
||||
void event_changed();
|
||||
|
||||
public: private: friend class Nes_Emu;
|
||||
|
||||
struct impl_t
|
||||
{
|
||||
enum { sram_size = 0x2000 };
|
||||
BOOST::uint8_t sram [sram_size];
|
||||
Nes_Apu apu;
|
||||
|
||||
// extra byte allows CPU to always read operand of instruction, which
|
||||
// might go past end of data
|
||||
BOOST::uint8_t unmapped_page [::Nes_Cpu::page_size + 1];
|
||||
};
|
||||
impl_t* impl; // keep large arrays separate
|
||||
unsigned long error_count;
|
||||
bool sram_present;
|
||||
|
||||
public:
|
||||
unsigned long current_joypad [2];
|
||||
int joypad_read_count;
|
||||
Nes_Cart const* cart;
|
||||
Nes_Mapper* mapper;
|
||||
nes_state_t nes;
|
||||
Nes_Ppu ppu;
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Core( const Nes_Core& );
|
||||
Nes_Core& operator = ( const Nes_Core& );
|
||||
|
||||
// Timing
|
||||
nes_time_t ppu_2002_time;
|
||||
void disable_rendering() { clock_ = 0; }
|
||||
nes_time_t earliest_irq( nes_time_t present );
|
||||
nes_time_t ppu_frame_length( nes_time_t present );
|
||||
nes_time_t earliest_event( nes_time_t present );
|
||||
|
||||
// APU and Joypad
|
||||
joypad_state_t joypad;
|
||||
int read_io( nes_addr_t );
|
||||
void write_io( nes_addr_t, int data );
|
||||
static int read_dmc( void* emu, nes_addr_t );
|
||||
static void apu_irq_changed( void* emu );
|
||||
|
||||
// CPU
|
||||
unsigned sram_readable;
|
||||
unsigned sram_writable;
|
||||
unsigned lrom_readable;
|
||||
nes_time_t clock_;
|
||||
nes_time_t cpu_time_offset;
|
||||
nes_time_t emulate_frame_();
|
||||
nes_addr_t read_vector( nes_addr_t );
|
||||
void vector_interrupt( nes_addr_t );
|
||||
static void log_unmapped( nes_addr_t addr, int data = -1 );
|
||||
void cpu_set_irq_time( nes_time_t t ) { cpu::set_irq_time_( t - 1 - cpu_time_offset ); }
|
||||
void cpu_set_end_time( nes_time_t t ) { cpu::set_end_time_( t - 1 - cpu_time_offset ); }
|
||||
nes_time_t cpu_time() const { return clock_ + 1; }
|
||||
void cpu_adjust_time( int offset );
|
||||
|
||||
public: private: friend class Nes_Ppu;
|
||||
void set_ppu_2002_time( nes_time_t t ) { ppu_2002_time = t - 1 - cpu_time_offset; }
|
||||
|
||||
public: private: friend class Nes_Mapper;
|
||||
void enable_prg_6000();
|
||||
void enable_sram( bool enabled, bool read_only = false );
|
||||
nes_time_t clock() const { return clock_; }
|
||||
void add_mapper_intercept( nes_addr_t start, unsigned size, bool read, bool write );
|
||||
|
||||
public: private: friend class Nes_Cpu;
|
||||
int cpu_read_ppu( nes_addr_t, nes_time_t );
|
||||
int cpu_read( nes_addr_t, nes_time_t );
|
||||
void cpu_write( nes_addr_t, int data, nes_time_t );
|
||||
void cpu_write_2007( int data );
|
||||
|
||||
private:
|
||||
unsigned char data_reader_mapped [page_count + 1]; // extra entry for overflow
|
||||
unsigned char data_writer_mapped [page_count + 1];
|
||||
};
|
||||
|
||||
int mem_differs( void const* p, int cmp, unsigned long s );
|
||||
|
||||
#endif
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,128 @@
|
|||
|
||||
// NES 6502 CPU emulator
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_CPU_H
|
||||
#define NES_CPU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
typedef long nes_time_t; // clock cycle count
|
||||
typedef unsigned nes_addr_t; // 16-bit address
|
||||
|
||||
class Nes_Cpu {
|
||||
public:
|
||||
typedef BOOST::uint8_t uint8_t;
|
||||
|
||||
// Clear registers, unmap memory, and map code pages to unmapped_page.
|
||||
void reset( void const* unmapped_page = 0 );
|
||||
|
||||
// Map code memory (memory accessed via the program counter). Start and size
|
||||
// must be multiple of page_size.
|
||||
enum { page_bits = 11 };
|
||||
enum { page_count = 0x10000 >> page_bits };
|
||||
enum { page_size = 1L << page_bits };
|
||||
void map_code( nes_addr_t start, unsigned size, void const* code );
|
||||
|
||||
// Access memory as the emulated CPU does.
|
||||
int read( nes_addr_t );
|
||||
void write( nes_addr_t, int data );
|
||||
uint8_t* get_code( nes_addr_t ); // non-const to allow debugger to modify code
|
||||
|
||||
// Push a byte on the stack
|
||||
void push_byte( int );
|
||||
|
||||
// NES 6502 registers. *Not* kept updated during a call to run().
|
||||
struct registers_t {
|
||||
long pc; // more than 16 bits to allow overflow detection
|
||||
BOOST::uint8_t a;
|
||||
BOOST::uint8_t x;
|
||||
BOOST::uint8_t y;
|
||||
BOOST::uint8_t status;
|
||||
BOOST::uint8_t sp;
|
||||
};
|
||||
//registers_t r;
|
||||
|
||||
// Reasons that run() returns
|
||||
enum result_t {
|
||||
result_cycles, // Requested number of cycles (or more) were executed
|
||||
result_sei, // I flag just set and IRQ time would generate IRQ now
|
||||
result_cli, // I flag just cleared but IRQ should occur *after* next instr
|
||||
result_badop // unimplemented/illegal instruction
|
||||
};
|
||||
|
||||
result_t run( nes_time_t end_time );
|
||||
|
||||
nes_time_t time() const { return clock_count; }
|
||||
void reduce_limit( int offset );
|
||||
void set_end_time_( nes_time_t t );
|
||||
void set_irq_time_( nes_time_t t );
|
||||
unsigned long error_count() const { return error_count_; }
|
||||
|
||||
// If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped.
|
||||
enum { page_wrap_opcode = 0xF2 };
|
||||
|
||||
// One of the many opcodes that are undefined and stop CPU emulation.
|
||||
enum { bad_opcode = 0xD2 };
|
||||
|
||||
private:
|
||||
uint8_t const* code_map [page_count + 1];
|
||||
nes_time_t clock_limit;
|
||||
nes_time_t clock_count;
|
||||
nes_time_t irq_time_;
|
||||
nes_time_t end_time_;
|
||||
unsigned long error_count_;
|
||||
|
||||
enum { irq_inhibit = 0x04 };
|
||||
void set_code_page( int, uint8_t const* );
|
||||
void update_clock_limit();
|
||||
|
||||
public:
|
||||
registers_t r;
|
||||
|
||||
// low_mem is a full page size so it can be mapped with code_map
|
||||
uint8_t low_mem [page_size > 0x800 ? page_size : 0x800];
|
||||
};
|
||||
|
||||
inline BOOST::uint8_t* Nes_Cpu::get_code( nes_addr_t addr )
|
||||
{
|
||||
return (uint8_t*) code_map [addr >> page_bits] + addr;
|
||||
}
|
||||
|
||||
inline void Nes_Cpu::update_clock_limit()
|
||||
{
|
||||
nes_time_t t = end_time_;
|
||||
if ( t > irq_time_ && !(r.status & irq_inhibit) )
|
||||
t = irq_time_;
|
||||
clock_limit = t;
|
||||
}
|
||||
|
||||
inline void Nes_Cpu::set_end_time_( nes_time_t t )
|
||||
{
|
||||
end_time_ = t;
|
||||
update_clock_limit();
|
||||
}
|
||||
|
||||
inline void Nes_Cpu::set_irq_time_( nes_time_t t )
|
||||
{
|
||||
irq_time_ = t;
|
||||
update_clock_limit();
|
||||
}
|
||||
|
||||
inline void Nes_Cpu::reduce_limit( int offset )
|
||||
{
|
||||
clock_limit -= offset;
|
||||
end_time_ -= offset;
|
||||
irq_time_ -= offset;
|
||||
}
|
||||
|
||||
inline void Nes_Cpu::push_byte( int data )
|
||||
{
|
||||
int sp = r.sp;
|
||||
r.sp = (sp - 1) & 0xFF;
|
||||
low_mem [0x100 + sp] = data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Effects_Buffer.h"
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Nes_Effects_Buffer::Nes_Effects_Buffer() :
|
||||
Effects_Buffer( true ) // nes never uses stereo channels
|
||||
{
|
||||
config_t c;
|
||||
c.effects_enabled = false;
|
||||
config( c );
|
||||
}
|
||||
|
||||
Nes_Effects_Buffer::~Nes_Effects_Buffer() { }
|
||||
|
||||
Multi_Buffer* set_apu( Nes_Effects_Buffer* buf, Nes_Apu* apu )
|
||||
{
|
||||
buf->set_apu( apu );
|
||||
return buf;
|
||||
}
|
||||
|
||||
void Nes_Effects_Buffer::enable_nonlinearity( bool b )
|
||||
{
|
||||
if ( b )
|
||||
clear();
|
||||
Nes_Apu* apu = nonlin.enable( b, channel( 2 ).center );
|
||||
apu->osc_output( 0, channel( 0 ).center );
|
||||
apu->osc_output( 1, channel( 1 ).center );
|
||||
}
|
||||
|
||||
void Nes_Effects_Buffer::config( const config_t& in )
|
||||
{
|
||||
config_t c = in;
|
||||
if ( !c.effects_enabled )
|
||||
{
|
||||
// effects must always be enabled to keep separate buffers, so
|
||||
// set parameters to be equivalent to disabled
|
||||
c.pan_1 = 0;
|
||||
c.pan_2 = 0;
|
||||
c.echo_level = 0;
|
||||
c.reverb_level = 0;
|
||||
c.effects_enabled = true;
|
||||
}
|
||||
Effects_Buffer::config( c );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Effects_Buffer::set_sample_rate( long rate, int msec )
|
||||
{
|
||||
enable_nonlinearity( nonlin.enabled ); // reapply
|
||||
return Effects_Buffer::set_sample_rate( rate, msec );
|
||||
}
|
||||
|
||||
void Nes_Effects_Buffer::clear()
|
||||
{
|
||||
nonlin.clear();
|
||||
Effects_Buffer::clear();
|
||||
}
|
||||
|
||||
Nes_Effects_Buffer::channel_t Nes_Effects_Buffer::channel( int i )
|
||||
{
|
||||
return Effects_Buffer::channel( (2 <= i && i <= 4) ? 2 : i & 1 );
|
||||
}
|
||||
|
||||
long Nes_Effects_Buffer::read_samples( blip_sample_t* out, long count )
|
||||
{
|
||||
count = 2 * nonlin.make_nonlinear( *channel( 2 ).center, count / 2 );
|
||||
return Effects_Buffer::read_samples( out, count );
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
// Effects_Buffer with non-linear sound
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_EFFECTS_BUFFER_H
|
||||
#define NES_EFFECTS_BUFFER_H
|
||||
|
||||
#include "Nes_Buffer.h"
|
||||
#include "Effects_Buffer.h"
|
||||
|
||||
// Effects_Buffer uses several buffers and outputs stereo sample pairs.
|
||||
class Nes_Effects_Buffer : public Effects_Buffer {
|
||||
public:
|
||||
Nes_Effects_Buffer();
|
||||
~Nes_Effects_Buffer();
|
||||
|
||||
// Setup APU for use with buffer, including setting its output to this buffer.
|
||||
// If you're using Nes_Emu, this is automatically called for you.
|
||||
void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); }
|
||||
|
||||
// Enable/disable non-linear output
|
||||
void enable_nonlinearity( bool = true );
|
||||
|
||||
// See Effects_Buffer.h for reference
|
||||
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
|
||||
void config( const config_t& );
|
||||
void clear();
|
||||
channel_t channel( int );
|
||||
long read_samples( blip_sample_t*, long );
|
||||
|
||||
private:
|
||||
Nes_Nonlinearizer nonlin;
|
||||
friend Multi_Buffer* set_apu( Nes_Effects_Buffer*, Nes_Apu* );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,494 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Emu.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_State.h"
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const sound_fade_size = 384;
|
||||
|
||||
// Constants are manually duplicated in Nes_Emu so their value can be seen
|
||||
// directly, rather than having to look in Nes_Ppu.h. "0 +" converts to int.
|
||||
BOOST_STATIC_ASSERT( Nes_Emu::image_width == 0 + Nes_Ppu::image_width );
|
||||
BOOST_STATIC_ASSERT( Nes_Emu::image_height == 0 + Nes_Ppu::image_height );
|
||||
|
||||
Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 };
|
||||
Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 };
|
||||
Nes_Emu::equalizer_t const Nes_Emu::tv_eq = { -12.0, 180 };
|
||||
|
||||
Nes_Emu::Nes_Emu()
|
||||
{
|
||||
frame_ = &single_frame;
|
||||
buffer_height_ = Nes_Ppu::buffer_height + 2;
|
||||
default_sound_buf = NULL;
|
||||
sound_buf = &silent_buffer;
|
||||
sound_buf_changed_count = 0;
|
||||
equalizer_ = nes_eq;
|
||||
channel_count_ = 0;
|
||||
sound_enabled = false;
|
||||
host_pixels = NULL;
|
||||
single_frame.pixels = 0;
|
||||
single_frame.top = 0;
|
||||
init_called = false;
|
||||
set_palette_range( 0 );
|
||||
memset( single_frame.palette, 0, sizeof single_frame.palette );
|
||||
}
|
||||
|
||||
Nes_Emu::~Nes_Emu()
|
||||
{
|
||||
delete default_sound_buf;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::init_()
|
||||
{
|
||||
return emu.init();
|
||||
}
|
||||
|
||||
inline blargg_err_t Nes_Emu::auto_init()
|
||||
{
|
||||
if ( !init_called )
|
||||
{
|
||||
RETURN_ERR( init_() );
|
||||
init_called = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void Nes_Emu::clear_sound_buf()
|
||||
{
|
||||
fade_sound_out = false;
|
||||
fade_sound_in = true;
|
||||
sound_buf->clear();
|
||||
}
|
||||
|
||||
// Emulation
|
||||
|
||||
void Nes_Emu::close()
|
||||
{
|
||||
if ( cart() )
|
||||
{
|
||||
emu.close();
|
||||
private_cart.clear();
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::set_cart( Nes_Cart const* new_cart )
|
||||
{
|
||||
close();
|
||||
RETURN_ERR( auto_init() );
|
||||
RETURN_ERR( emu.open( new_cart ) );
|
||||
|
||||
channel_count_ = Nes_Apu::osc_count + emu.mapper->channel_count();
|
||||
RETURN_ERR( sound_buf->set_channel_count( channel_count() ) );
|
||||
set_equalizer( equalizer_ );
|
||||
enable_sound( true );
|
||||
|
||||
reset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Emu::reset( bool full_reset, bool erase_battery_ram )
|
||||
{
|
||||
require( cart() );
|
||||
|
||||
clear_sound_buf();
|
||||
set_timestamp( 0 );
|
||||
emu.reset( full_reset, erase_battery_ram );
|
||||
}
|
||||
|
||||
void Nes_Emu::set_palette_range( int begin, int end )
|
||||
{
|
||||
require( (unsigned) end <= 0x100 );
|
||||
// round up to alignment
|
||||
emu.ppu.palette_begin = (begin + palette_alignment - 1) & ~(palette_alignment - 1);
|
||||
host_palette_size = end - emu.ppu.palette_begin;
|
||||
require( host_palette_size >= palette_alignment );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::emulate_frame( int joypad1, int joypad2 )
|
||||
{
|
||||
emu.current_joypad [0] = (joypad1 |= ~0xFF);
|
||||
emu.current_joypad [1] = (joypad2 |= ~0xFF);
|
||||
|
||||
emu.ppu.host_pixels = NULL;
|
||||
|
||||
unsigned changed_count = sound_buf->channels_changed_count();
|
||||
bool new_enabled = (frame_ != NULL);
|
||||
if ( sound_buf_changed_count != changed_count || sound_enabled != new_enabled )
|
||||
{
|
||||
sound_buf_changed_count = changed_count;
|
||||
sound_enabled = new_enabled;
|
||||
enable_sound( sound_enabled );
|
||||
}
|
||||
|
||||
frame_t* f = frame_;
|
||||
if ( f )
|
||||
{
|
||||
emu.ppu.max_palette_size = host_palette_size;
|
||||
emu.ppu.host_palette = f->palette + emu.ppu.palette_begin;
|
||||
// add black and white for emulator to use (unless emulator uses entire
|
||||
// palette for frame)
|
||||
f->palette [252] = 0x0F;
|
||||
f->palette [254] = 0x30;
|
||||
f->palette [255] = 0x0F;
|
||||
if ( host_pixels )
|
||||
emu.ppu.host_pixels = (BOOST::uint8_t*) host_pixels +
|
||||
emu.ppu.host_row_bytes * f->top;
|
||||
|
||||
if ( sound_buf->samples_avail() )
|
||||
clear_sound_buf();
|
||||
|
||||
nes_time_t frame_len = emu.emulate_frame();
|
||||
sound_buf->end_frame( frame_len, false );
|
||||
|
||||
f = frame_;
|
||||
f->sample_count = sound_buf->samples_avail();
|
||||
f->chan_count = sound_buf->samples_per_frame();
|
||||
f->palette_begin = emu.ppu.palette_begin;
|
||||
f->palette_size = emu.ppu.palette_size;
|
||||
f->joypad_read_count = emu.joypad_read_count;
|
||||
f->burst_phase = emu.ppu.burst_phase;
|
||||
f->pitch = emu.ppu.host_row_bytes;
|
||||
f->pixels = emu.ppu.host_pixels + f->left;
|
||||
}
|
||||
else
|
||||
{
|
||||
emu.ppu.max_palette_size = 0;
|
||||
emu.emulate_frame();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Extras
|
||||
|
||||
blargg_err_t Nes_Emu::load_ines( Auto_File_Reader in )
|
||||
{
|
||||
close();
|
||||
RETURN_ERR( private_cart.load_ines( in ) );
|
||||
return set_cart( &private_cart );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::save_battery_ram( Auto_File_Writer out )
|
||||
{
|
||||
RETURN_ERR( out.open() );
|
||||
return out->write( emu.impl->sram, emu.impl->sram_size );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::load_battery_ram( Auto_File_Reader in )
|
||||
{
|
||||
RETURN_ERR( in.open() );
|
||||
emu.sram_present = true;
|
||||
return in->read( emu.impl->sram, emu.impl->sram_size );
|
||||
}
|
||||
|
||||
void Nes_Emu::load_state( Nes_State_ const& in )
|
||||
{
|
||||
clear_sound_buf();
|
||||
emu.load_state( in );
|
||||
}
|
||||
|
||||
void Nes_Emu::load_state( Nes_State const& in )
|
||||
{
|
||||
loading_state( in );
|
||||
load_state( STATIC_CAST(Nes_State_ const&,in) );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::load_state( Auto_File_Reader in )
|
||||
{
|
||||
Nes_State* state = BLARGG_NEW Nes_State;
|
||||
CHECK_ALLOC( state );
|
||||
blargg_err_t err = state->read( in );
|
||||
if ( !err )
|
||||
load_state( *state );
|
||||
delete state;
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::save_state( Auto_File_Writer out ) const
|
||||
{
|
||||
Nes_State* state = BLARGG_NEW Nes_State;
|
||||
CHECK_ALLOC( state );
|
||||
save_state( state );
|
||||
blargg_err_t err = state->write( out );
|
||||
delete state;
|
||||
return err;
|
||||
}
|
||||
|
||||
void Nes_Emu::write_chr( void const* p, long count, long offset )
|
||||
{
|
||||
require( (unsigned long) offset <= (unsigned long) chr_size() );
|
||||
long end = offset + count;
|
||||
require( (unsigned long) end <= (unsigned long) chr_size() );
|
||||
memcpy( (byte*) chr_mem() + offset, p, count );
|
||||
emu.ppu.rebuild_chr( offset, end );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::set_sample_rate( long rate, class Nes_Buffer* buf )
|
||||
{
|
||||
extern Multi_Buffer* set_apu( class Nes_Buffer*, Nes_Apu* );
|
||||
RETURN_ERR( auto_init() );
|
||||
return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::set_sample_rate( long rate, class Nes_Effects_Buffer* buf )
|
||||
{
|
||||
extern Multi_Buffer* set_apu( class Nes_Effects_Buffer*, Nes_Apu* );
|
||||
RETURN_ERR( auto_init() );
|
||||
return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) );
|
||||
}
|
||||
|
||||
// Sound
|
||||
|
||||
void Nes_Emu::set_frame_rate( double rate )
|
||||
{
|
||||
sound_buf->clock_rate( (long) (1789773 / 60.0 * rate) );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::set_sample_rate( long rate, Multi_Buffer* new_buf )
|
||||
{
|
||||
require( new_buf );
|
||||
RETURN_ERR( auto_init() );
|
||||
emu.impl->apu.volume( 1.0 ); // cancel any previous non-linearity
|
||||
RETURN_ERR( new_buf->set_sample_rate( rate, 1200 / frame_rate ) );
|
||||
sound_buf = new_buf;
|
||||
sound_buf_changed_count = 0;
|
||||
if ( new_buf != default_sound_buf )
|
||||
{
|
||||
delete default_sound_buf;
|
||||
default_sound_buf = NULL;
|
||||
}
|
||||
set_frame_rate( frame_rate );
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Emu::set_sample_rate( long rate )
|
||||
{
|
||||
if ( !default_sound_buf )
|
||||
CHECK_ALLOC( default_sound_buf = BLARGG_NEW Mono_Buffer );
|
||||
return set_sample_rate( rate, default_sound_buf );
|
||||
}
|
||||
|
||||
void Nes_Emu::set_equalizer( equalizer_t const& eq )
|
||||
{
|
||||
equalizer_ = eq;
|
||||
if ( cart() )
|
||||
{
|
||||
blip_eq_t blip_eq( eq.treble, 0, sound_buf->sample_rate() );
|
||||
emu.impl->apu.treble_eq( blip_eq );
|
||||
emu.mapper->set_treble( blip_eq );
|
||||
sound_buf->bass_freq( equalizer_.bass );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Emu::enable_sound( bool enabled )
|
||||
{
|
||||
if ( enabled )
|
||||
{
|
||||
for ( int i = channel_count(); i-- > 0; )
|
||||
{
|
||||
Blip_Buffer* buf = sound_buf->channel( i ).center;
|
||||
int mapper_index = i - Nes_Apu::osc_count;
|
||||
if ( mapper_index < 0 )
|
||||
emu.impl->apu.osc_output( i, buf );
|
||||
else
|
||||
emu.mapper->set_channel_buf( mapper_index, buf );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
emu.impl->apu.output( NULL );
|
||||
for ( int i = channel_count() - Nes_Apu::osc_count; i-- > 0; )
|
||||
emu.mapper->set_channel_buf( i, NULL );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Emu::fade_samples( blip_sample_t* p, int size, int step )
|
||||
{
|
||||
if ( size >= sound_fade_size )
|
||||
{
|
||||
if ( step < 0 )
|
||||
p += size - sound_fade_size;
|
||||
|
||||
int const shift = 15;
|
||||
int mul = (1 - step) << (shift - 1);
|
||||
step *= (1 << shift) / sound_fade_size;
|
||||
|
||||
for ( int n = sound_fade_size; n--; )
|
||||
{
|
||||
*p = (*p * mul) >> 15;
|
||||
++p;
|
||||
mul += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long Nes_Emu::read_samples( short* out, long out_size )
|
||||
{
|
||||
require( out_size >= sound_buf->samples_avail() );
|
||||
long count = sound_buf->read_samples( out, out_size );
|
||||
if ( fade_sound_in )
|
||||
{
|
||||
fade_sound_in = false;
|
||||
fade_samples( out, count, 1 );
|
||||
}
|
||||
|
||||
if ( fade_sound_out )
|
||||
{
|
||||
fade_sound_out = false;
|
||||
fade_sound_in = true; // next buffer should be faded in
|
||||
fade_samples( out, count, -1 );
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Nes_Emu::rgb_t const Nes_Emu::nes_colors [color_table_size] =
|
||||
{
|
||||
// generated with nes_ntsc default settings
|
||||
{102,102,102},{ 0, 42,136},{ 20, 18,168},{ 59, 0,164},
|
||||
{ 92, 0,126},{110, 0, 64},{108, 7, 0},{ 87, 29, 0},
|
||||
{ 52, 53, 0},{ 12, 73, 0},{ 0, 82, 0},{ 0, 79, 8},
|
||||
{ 0, 64, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{174,174,174},{ 21, 95,218},{ 66, 64,254},{118, 39,255},
|
||||
{161, 27,205},{184, 30,124},{181, 50, 32},{153, 79, 0},
|
||||
{108,110, 0},{ 56,135, 0},{ 13,148, 0},{ 0,144, 50},
|
||||
{ 0,124,142},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{254,254,254},{100,176,254},{147,144,254},{199,119,254},
|
||||
{243,106,254},{254,110,205},{254,130,112},{235,159, 35},
|
||||
{189,191, 0},{137,217, 0},{ 93,229, 48},{ 69,225,130},
|
||||
{ 72,206,223},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{254,254,254},{193,224,254},{212,211,254},{233,200,254},
|
||||
{251,195,254},{254,197,235},{254,205,198},{247,217,166},
|
||||
{229,230,149},{208,240,151},{190,245,171},{180,243,205},
|
||||
{181,236,243},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
|
||||
{114, 83, 79},{ 0, 23,113},{ 32, 0,145},{ 71, 0,141},
|
||||
{104, 0,103},{122, 0, 41},{120, 0, 0},{ 99, 10, 0},
|
||||
{ 64, 34, 0},{ 24, 54, 0},{ 0, 63, 0},{ 0, 60, 0},
|
||||
{ 0, 45, 54},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{190,148,143},{ 37, 69,187},{ 83, 38,228},{134, 13,224},
|
||||
{177, 1,174},{200, 4, 92},{198, 24, 1},{170, 53, 0},
|
||||
{124, 84, 0},{ 73,109, 0},{ 30,122, 0},{ 6,118, 19},
|
||||
{ 9, 98,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{254,222,215},{122,142,254},{168,110,254},{220, 85,254},
|
||||
{254, 72,247},{254, 76,164},{254, 96, 71},{254,125, 0},
|
||||
{210,157, 0},{158,183, 0},{114,195, 7},{ 90,191, 89},
|
||||
{ 93,172,182},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{254,222,215},{214,190,233},{233,177,250},{254,166,248},
|
||||
{254,161,228},{254,163,194},{254,171,157},{254,183,125},
|
||||
{250,196,108},{229,206,110},{211,211,130},{201,210,164},
|
||||
{203,202,202},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 75,106, 64},{ 0, 46, 98},{ 0, 22,130},{ 32, 3,126},
|
||||
{ 65, 0, 88},{ 82, 0, 26},{ 80, 11, 0},{ 59, 34, 0},
|
||||
{ 24, 58, 0},{ 0, 77, 0},{ 0, 86, 0},{ 0, 83, 0},
|
||||
{ 0, 68, 39},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{136,180,122},{ 0,101,166},{ 29, 69,208},{ 80, 44,203},
|
||||
{123, 32,153},{146, 36, 72},{144, 55, 0},{116, 84, 0},
|
||||
{ 70,116, 0},{ 19,141, 0},{ 0,153, 0},{ 0,149, 0},
|
||||
{ 0,130, 90},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{207,254,188},{ 51,183,233},{ 98,151,254},{150,126,254},
|
||||
{193,113,220},{217,117,137},{214,137, 45},{186,166, 0},
|
||||
{140,198, 0},{ 88,224, 0},{ 44,236, 0},{ 20,232, 63},
|
||||
{ 23,213,155},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{207,254,188},{144,231,207},{163,218,224},{184,207,222},
|
||||
{201,202,201},{211,204,168},{210,212,130},{198,224, 99},
|
||||
{180,237, 81},{159,247, 83},{141,252,104},{131,251,137},
|
||||
{132,243,175},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 83, 83, 55},{ 0, 23, 89},{ 0, 0,121},{ 40, 0,117},
|
||||
{ 73, 0, 79},{ 90, 0, 17},{ 88, 0, 0},{ 67, 10, 0},
|
||||
{ 32, 34, 0},{ 0, 53, 0},{ 0, 63, 0},{ 0, 60, 0},
|
||||
{ 0, 45, 30},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{147,148,110},{ 0, 69,154},{ 40, 38,196},{ 91, 12,191},
|
||||
{134, 0,141},{157, 4, 60},{155, 23, 0},{127, 52, 0},
|
||||
{ 81, 84, 0},{ 30,109, 0},{ 0,121, 0},{ 0,117, 0},
|
||||
{ 0, 98, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{221,222,173},{ 65,142,217},{112,110,254},{164, 84,255},
|
||||
{208, 72,204},{231, 76,122},{229, 95, 29},{200,125, 0},
|
||||
{154,157, 0},{102,182, 0},{ 58,195, 0},{ 34,191, 47},
|
||||
{ 37,171,140},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{221,222,173},{158,189,191},{177,176,208},{198,166,206},
|
||||
{216,161,185},{225,163,152},{224,171,114},{213,183, 83},
|
||||
{194,195, 66},{173,206, 68},{155,211, 88},{145,209,122},
|
||||
{146,201,159},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 87, 87,133},{ 0, 26,167},{ 5, 2,198},{ 44, 0,195},
|
||||
{ 77, 0,157},{ 95, 0, 94},{ 93, 0, 25},{ 71, 14, 0},
|
||||
{ 36, 38, 0},{ 0, 57, 0},{ 0, 66, 0},{ 0, 63, 38},
|
||||
{ 0, 49,108},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{153,153,216},{ 0, 74,254},{ 46, 43,254},{ 97, 17,254},
|
||||
{140, 5,247},{164, 9,165},{161, 28, 74},{133, 57, 0},
|
||||
{ 87, 89, 0},{ 36,114, 0},{ 0,126, 10},{ 0,122, 92},
|
||||
{ 0,103,183},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{229,228,254},{ 74,148,254},{120,116,254},{172, 91,254},
|
||||
{216, 78,254},{239, 82,254},{237,102,166},{208,131, 89},
|
||||
{162,163, 46},{110,189, 51},{ 66,201,102},{ 42,197,184},
|
||||
{ 45,178,254},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{229,228,254},{166,196,254},{185,183,254},{206,172,254},
|
||||
{224,167,254},{233,169,254},{232,177,252},{221,189,220},
|
||||
{202,202,203},{181,212,205},{163,217,226},{153,216,254},
|
||||
{154,208,254},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 90, 71, 97},{ 0, 11,130},{ 8, 0,162},{ 47, 0,158},
|
||||
{ 80, 0,120},{ 98, 0, 58},{ 96, 0, 0},{ 74, 0, 0},
|
||||
{ 39, 22, 0},{ 0, 42, 0},{ 0, 51, 0},{ 0, 48, 2},
|
||||
{ 0, 33, 72},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{158,132,166},{ 4, 53,210},{ 50, 22,252},{101, 0,247},
|
||||
{144, 0,197},{168, 0,116},{165, 7, 25},{137, 36, 0},
|
||||
{ 91, 68, 0},{ 40, 93, 0},{ 0,105, 0},{ 0,101, 42},
|
||||
{ 0, 82,134},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{234,201,246},{ 79,121,254},{125, 89,254},{177, 63,254},
|
||||
{221, 51,254},{245, 55,195},{242, 74,102},{214,104, 24},
|
||||
{167,136, 0},{115,161, 0},{ 71,174, 37},{ 48,170,120},
|
||||
{ 50,150,213},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{234,201,246},{171,168,254},{190,155,254},{211,145,254},
|
||||
{229,140,254},{239,142,225},{237,150,187},{226,162,156},
|
||||
{207,174,139},{186,185,141},{168,190,161},{159,188,195},
|
||||
{160,180,232},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 66, 85, 88},{ 0, 25,121},{ 0, 1,153},{ 23, 0,149},
|
||||
{ 56, 0,111},{ 74, 0, 49},{ 72, 0, 0},{ 51, 12, 0},
|
||||
{ 16, 36, 0},{ 0, 55, 0},{ 0, 65, 0},{ 0, 62, 0},
|
||||
{ 0, 47, 63},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{125,151,154},{ 0, 72,198},{ 17, 40,240},{ 69, 15,235},
|
||||
{112, 3,185},{135, 7,104},{132, 26, 12},{104, 55, 0},
|
||||
{ 59, 87, 0},{ 7,112, 0},{ 0,124, 0},{ 0,120, 30},
|
||||
{ 0,101,121},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{192,225,230},{ 37,145,254},{ 83,114,254},{135, 88,254},
|
||||
{179, 76,254},{202, 80,179},{200, 99, 86},{171,129, 8},
|
||||
{125,160, 0},{ 73,186, 0},{ 29,198, 21},{ 5,194,104},
|
||||
{ 8,175,197},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{192,225,230},{129,193,248},{148,180,254},{169,170,254},
|
||||
{187,165,242},{196,166,209},{195,174,171},{184,186,140},
|
||||
{165,199,123},{144,209,125},{126,214,145},{116,213,179},
|
||||
{118,205,216},{184,184,184},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{ 69, 69, 69},{ 0, 16,110},{ 0, 0,142},{ 33, 0,138},
|
||||
{ 66, 0,100},{ 84, 0, 38},{ 82, 0, 0},{ 60, 3, 0},
|
||||
{ 25, 27, 0},{ 0, 46, 0},{ 0, 56, 0},{ 0, 53, 0},
|
||||
{ 0, 38, 51},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{134,134,134},{ 0, 64,187},{ 35, 32,228},{ 86, 7,223},
|
||||
{129, 0,174},{153, 0, 92},{150, 18, 1},{122, 47, 0},
|
||||
{ 76, 79, 0},{ 25,104, 0},{ 0,116, 0},{ 0,112, 19},
|
||||
{ 0, 93,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{207,207,207},{ 60,136,254},{107,104,254},{159, 79,254},
|
||||
{203, 66,248},{226, 70,165},{224, 90, 72},{195,119, 0},
|
||||
{149,151, 0},{ 97,177, 0},{ 53,189, 8},{ 29,185, 91},
|
||||
{ 32,166,183},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0},
|
||||
{207,207,207},{148,178,229},{166,165,246},{188,155,244},
|
||||
{205,150,224},{215,152,190},{214,159,152},{202,171,121},
|
||||
{183,184,104},{162,195,106},{145,200,126},{135,198,160},
|
||||
{136,190,197},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}
|
||||
};
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
|
||||
// NES video game console emulator with snapshot support
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_EMU_H
|
||||
#define NES_EMU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Multi_Buffer.h"
|
||||
#include "Nes_Cart.h"
|
||||
#include "Nes_Core.h"
|
||||
class Nes_State;
|
||||
|
||||
// Register optional mappers included with Nes_Emu
|
||||
void register_optional_mappers();
|
||||
|
||||
extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper
|
||||
|
||||
class Nes_Emu {
|
||||
public:
|
||||
Nes_Emu();
|
||||
virtual ~Nes_Emu();
|
||||
|
||||
// Basic setup
|
||||
|
||||
// Load iNES file into emulator and clear recording
|
||||
blargg_err_t load_ines( Auto_File_Reader );
|
||||
|
||||
// Set sample rate for sound generation
|
||||
blargg_err_t set_sample_rate( long );
|
||||
|
||||
// Size and depth of graphics buffer required for rendering. Note that this
|
||||
// is larger than the actual image, with a temporary area around the edge
|
||||
// that gets filled with junk. Its height is many times larger in Nes_Recorder
|
||||
// to allow caching of multiple images.
|
||||
enum { buffer_width = Nes_Ppu::buffer_width };
|
||||
int buffer_height() const { return buffer_height_; }
|
||||
enum { bits_per_pixel = 8 };
|
||||
|
||||
// Set graphics buffer to render pixels to. Pixels points to top-left pixel and
|
||||
// row_bytes is the number of bytes to get to the next line (positive or negative).
|
||||
void set_pixels( void* pixels, long row_bytes );
|
||||
|
||||
// Size of image generated in graphics buffer
|
||||
enum { image_width = 256 };
|
||||
enum { image_height = 240 };
|
||||
|
||||
// Basic emulation
|
||||
|
||||
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
|
||||
// and sound are available for output using the accessors below.
|
||||
virtual blargg_err_t emulate_frame( int joypad1, int joypad2 = 0 );
|
||||
|
||||
// Maximum size of palette that can be generated
|
||||
enum { max_palette_size = 256 };
|
||||
|
||||
// Result of current frame
|
||||
struct frame_t
|
||||
{
|
||||
int joypad_read_count; // number of times joypads were strobed (read)
|
||||
int burst_phase; // NTSC burst phase for frame (0, 1, or 2)
|
||||
|
||||
int sample_count; // number of samples (always a multiple of chan_count)
|
||||
int chan_count; // 1: mono, 2: stereo
|
||||
|
||||
int top; // top-left position of image in graphics buffer
|
||||
enum { left = 8 };
|
||||
unsigned char* pixels; // pointer to top-left pixel of image
|
||||
long pitch; // number of bytes to get to next row of image
|
||||
|
||||
int palette_begin; // first host palette entry, as set by set_palette_range()
|
||||
int palette_size; // number of entries used for current frame
|
||||
short palette [max_palette_size]; // [palette_begin to palette_begin+palette_size-1]
|
||||
};
|
||||
frame_t const& frame() const { return *frame_; }
|
||||
|
||||
// Read samples for the current frame. Returns number of samples read into buffer.
|
||||
// Currently all samples must be read in one call.
|
||||
virtual long read_samples( short* out, long max_samples );
|
||||
|
||||
// Additional features
|
||||
|
||||
// Use already-loaded cartridge. Retains pointer, so it must be kept around until
|
||||
// closed. A cartridge can be shared among multiple emulators. After opening,
|
||||
// cartridge's CHR data shouldn't be modified since a copy is cached internally.
|
||||
blargg_err_t set_cart( Nes_Cart const* );
|
||||
|
||||
// Pointer to current cartridge, or NULL if none is loaded
|
||||
Nes_Cart const* cart() const { return emu.cart; }
|
||||
|
||||
// Free any memory and close cartridge, if one was currently open. A new cartridge
|
||||
// must be opened before further emulation can take place.
|
||||
void close();
|
||||
|
||||
// Emulate powering NES off and then back on. If full_reset is false, emulates
|
||||
// pressing the reset button only, which doesn't affect memory, otherwise
|
||||
// emulates powering system off then on.
|
||||
virtual void reset( bool full_reset = true, bool erase_battery_ram = false );
|
||||
|
||||
// Number of undefined CPU instructions encountered. Cleared after reset() and
|
||||
// load_state(). A non-zero value indicates that cartridge is probably
|
||||
// incompatible.
|
||||
unsigned long error_count() const { return emu.error_count; }
|
||||
|
||||
// Sound
|
||||
|
||||
// Set sample rate and use a custom sound buffer instead of the default
|
||||
// mono buffer, i.e. Nes_Buffer, Effects_Buffer, etc..
|
||||
blargg_err_t set_sample_rate( long rate, Multi_Buffer* );
|
||||
|
||||
// Adjust effective frame rate by changing how many samples are generated each frame.
|
||||
// Allows fine tuning of frame rate to improve synchronization.
|
||||
void set_frame_rate( double rate );
|
||||
|
||||
// Number of sound channels for current cartridge
|
||||
int channel_count() const { return channel_count_; }
|
||||
|
||||
// Frequency equalizer parameters
|
||||
struct equalizer_t {
|
||||
double treble; // 5.0 = extra-crisp, -200.0 = muffled
|
||||
long bass; // 0 = deep, 20000 = tinny
|
||||
};
|
||||
|
||||
// Current frequency equalization
|
||||
equalizer_t const& equalizer() const { return equalizer_; }
|
||||
|
||||
// Change frequency equalization
|
||||
void set_equalizer( equalizer_t const& );
|
||||
|
||||
// Equalizer presets
|
||||
static equalizer_t const nes_eq; // NES
|
||||
static equalizer_t const famicom_eq; // Famicom
|
||||
static equalizer_t const tv_eq; // TV speaker
|
||||
|
||||
// File save/load
|
||||
|
||||
// Save emulator state
|
||||
void save_state( Nes_State* s ) const { emu.save_state( s ); }
|
||||
blargg_err_t save_state( Auto_File_Writer ) const;
|
||||
|
||||
// Load state into emulator
|
||||
void load_state( Nes_State const& );
|
||||
blargg_err_t load_state( Auto_File_Reader );
|
||||
|
||||
// True if current cartridge claims it uses battery-backed memory
|
||||
bool has_battery_ram() const { return cart()->has_battery_ram(); }
|
||||
|
||||
// Save current battery RAM
|
||||
blargg_err_t save_battery_ram( Auto_File_Writer );
|
||||
|
||||
// Load battery RAM from file. Best called just after reset() or loading cartridge.
|
||||
blargg_err_t load_battery_ram( Auto_File_Reader );
|
||||
|
||||
// Graphics
|
||||
|
||||
// Number of frames generated per second
|
||||
enum { frame_rate = 60 };
|
||||
|
||||
// Size of fixed NES color table (including the 8 color emphasis modes)
|
||||
enum { color_table_size = 8 * 64 };
|
||||
|
||||
// NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to
|
||||
// generate a palette with custom parameters.
|
||||
struct rgb_t { unsigned char red, green, blue; };
|
||||
static rgb_t const nes_colors [color_table_size];
|
||||
|
||||
// Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy.
|
||||
enum sprite_mode_t {
|
||||
sprites_hidden = 0,
|
||||
sprites_visible = 8, // limit of 8 sprites per scanline as on NES (default)
|
||||
sprites_enhanced = 64 // unlimited sprites per scanline (no flickering)
|
||||
};
|
||||
void set_sprite_mode( sprite_mode_t n ) { emu.ppu.sprite_limit = n; }
|
||||
|
||||
// Set range of host palette entries to use in graphics buffer; default uses
|
||||
// all of them. Begin will be rounded up to next multiple of palette_alignment.
|
||||
// Use frame().palette_begin to find the adjusted beginning entry used.
|
||||
enum { palette_alignment = 64 };
|
||||
void set_palette_range( int begin, int end = 256 );
|
||||
|
||||
// Access to emulated memory, for viewer/cheater/debugger
|
||||
|
||||
// CHR
|
||||
byte const* chr_mem();
|
||||
long chr_size() const;
|
||||
void write_chr( void const*, long count, long offset );
|
||||
|
||||
// Nametable
|
||||
byte* nametable_mem() { return emu.ppu.impl->nt_ram; }
|
||||
long nametable_size() const { return 0x1000; }
|
||||
|
||||
// Built-in 2K memory
|
||||
enum { low_mem_size = 0x800 };
|
||||
byte* low_mem() { return emu.low_mem; }
|
||||
|
||||
// Optional 8K memory
|
||||
enum { high_mem_size = 0x2000 };
|
||||
byte* high_mem() { return emu.impl->sram; }
|
||||
|
||||
// End of public interface
|
||||
public:
|
||||
blargg_err_t set_sample_rate( long rate, class Nes_Buffer* );
|
||||
blargg_err_t set_sample_rate( long rate, class Nes_Effects_Buffer* );
|
||||
void irq_changed() { emu.irq_changed(); }
|
||||
private:
|
||||
friend class Nes_Recorder;
|
||||
|
||||
frame_t* frame_;
|
||||
int buffer_height_;
|
||||
bool fade_sound_in;
|
||||
bool fade_sound_out;
|
||||
virtual blargg_err_t init_();
|
||||
|
||||
virtual void loading_state( Nes_State const& ) { }
|
||||
void load_state( Nes_State_ const& );
|
||||
void save_state( Nes_State_* s ) const { emu.save_state( s ); }
|
||||
int joypad_read_count() const { return emu.joypad_read_count; }
|
||||
long timestamp() const { return emu.nes.frame_count; }
|
||||
void set_timestamp( long t ) { emu.nes.frame_count = t; }
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Emu( const Nes_Emu& );
|
||||
Nes_Emu& operator = ( const Nes_Emu& );
|
||||
|
||||
// sound
|
||||
Multi_Buffer* default_sound_buf;
|
||||
Multi_Buffer* sound_buf;
|
||||
unsigned sound_buf_changed_count;
|
||||
Silent_Buffer silent_buffer;
|
||||
equalizer_t equalizer_;
|
||||
int channel_count_;
|
||||
bool sound_enabled;
|
||||
void enable_sound( bool );
|
||||
void clear_sound_buf();
|
||||
void fade_samples( blip_sample_t*, int size, int step );
|
||||
|
||||
char* host_pixels;
|
||||
int host_palette_size;
|
||||
frame_t single_frame;
|
||||
Nes_Cart private_cart;
|
||||
Nes_Core emu; // large; keep at end
|
||||
|
||||
bool init_called;
|
||||
blargg_err_t auto_init();
|
||||
};
|
||||
|
||||
inline void Nes_Emu::set_pixels( void* p, long n )
|
||||
{
|
||||
host_pixels = (char*) p + n;
|
||||
emu.ppu.host_row_bytes = n;
|
||||
}
|
||||
|
||||
inline byte const* Nes_Emu::chr_mem()
|
||||
{
|
||||
return cart()->chr_size() ? (byte*) cart()->chr() : emu.ppu.impl->chr_ram;
|
||||
}
|
||||
|
||||
inline long Nes_Emu::chr_size() const
|
||||
{
|
||||
return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_File.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// Nes_File_Writer
|
||||
|
||||
Nes_File_Writer::Nes_File_Writer()
|
||||
{
|
||||
write_remain = 0;
|
||||
depth_ = 0;
|
||||
}
|
||||
|
||||
Nes_File_Writer::~Nes_File_Writer()
|
||||
{
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::begin( Auto_File_Writer dw, nes_tag_t tag )
|
||||
{
|
||||
require( !out );
|
||||
out = dw;
|
||||
RETURN_ERR( out.open_comp() );
|
||||
return begin_group( tag );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::begin_group( nes_tag_t tag )
|
||||
{
|
||||
depth_++;
|
||||
return write_header( tag, group_begin_size );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::write_header( nes_tag_t tag, long size )
|
||||
{
|
||||
nes_block_t h;
|
||||
h.tag = tag;
|
||||
h.size = size;
|
||||
h.swap();
|
||||
return out->write( &h, sizeof h );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::write_block( nes_tag_t tag, void const* data, long size )
|
||||
{
|
||||
RETURN_ERR( write_block_header( tag, size ) );
|
||||
return write( data, size );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::write_block_header( nes_tag_t tag, long size )
|
||||
{
|
||||
require( !write_remain );
|
||||
write_remain = size;
|
||||
return write_header( tag, size );
|
||||
}
|
||||
|
||||
Nes_File_Writer::error_t Nes_File_Writer::write( void const* p, long s )
|
||||
{
|
||||
write_remain -= s;
|
||||
require( write_remain >= 0 );
|
||||
return out->write( p, s );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::end()
|
||||
{
|
||||
require( depth_ == 1 );
|
||||
return end_group();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Writer::end_group()
|
||||
{
|
||||
require( depth_ > 0 );
|
||||
depth_--;
|
||||
return write_header( group_end_tag, 0 );
|
||||
}
|
||||
|
||||
// Nes_File_Reader
|
||||
|
||||
Nes_File_Reader::Nes_File_Reader()
|
||||
{
|
||||
h.tag = 0;
|
||||
h.size = 0;
|
||||
block_type_ = invalid;
|
||||
depth_ = -1;
|
||||
}
|
||||
|
||||
Nes_File_Reader::~Nes_File_Reader()
|
||||
{
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::read_block_data( void* p, long s )
|
||||
{
|
||||
long extra = remain();
|
||||
if ( s > extra )
|
||||
s = extra;
|
||||
extra -= s;
|
||||
RETURN_ERR( read( p, s ) );
|
||||
if ( extra )
|
||||
RETURN_ERR( skip( extra ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::begin( Auto_File_Reader dr )
|
||||
{
|
||||
require( !in );
|
||||
RETURN_ERR( dr.open() );
|
||||
in = dr;
|
||||
RETURN_ERR( read_header() );
|
||||
if ( block_type() != group_begin )
|
||||
return "File is wrong type";
|
||||
return enter_group();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::read_header()
|
||||
{
|
||||
RETURN_ERR( in->read( &h, sizeof h ) );
|
||||
h.swap();
|
||||
block_type_ = data_block;
|
||||
if ( h.size == group_begin_size )
|
||||
{
|
||||
block_type_ = group_begin;
|
||||
h.size = 0;
|
||||
}
|
||||
if ( (long) h.tag == group_end_tag )
|
||||
{
|
||||
block_type_ = group_end;
|
||||
h.tag = 0;
|
||||
}
|
||||
set_remain( h.size );
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::next_block()
|
||||
{
|
||||
require( depth() >= 0 );
|
||||
switch ( block_type() )
|
||||
{
|
||||
case group_end:
|
||||
require( false );
|
||||
return "Tried to go past end of blocks";
|
||||
|
||||
case group_begin: {
|
||||
int d = 1;
|
||||
do
|
||||
{
|
||||
RETURN_ERR( skip( h.size ) );
|
||||
RETURN_ERR( read_header() );
|
||||
if ( block_type() == group_begin )
|
||||
d++;
|
||||
if ( block_type() == group_end )
|
||||
d--;
|
||||
}
|
||||
while ( d > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case data_block:
|
||||
RETURN_ERR( skip( h.size ) );
|
||||
break;
|
||||
|
||||
case invalid:
|
||||
break;
|
||||
}
|
||||
return read_header();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::enter_group()
|
||||
{
|
||||
require( block_type() == group_begin );
|
||||
block_type_ = invalid; // cause next_block() not to skip group
|
||||
depth_++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::exit_group()
|
||||
{
|
||||
require( depth() > 0 );
|
||||
int d = 1;
|
||||
while ( true )
|
||||
{
|
||||
if ( block_type() == group_end )
|
||||
d--;
|
||||
if ( block_type() == group_begin )
|
||||
d++;
|
||||
if ( d == 0 )
|
||||
break;
|
||||
RETURN_ERR( skip( h.size ) );
|
||||
RETURN_ERR( read_header() );
|
||||
}
|
||||
|
||||
block_type_ = invalid; // cause next_block() to read past end block
|
||||
depth_--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::skip_v( int s )
|
||||
{
|
||||
require( block_type() == data_block );
|
||||
if ( (unsigned long) s > h.size )
|
||||
return "Tried to skip past end of data";
|
||||
h.size -= s;
|
||||
set_remain( h.size );
|
||||
return in->skip( s );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_File_Reader::read_v( void* p, int n )
|
||||
{
|
||||
require( block_type() == data_block );
|
||||
if ( (unsigned long) n > h.size )
|
||||
n = h.size;
|
||||
h.size -= n;
|
||||
set_remain( h.size );
|
||||
return in->read( p, n );
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
// NES block-oriented file access
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_FILE_H
|
||||
#define NES_FILE_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "abstract_file.h"
|
||||
#include "nes_data.h"
|
||||
|
||||
// Writes a structured file
|
||||
class Nes_File_Writer : public Data_Writer {
|
||||
public:
|
||||
Nes_File_Writer();
|
||||
~Nes_File_Writer();
|
||||
|
||||
// Begin writing file with specified signature tag
|
||||
blargg_err_t begin( Auto_File_Writer, nes_tag_t );
|
||||
|
||||
// Begin tagged group
|
||||
blargg_err_t begin_group( nes_tag_t );
|
||||
|
||||
// Write tagged block
|
||||
blargg_err_t write_block( nes_tag_t, void const*, long size );
|
||||
|
||||
// Write tagged block header. 'Size' bytes must be written before next block.
|
||||
blargg_err_t write_block_header( nes_tag_t, long size );
|
||||
|
||||
// Write data to current block
|
||||
error_t write( void const*, long );
|
||||
|
||||
// End tagged group
|
||||
blargg_err_t end_group();
|
||||
|
||||
// End file
|
||||
blargg_err_t end();
|
||||
|
||||
private:
|
||||
Auto_File_Writer out;
|
||||
long write_remain;
|
||||
int depth_;
|
||||
blargg_err_t write_header( nes_tag_t tag, long size );
|
||||
};
|
||||
|
||||
// Reads a structured file
|
||||
class Nes_File_Reader : public Data_Reader {
|
||||
public:
|
||||
Nes_File_Reader();
|
||||
~Nes_File_Reader();
|
||||
|
||||
// If true, verify checksums of any blocks that have them
|
||||
void enable_checksums( bool = true );
|
||||
|
||||
// Begin reading file. Until next_block() is called, block_tag() yields tag for file.
|
||||
blargg_err_t begin( Auto_File_Reader );
|
||||
|
||||
// Read header of next block in current group
|
||||
blargg_err_t next_block();
|
||||
|
||||
// Type of current block
|
||||
enum block_type_t {
|
||||
data_block,
|
||||
group_begin,
|
||||
group_end,
|
||||
invalid
|
||||
};
|
||||
block_type_t block_type() const { return block_type_; }
|
||||
|
||||
// Tag of current block
|
||||
nes_tag_t block_tag() const { return h.tag; }
|
||||
|
||||
// Read at most s bytes from block and skip any remaining bytes
|
||||
blargg_err_t read_block_data( void*, long s );
|
||||
|
||||
// Read at most 's' bytes from current block and return number of bytes actually read
|
||||
virtual blargg_err_t read_v( void*, int n );
|
||||
|
||||
// Skip 's' bytes in current block
|
||||
virtual blargg_err_t skip_v( int s );
|
||||
|
||||
// Read first sub-block of current group block
|
||||
blargg_err_t enter_group();
|
||||
|
||||
// Skip past current group
|
||||
blargg_err_t exit_group();
|
||||
|
||||
// Current depth, where 0 is top-level in file and higher is deeper
|
||||
int depth() const { return depth_; }
|
||||
|
||||
// True if all data has been read
|
||||
bool done() const { return depth() == 0 && block_type() == group_end; }
|
||||
private:
|
||||
Auto_File_Reader in;
|
||||
nes_block_t h;
|
||||
block_type_t block_type_;
|
||||
int depth_;
|
||||
blargg_err_t read_header();
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline blargg_err_t read_nes_state( Nes_File_Reader& in, T* out )
|
||||
{
|
||||
blargg_err_t err = in.read_block_data( out, sizeof *out );
|
||||
out->swap();
|
||||
return err;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline blargg_err_t write_nes_state( Nes_File_Writer& out, T& in )
|
||||
{
|
||||
in.swap();
|
||||
blargg_err_t err = out.write_block( in.tag, &in, sizeof in );
|
||||
in.swap();
|
||||
return err;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline blargg_err_t write_nes_state( Nes_File_Writer& out, const T& in )
|
||||
{
|
||||
T copy = in;
|
||||
copy.swap();
|
||||
return out.write_block( copy.tag, ©, sizeof copy );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,531 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Film.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
nes_tag_t const joypad_data_tag = FOUR_CHAR('JOYP');
|
||||
|
||||
Nes_Film::Nes_Film() { clear( 60 * 60 ); }
|
||||
|
||||
Nes_Film::~Nes_Film() { }
|
||||
|
||||
void Nes_Film::clear( frame_count_t new_period )
|
||||
{
|
||||
period_ = new_period;
|
||||
end_ = begin_ = -invalid_frame_count;
|
||||
time_offset = 0;
|
||||
has_joypad_sync_ = true;
|
||||
has_second_joypad = false;
|
||||
data.clear( new_period );
|
||||
}
|
||||
|
||||
inline int Nes_Film::calc_block_count( frame_count_t new_end ) const
|
||||
{
|
||||
// usually one block extra than absolutely needed
|
||||
return (new_end - time_offset) / data.period() + 2;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::resize( frame_count_t new_end )
|
||||
{
|
||||
blargg_err_t err = data.resize( calc_block_count( new_end ) );
|
||||
if ( !err )
|
||||
end_ = new_end;
|
||||
return err;
|
||||
}
|
||||
|
||||
inline int Nes_Film::calc_block( frame_count_t time, int* index_out ) const
|
||||
{
|
||||
assert( time_offset <= time && time <= end() );
|
||||
frame_count_t rel = time - time_offset;
|
||||
int block = rel / data.period();
|
||||
*index_out = rel - block * data.period();
|
||||
return block;
|
||||
}
|
||||
|
||||
Nes_Film::joypad_t Nes_Film::get_joypad( frame_count_t time ) const
|
||||
{
|
||||
int index;
|
||||
block_t const& b = data.read( calc_block( time, &index ) );
|
||||
joypad_t result = b.joypad0 [index];
|
||||
if ( b.joypads [1] )
|
||||
result |= b.joypads [1] [index] << 8;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::set_joypad( frame_count_t time, joypad_t joypad )
|
||||
{
|
||||
int index;
|
||||
int block = calc_block( time, &index );
|
||||
block_t* b = data.write( block );
|
||||
CHECK_ALLOC( b );
|
||||
b->joypad0 [index] = joypad & 0xFF;
|
||||
|
||||
int joypad2 = joypad >> 8 & 0xFF;
|
||||
if ( joypad2 && !b->joypads [1] )
|
||||
CHECK_ALLOC( b = data.alloc_joypad2( block ) );
|
||||
if ( b->joypads [1] )
|
||||
{
|
||||
b->joypads [1] [index] = joypad2;
|
||||
has_second_joypad = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::record_frame( frame_count_t time, joypad_t joypad, Nes_State_** out )
|
||||
{
|
||||
if ( out )
|
||||
*out = 0;
|
||||
|
||||
if ( !contains( time ) )
|
||||
{
|
||||
require( blank() );
|
||||
clear();
|
||||
begin_ = end_ = time;
|
||||
time_offset = time - time % period_;
|
||||
}
|
||||
|
||||
RETURN_ERR( resize( time + 1 ) );
|
||||
|
||||
RETURN_ERR( set_joypad( time, joypad ) );
|
||||
|
||||
// first check detects stale snapshot left after trimming film
|
||||
if ( read_snapshot( time ).timestamp() > time || time == begin_ || time % period_ == 0 )
|
||||
{
|
||||
Nes_State_* ss = modify_snapshot( time );
|
||||
CHECK_ALLOC( ss );
|
||||
if ( out )
|
||||
*out = ss;
|
||||
if ( time != begin_ )
|
||||
ss->set_timestamp( invalid_frame_count ); // caller might not take snapshot
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Nes_State_ const* Nes_Film::nearest_snapshot( frame_count_t time ) const
|
||||
{
|
||||
require( contains( time ) );
|
||||
|
||||
if ( time > end() )
|
||||
time = end();
|
||||
|
||||
for ( int i = snapshot_index( time ); i >= 0; i-- )
|
||||
{
|
||||
Nes_State_ const& ss = snapshots( i );
|
||||
if ( ss.timestamp() <= time ) // invalid timestamp will always be greater
|
||||
return &ss;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
frame_count_t Nes_Film::constrain( frame_count_t t ) const
|
||||
{
|
||||
if ( t != invalid_frame_count && !blank() )
|
||||
{
|
||||
if ( t < begin_ ) t = begin_;
|
||||
if ( t > end_ ) t = end_;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
inline bool Nes_Film::contains_range( frame_count_t first, frame_count_t last ) const
|
||||
{
|
||||
return begin_ <= first && first <= last && last <= end_;
|
||||
}
|
||||
|
||||
void Nes_Film::trim( frame_count_t first, frame_count_t last )
|
||||
{
|
||||
check( begin() <= first && first <= last && last <= end() );
|
||||
|
||||
// TODO: this routine was broken; check thoroughly
|
||||
|
||||
if ( first > begin_ )
|
||||
begin_ = first;
|
||||
|
||||
// preserve first snapshot, which might be before beginning
|
||||
int first_block = (begin_ - time_offset) / data.period();
|
||||
if ( first_block > 0 )
|
||||
{
|
||||
// TODO: pathological thrashing still possible
|
||||
Nes_State_ const* ss = nearest_snapshot( begin_ );
|
||||
if ( ss )
|
||||
first_block = (ss->timestamp() - time_offset) / data.period();
|
||||
time_offset += first_block * data.period();
|
||||
}
|
||||
|
||||
if ( begin_ <= last && last < end_ )
|
||||
end_ = last;
|
||||
data.trim( first_block, calc_block_count( end_ ) );
|
||||
// be sure snapshot for beginning was preserved
|
||||
assert( nearest_snapshot( begin_ ) );
|
||||
}
|
||||
|
||||
// Nes_Film_Joypad_Scanner
|
||||
|
||||
// Simplifies scanning joypad data
|
||||
class Nes_Film_Joypad_Scanner {
|
||||
public:
|
||||
// Begin scanning range and set public members for first block
|
||||
Nes_Film_Joypad_Scanner( frame_count_t first, frame_count_t last, Nes_Film const& );
|
||||
|
||||
int block; // block index
|
||||
int offset; // offset in data
|
||||
int count; // number of bytes
|
||||
frame_count_t remain; // number of bytes remaining to scan
|
||||
|
||||
// Pointer to temporary buffer of 'block_period' bytes. Cleared
|
||||
// to zero before first use.
|
||||
unsigned char* buf();
|
||||
|
||||
// Go to next block. False if no more blocks.
|
||||
bool next();
|
||||
|
||||
~Nes_Film_Joypad_Scanner();
|
||||
private:
|
||||
Nes_Film& film;
|
||||
unsigned char* buf_;
|
||||
void recalc_count();
|
||||
};
|
||||
|
||||
inline unsigned char* Nes_Film_Joypad_Scanner::buf()
|
||||
{
|
||||
if ( !buf_ )
|
||||
buf_ = (unsigned char*) calloc( 1, film.data.period() );
|
||||
return buf_;
|
||||
}
|
||||
|
||||
inline void Nes_Film_Joypad_Scanner::recalc_count()
|
||||
{
|
||||
count = film.data.period() - offset;
|
||||
if ( count > remain )
|
||||
count = remain;
|
||||
}
|
||||
|
||||
Nes_Film_Joypad_Scanner::Nes_Film_Joypad_Scanner( frame_count_t first, frame_count_t last,
|
||||
Nes_Film const& f ) : film( *(Nes_Film*) &f )
|
||||
{
|
||||
buf_ = 0;
|
||||
remain = last - first;
|
||||
block = film.calc_block( first, &offset );
|
||||
recalc_count();
|
||||
film.data.joypad_only( true );
|
||||
}
|
||||
|
||||
Nes_Film_Joypad_Scanner::~Nes_Film_Joypad_Scanner()
|
||||
{
|
||||
film.data.joypad_only( false );
|
||||
free( buf_ );
|
||||
}
|
||||
|
||||
bool Nes_Film_Joypad_Scanner::next()
|
||||
{
|
||||
block++;
|
||||
offset = 0;
|
||||
remain -= count;
|
||||
if ( remain <= 0 )
|
||||
return false;
|
||||
recalc_count();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nes_Film_Writer
|
||||
|
||||
blargg_err_t Nes_Film_Writer::end( Nes_Film const& film, frame_count_t first,
|
||||
frame_count_t last, frame_count_t period )
|
||||
{
|
||||
RETURN_ERR( film.write_blocks( *this, first, last, period ) );
|
||||
return Nes_File_Writer::end();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::write( Auto_File_Writer out, frame_count_t first,
|
||||
frame_count_t last, frame_count_t period ) const
|
||||
{
|
||||
Nes_Film_Writer writer;
|
||||
RETURN_ERR( writer.begin( out ) );
|
||||
return writer.end( *this, first, last, (period ? period : this->period()) );
|
||||
}
|
||||
|
||||
static blargg_err_t write_state( Nes_State_ const& ss, Nes_File_Writer& out )
|
||||
{
|
||||
RETURN_ERR( out.begin_group( state_file_tag ) );
|
||||
RETURN_ERR( ss.write_blocks( out ) );
|
||||
return out.end_group();
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::write_blocks( Nes_File_Writer& out, frame_count_t first,
|
||||
frame_count_t last, frame_count_t period ) const
|
||||
{
|
||||
require( contains_range( first, last ) );
|
||||
require( nearest_snapshot( first ) );
|
||||
frame_count_t first_snapshot = nearest_snapshot( first )->timestamp();
|
||||
assert( first_snapshot <= first );
|
||||
|
||||
// write info block
|
||||
movie_info_t info;
|
||||
memset( &info, 0, sizeof info );
|
||||
info.begin = first;
|
||||
info.length = last - first;
|
||||
info.extra = first - first_snapshot;
|
||||
info.period = period;
|
||||
info.has_joypad_sync = has_joypad_sync_;
|
||||
info.joypad_count = 1;
|
||||
if ( has_second_joypad )
|
||||
{
|
||||
// Scan second joypad data for any blocks containing non-zero data
|
||||
Nes_Film_Joypad_Scanner joypad( first, last, *this );
|
||||
do
|
||||
{
|
||||
block_t const& b = data.read( joypad.block );
|
||||
if ( b.joypads [1] &&
|
||||
mem_differs( &b.joypads [1] [joypad.offset], 0, joypad.count ) )
|
||||
{
|
||||
info.joypad_count = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ( joypad.next() );
|
||||
}
|
||||
RETURN_ERR( write_nes_state( out, info ) );
|
||||
|
||||
// write joypad data
|
||||
for ( int i = 0; i < info.joypad_count; i++ )
|
||||
{
|
||||
Nes_Film_Joypad_Scanner joypad( first_snapshot, last, *this );
|
||||
RETURN_ERR( out.write_block_header( joypad_data_tag, joypad.remain ) );
|
||||
do
|
||||
{
|
||||
block_t const& b = data.read( joypad.block );
|
||||
byte const* data = b.joypads [i];
|
||||
if ( !data )
|
||||
CHECK_ALLOC( data = joypad.buf() );
|
||||
RETURN_ERR( out.write( &data [joypad.offset], joypad.count ) );
|
||||
}
|
||||
while ( joypad.next() );
|
||||
}
|
||||
|
||||
// write first state
|
||||
int index = snapshot_index( first_snapshot );
|
||||
assert( snapshots( index ).timestamp() == first_snapshot );
|
||||
RETURN_ERR( write_state( snapshots( index ), out ) );
|
||||
|
||||
// write snapshots that fall within output periods
|
||||
// TODO: thorougly verify this tricky algorithm
|
||||
//dprintf( "last: %6d\n", last );
|
||||
int last_index = snapshot_index( last );
|
||||
frame_count_t time = first_snapshot + period;
|
||||
for ( ; ++index <= last_index; )
|
||||
{
|
||||
Nes_State_ const& ss = snapshots( index );
|
||||
frame_count_t t = ss.timestamp();
|
||||
if ( t != invalid_frame_count )
|
||||
{
|
||||
while ( time + period <= t )
|
||||
time += period;
|
||||
|
||||
if ( t >= time - period )
|
||||
{
|
||||
time += period;
|
||||
//dprintf( "time: %6d\n", t );
|
||||
RETURN_ERR( write_state( ss, out ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Nes_Film_Reader
|
||||
|
||||
blargg_err_t Nes_Film::read( Auto_File_Reader in )
|
||||
{
|
||||
Nes_Film_Reader reader;
|
||||
RETURN_ERR( reader.begin( in, this ) );
|
||||
while ( !reader.done() )
|
||||
RETURN_ERR( reader.next_block() );
|
||||
return 0;
|
||||
}
|
||||
|
||||
Nes_Film_Reader::Nes_Film_Reader()
|
||||
{
|
||||
film = 0;
|
||||
info_ptr = 0;
|
||||
joypad_count = 0;
|
||||
film_initialized = false;
|
||||
memset( &info_, 0, sizeof info_ );
|
||||
}
|
||||
|
||||
Nes_Film_Reader::~Nes_Film_Reader() { }
|
||||
|
||||
blargg_err_t Nes_Film_Reader::begin( Auto_File_Reader dr, Nes_Film* nf )
|
||||
{
|
||||
film = nf;
|
||||
RETURN_ERR( Nes_File_Reader::begin( dr ) );
|
||||
if ( block_tag() != movie_file_tag )
|
||||
return "Not a movie file";
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::begin_read( movie_info_t const& info )
|
||||
{
|
||||
begin_ = info.begin - info.extra;
|
||||
end_ = info.begin + info.length;
|
||||
time_offset = begin_ - begin_ % period_;
|
||||
has_joypad_sync_ = info.has_joypad_sync;
|
||||
assert( begin_ <= end_ );
|
||||
return resize( end_ );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film_Reader::customize()
|
||||
{
|
||||
require( info_ptr );
|
||||
if ( film_initialized )
|
||||
return 0;
|
||||
film_initialized = true;
|
||||
film->clear();
|
||||
return film->begin_read( info_ );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film_Reader::next_block()
|
||||
{
|
||||
blargg_err_t err = next_block_();
|
||||
if ( err )
|
||||
film->clear(); // don't leave film in inconsistent state when reading fails
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film_Reader::next_block_()
|
||||
{
|
||||
for ( ; ; )
|
||||
{
|
||||
RETURN_ERR( Nes_File_Reader::next_block() );
|
||||
switch ( depth() == 0 ? block_tag() : 0 )
|
||||
{
|
||||
case movie_info_t::tag:
|
||||
check( !info_ptr );
|
||||
RETURN_ERR( read_nes_state( *this, &info_ ) );
|
||||
info_ptr = &info_;
|
||||
return 0;
|
||||
|
||||
case joypad_data_tag:
|
||||
RETURN_ERR( customize() );
|
||||
RETURN_ERR( film->read_joypad( *this, joypad_count++ ) );
|
||||
break;
|
||||
|
||||
case state_file_tag:
|
||||
RETURN_ERR( customize() );
|
||||
RETURN_ERR( film->read_snapshot( *this ) );
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( done() )
|
||||
{
|
||||
// at least first snapshot must have been read
|
||||
check( film->read_snapshot( film->begin_ ).timestamp() != invalid_frame_count );
|
||||
film->begin_ += info_.extra; // bump back to claimed beginning
|
||||
// todo: remove
|
||||
#if !defined (NDEBUG) && 0
|
||||
FILE* out = fopen( "raw_block", "wb" );
|
||||
int block_count = (film->end() - film->time_offset) / film->data.period() + 1;
|
||||
//for ( int i = 0; i < block_count; i++ )
|
||||
int i = (block_count > 1);
|
||||
fwrite( &film->data.read( i ), offsetof (Nes_Film_Data::block_t,joypad0 [film->data.period()]), 1, out );
|
||||
fclose( out );
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::read_joypad( Nes_File_Reader& in, int index )
|
||||
{
|
||||
check( index <= 1 );
|
||||
if ( index <= 1 )
|
||||
{
|
||||
Nes_Film_Joypad_Scanner joypad( begin_, end_, *this );
|
||||
do
|
||||
{
|
||||
block_t* b = data.write( joypad.block );
|
||||
CHECK_ALLOC( b );
|
||||
byte* p = b->joypads [index];
|
||||
if ( !p )
|
||||
CHECK_ALLOC( p = joypad.buf() );
|
||||
p += joypad.offset;
|
||||
RETURN_ERR( in.read( p, joypad.count ) );
|
||||
if ( !b->joypads [index] && mem_differs( p, 0, joypad.count ) )
|
||||
{
|
||||
// non-zero joypad2 data
|
||||
CHECK_ALLOC( b = data.alloc_joypad2( joypad.block ) );
|
||||
memcpy( &b->joypads [index] [joypad.offset], p, joypad.count );
|
||||
has_second_joypad = true;
|
||||
}
|
||||
}
|
||||
while ( joypad.next() );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film::read_snapshot( Nes_File_Reader& in )
|
||||
{
|
||||
RETURN_ERR( in.enter_group() );
|
||||
|
||||
// read snapshot's timestamp
|
||||
nes_state_t info;
|
||||
memset( &info, 0, sizeof info );
|
||||
for ( ; ; )
|
||||
{
|
||||
RETURN_ERR( in.next_block() );
|
||||
if ( in.block_tag() == info.tag )
|
||||
{
|
||||
RETURN_ERR( read_nes_state( in, &info ) );
|
||||
break;
|
||||
}
|
||||
check( false ); // shouldn't encounter any unknown blocks
|
||||
}
|
||||
frame_count_t time = info.frame_count;
|
||||
|
||||
if ( !contains( time ) )
|
||||
{
|
||||
check( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
// read snapshot only if it's earlier than any existing snapshot in same segment
|
||||
Nes_State_* ss = modify_snapshot( time );
|
||||
CHECK_ALLOC( ss );
|
||||
|
||||
// uninitialized snapshot's time is large positive value so always compares greater
|
||||
if ( time < ss->timestamp() )
|
||||
{
|
||||
// read new snapshot
|
||||
ss->clear();
|
||||
ss->set_nes_state( info );
|
||||
do
|
||||
{
|
||||
RETURN_ERR( ss->read_blocks( in ) );
|
||||
}
|
||||
while ( in.block_type() != in.group_end );
|
||||
}
|
||||
}
|
||||
return in.exit_group();
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
|
||||
// Film to record NES movies on using Nes_Recorder
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_FILM_H
|
||||
#define NES_FILM_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Nes_Film_Data.h"
|
||||
|
||||
// See below for custom reader and writer classes that allow user data in movie files
|
||||
class Nes_Film_Writer;
|
||||
class Nes_Film_Reader;
|
||||
|
||||
class Nes_Film {
|
||||
public:
|
||||
Nes_Film();
|
||||
~Nes_Film();
|
||||
|
||||
// Clear film to blankness
|
||||
void clear() { clear( period() ); }
|
||||
|
||||
// Time of first recorded snapshot
|
||||
frame_count_t begin() const { return begin_; }
|
||||
|
||||
// Time of *end* of last recorded frame
|
||||
frame_count_t end() const { return end_; }
|
||||
|
||||
// Number of frames in recording
|
||||
frame_count_t length() const { return end() - begin(); }
|
||||
|
||||
// Trim to subset of recording. OK if new_begin == new_end, which does
|
||||
// not make film blank, merely of zero length.
|
||||
void trim( frame_count_t new_begin, frame_count_t new_end );
|
||||
|
||||
// Write entire recording to file
|
||||
blargg_err_t write( Auto_File_Writer ) const;
|
||||
|
||||
// Read entire recording from file
|
||||
blargg_err_t read( Auto_File_Reader );
|
||||
|
||||
// Additional features
|
||||
|
||||
// Write trimmed recording to file, with snapshots approximately every 'period' frames
|
||||
blargg_err_t write( Auto_File_Writer, frame_count_t begin, frame_count_t end,
|
||||
frame_count_t period = 0 ) const;
|
||||
|
||||
// Clear film and set how often snapshots are taken. One snapshot is kept for
|
||||
// every 'period' frames of recording. A lower period makes seeking faster but
|
||||
// uses more memory.
|
||||
void clear( frame_count_t new_period );
|
||||
|
||||
// Average number of frames between snapshots
|
||||
frame_count_t period() const { return period_; }
|
||||
|
||||
// True if film has just been cleared
|
||||
bool blank() const { return end_ < 0; }
|
||||
|
||||
// True if timestamp is within recording. Always false if film is blank.
|
||||
bool contains( frame_count_t t ) const { return begin() <= t && t <= end(); }
|
||||
|
||||
// True if recording contains frame beginning at timestamp
|
||||
bool contains_frame( frame_count_t t ) const { return begin() <= t && t < end(); }
|
||||
|
||||
// Constrain timestamp to recorded range, or return unchanged if film is blank
|
||||
frame_count_t constrain( frame_count_t ) const;
|
||||
|
||||
// Raw access for use by Nes_Recorder
|
||||
|
||||
// True if joypad entries are 0xFF on frames that the joypad isn't read
|
||||
bool has_joypad_sync() const { return has_joypad_sync_; }
|
||||
|
||||
// Snapshot that might have current timestamp
|
||||
Nes_State_ const& read_snapshot( frame_count_t ) const;
|
||||
|
||||
// Snapshot that current timestamp maps to. NULL if out of memory.
|
||||
Nes_State_* modify_snapshot( frame_count_t );
|
||||
|
||||
// Pointer to nearest snapshot at or before timestamp, or NULL if none
|
||||
Nes_State_ const* nearest_snapshot( frame_count_t ) const;
|
||||
|
||||
typedef unsigned long joypad_t;
|
||||
|
||||
// Get joypad data for frame beginning at timestamp
|
||||
joypad_t get_joypad( frame_count_t ) const;
|
||||
|
||||
// Change joypad data for frame beginning at timestamp. Frame must already have
|
||||
// been recorded normally.
|
||||
blargg_err_t set_joypad( frame_count_t, joypad_t );
|
||||
|
||||
// Record new frame beginning at timestamp using joypad data. Returns
|
||||
// pointer where snapshot should be saved to, or NULL if a snapshot isn't
|
||||
// needed for this timestamp. Removes anything recorded after frame.
|
||||
blargg_err_t record_frame( frame_count_t, joypad_t joypad, Nes_State_** out = 0 );
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Film( Nes_Film const& );
|
||||
Nes_Film& operator = ( Nes_Film const& );
|
||||
|
||||
typedef Nes_Film_Data::block_t block_t;
|
||||
Nes_Film_Data data;
|
||||
frame_count_t begin_;
|
||||
frame_count_t end_;
|
||||
frame_count_t period_;
|
||||
frame_count_t time_offset;
|
||||
bool has_joypad_sync_;
|
||||
bool has_second_joypad;
|
||||
|
||||
int calc_block( frame_count_t time, int* index_out ) const;
|
||||
int snapshot_index( frame_count_t ) const;
|
||||
Nes_State_ const& snapshots( int ) const;
|
||||
int calc_block_count( frame_count_t new_end ) const;
|
||||
blargg_err_t resize( frame_count_t new_end );
|
||||
bool contains_range( frame_count_t first, frame_count_t last ) const;
|
||||
|
||||
blargg_err_t write_blocks( Nes_File_Writer&, frame_count_t first,
|
||||
frame_count_t last, frame_count_t period ) const;
|
||||
blargg_err_t begin_read( movie_info_t const& );
|
||||
blargg_err_t read_joypad( Nes_File_Reader&, int index );
|
||||
blargg_err_t read_snapshot( Nes_File_Reader& );
|
||||
|
||||
friend class Nes_Film_Reader;
|
||||
friend class Nes_Film_Writer;
|
||||
friend class Nes_Film_Joypad_Scanner;
|
||||
};
|
||||
|
||||
// Allows user data blocks to be written with film
|
||||
class Nes_Film_Writer : public Nes_File_Writer {
|
||||
public:
|
||||
// Begin writing movie file
|
||||
blargg_err_t begin( Auto_File_Writer );
|
||||
|
||||
// End writing movie file. Optionally specify custom period and subset of
|
||||
// recording to write.
|
||||
blargg_err_t end( Nes_Film const& );
|
||||
blargg_err_t end( Nes_Film const&, frame_count_t first, frame_count_t last,
|
||||
frame_count_t period );
|
||||
};
|
||||
|
||||
// Allows film information to be checked before loading film, and for handling
|
||||
// of user data blocks.
|
||||
class Nes_Film_Reader : public Nes_File_Reader {
|
||||
public:
|
||||
Nes_Film_Reader();
|
||||
~Nes_Film_Reader();
|
||||
|
||||
// Begin reading from movie file. Does not modify film until later (see below).
|
||||
blargg_err_t begin( Auto_File_Reader, Nes_Film* out );
|
||||
|
||||
// Go to next custom block in file
|
||||
blargg_err_t next_block();
|
||||
|
||||
// Information about film (see nes_state.h for fields). Returns zero
|
||||
// until information is encountered in file. Once return value becomes
|
||||
// non-zero, next call to next_block() will read movie into film.
|
||||
// Until that time, film is not modified or examined at all.
|
||||
movie_info_t const* info() const { return info_ptr; }
|
||||
|
||||
// to do: allow reading subset of recording from file (for example, last 5 minutes)
|
||||
|
||||
private:
|
||||
Nes_Film* film;
|
||||
bool film_initialized;
|
||||
movie_info_t info_;
|
||||
movie_info_t* info_ptr;
|
||||
int joypad_count;
|
||||
|
||||
blargg_err_t customize();
|
||||
blargg_err_t next_block_();
|
||||
};
|
||||
|
||||
inline blargg_err_t Nes_Film_Writer::begin( Auto_File_Writer dw )
|
||||
{
|
||||
return Nes_File_Writer::begin( dw, movie_file_tag );
|
||||
}
|
||||
|
||||
inline blargg_err_t Nes_Film::write( Auto_File_Writer out ) const
|
||||
{
|
||||
return write( out, begin(), end(), period() );
|
||||
}
|
||||
|
||||
inline blargg_err_t Nes_Film_Writer::end( Nes_Film const& film )
|
||||
{
|
||||
return end( film, film.begin(), film.end(), film.period() );
|
||||
}
|
||||
|
||||
inline int Nes_Film::snapshot_index( frame_count_t time ) const
|
||||
{
|
||||
return (time - time_offset) / period_;
|
||||
}
|
||||
|
||||
inline Nes_State_ const& Nes_Film::snapshots( int i ) const
|
||||
{
|
||||
return data.read( (unsigned) i / data.block_size ).states [(unsigned) i % data.block_size];
|
||||
}
|
||||
|
||||
inline Nes_State_ const& Nes_Film::read_snapshot( frame_count_t time ) const
|
||||
{
|
||||
return snapshots( snapshot_index( time ) );
|
||||
}
|
||||
|
||||
inline Nes_State_* Nes_Film::modify_snapshot( frame_count_t time )
|
||||
{
|
||||
int i = snapshot_index( time );
|
||||
block_t* b = data.write( (unsigned) i / data.block_size );
|
||||
if ( b )
|
||||
return &b->states [(unsigned) i % data.block_size];
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Film_Data.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Nes_Film_Data::Nes_Film_Data()
|
||||
{
|
||||
blocks = 0;
|
||||
active = 0;
|
||||
block_count = 0;
|
||||
period_ = 0;
|
||||
packer = 0;
|
||||
|
||||
BOOST_STATIC_ASSERT( sizeof active->cpu [0] % 4 == 0 );
|
||||
BOOST_STATIC_ASSERT( sizeof active->joypad [0] % 4 == 0 );
|
||||
BOOST_STATIC_ASSERT( sizeof active->apu [0] % 4 == 0 );
|
||||
BOOST_STATIC_ASSERT( sizeof active->ppu [0] % 4 == 0 );
|
||||
BOOST_STATIC_ASSERT( sizeof active->mapper [0] % 4 == 0 );
|
||||
BOOST_STATIC_ASSERT( sizeof active->states [0] % 4 == 0 );
|
||||
//BOOST_STATIC_ASSERT( offsetof (block_t,joypad0) % 4 == 0 ); // XXX
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
static void write_file( void const* in, long size, const char* path )
|
||||
{
|
||||
FILE* out = fopen( path, "wb" );
|
||||
if ( out )
|
||||
{
|
||||
fwrite( in, size, 1, out );
|
||||
fclose( out );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Film_Data::debug_packer() const
|
||||
{
|
||||
comp_block_t* b = blocks [active_index];
|
||||
static byte* temp = new byte [active_size() * 2];
|
||||
for ( int i = 0; i < active_size() * 2; i++ )
|
||||
temp [i] = i;
|
||||
long vs = packer->unpack( b->data, b->size, temp );
|
||||
if ( vs != active_size() - b->offset )
|
||||
{
|
||||
dprintf( "Unpacked size differs\n" );
|
||||
write_file( (byte*) active + b->offset, vs, "original" );
|
||||
write_file( temp, vs, "error" );
|
||||
assert( false );
|
||||
}
|
||||
if ( memcmp( (byte*) active + b->offset, temp, vs ) )
|
||||
{
|
||||
dprintf( "Unpacked content differs\n" );
|
||||
write_file( (byte*) active + b->offset, vs, "original" );
|
||||
write_file( temp, vs, "error" );
|
||||
assert( false );
|
||||
}
|
||||
|
||||
if ( 0 )
|
||||
{
|
||||
long total = 0;
|
||||
for ( int i = 0; i < block_count; i++ )
|
||||
if ( blocks [i] )
|
||||
total += blocks [i]->size;
|
||||
//dprintf( "Compression: %ld%%\n", total * 100 / (block_count * (active_size() - period_)) );
|
||||
dprintf( "Memory: %ld+%ldK\n", total / 1024, active_size() * 2 / 1024 + 16 );
|
||||
//dprintf( "Memory: %ldK\n", (total + active_size() * 2) / 1024 + 16 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Nes_Film_Data::flush_active() const
|
||||
{
|
||||
if ( active_dirty )
|
||||
{
|
||||
assert( (unsigned) active_index < (unsigned) block_count );
|
||||
|
||||
active_dirty = false;
|
||||
comp_block_t* b = blocks [active_index];
|
||||
assert( b && !b->size ); // should have been reallocated in write()
|
||||
check( b->offset == joypad_only_ );
|
||||
b->size = packer->pack( (byte*) active + b->offset, active_size() - b->offset, b->data );
|
||||
assert( b->size <= packer->worst_case( active_size() - b->offset ) );
|
||||
|
||||
// shrink allocation
|
||||
void* mem = realloc( b, offsetof (comp_block_t, data) + b->size * sizeof(b->data[0]) );
|
||||
if ( mem )
|
||||
blocks [active_index] = (comp_block_t*) mem;
|
||||
else
|
||||
check( false ); // shrink shouldn't fail, but fine if it does
|
||||
|
||||
#ifndef NDEBUG
|
||||
debug_packer();
|
||||
#endif
|
||||
}
|
||||
active_index = -1;
|
||||
}
|
||||
|
||||
void Nes_Film_Data::init_states() const
|
||||
{
|
||||
memset( active->states, 0, sizeof active->states );
|
||||
block_t* b = active;
|
||||
b->garbage0 = 1;
|
||||
b->garbage1 = 2;
|
||||
b->garbage2 = 3;
|
||||
for ( int j = 0; j < block_size; j++ )
|
||||
{
|
||||
Nes_State_& s = b->states [j];
|
||||
s.cpu = &b->cpu [j];
|
||||
s.joypad = &b->joypad [j];
|
||||
s.apu = &b->apu [j];
|
||||
s.ppu = &b->ppu [j];
|
||||
s.mapper = &b->mapper [j];
|
||||
s.ram = b->ram [j];
|
||||
s.sram = b->sram [j];
|
||||
s.spr_ram = b->spr_ram [j];
|
||||
s.nametable = b->nametable [j];
|
||||
s.chr = b->chr [j];
|
||||
s.set_timestamp( invalid_frame_count );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Film_Data::access( index_t i ) const
|
||||
{
|
||||
assert( (unsigned) i < (unsigned) block_count );
|
||||
if ( active_dirty )
|
||||
flush_active();
|
||||
active_index = i;
|
||||
comp_block_t* b = blocks [i];
|
||||
if ( b )
|
||||
{
|
||||
assert( b->size );
|
||||
long size = packer->unpack( b->data, b->size, (byte*) active + b->offset );
|
||||
assert( b->offset + size == active_size() );
|
||||
if ( b->offset )
|
||||
init_states();
|
||||
}
|
||||
else
|
||||
{
|
||||
active->joypads [0] = &active->joypad0 [0];
|
||||
active->joypads [1] = &active->joypad0 [period_];
|
||||
init_states();
|
||||
memset( active->joypad0, 0, period_ * 2 );
|
||||
}
|
||||
}
|
||||
|
||||
Nes_Film_Data::block_t* Nes_Film_Data::write( int i )
|
||||
{
|
||||
require( (unsigned) i < (unsigned) block_count );
|
||||
if ( i != active_index )
|
||||
access( i );
|
||||
if ( !active_dirty )
|
||||
{
|
||||
// preallocate now to avoid losing write when flushed later
|
||||
long size = packer->worst_case( active_size() - joypad_only_ );
|
||||
comp_block_t* new_mem = (comp_block_t*) realloc( blocks [i], size );
|
||||
if ( !new_mem )
|
||||
return 0;
|
||||
new_mem->size = 0;
|
||||
new_mem->offset = joypad_only_;
|
||||
blocks [i] = new_mem;
|
||||
active_dirty = true;
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
void Nes_Film_Data::joypad_only( bool b )
|
||||
{
|
||||
flush_active();
|
||||
joypad_only_ = b * offsetof (block_t,joypad0);
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Film_Data::resize( int new_count )
|
||||
{
|
||||
if ( new_count < block_count )
|
||||
{
|
||||
assert( active );
|
||||
|
||||
if ( active_index >= new_count )
|
||||
flush_active();
|
||||
|
||||
for ( int i = new_count; i < block_count; i++ )
|
||||
free( blocks [i] );
|
||||
|
||||
block_count = new_count;
|
||||
void* new_blocks = realloc( blocks, new_count * sizeof *blocks );
|
||||
if ( new_blocks || !new_count )
|
||||
blocks = (comp_block_t**) new_blocks;
|
||||
else
|
||||
check( false ); // shrink shouldn't fail, but fine if it does
|
||||
}
|
||||
else if ( new_count > block_count )
|
||||
{
|
||||
if ( !packer )
|
||||
CHECK_ALLOC( packer = BLARGG_NEW Nes_Film_Packer );
|
||||
|
||||
if ( !active )
|
||||
{
|
||||
assert( period_ );
|
||||
active = (block_t*) calloc( active_size(), 1 );
|
||||
CHECK_ALLOC( active );
|
||||
//init_active(); // TODO: unnecessary since it's called on first access anyway?
|
||||
packer->prepare( active, active_size() );
|
||||
}
|
||||
|
||||
void* new_blocks = realloc( blocks, new_count * sizeof *blocks );
|
||||
CHECK_ALLOC( new_blocks );
|
||||
blocks = (comp_block_t**) new_blocks;
|
||||
|
||||
for ( int i = block_count; i < new_count; i++ )
|
||||
blocks [i] = 0;
|
||||
|
||||
block_count = new_count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Film_Data::clear( frame_count_t period )
|
||||
{
|
||||
active_index = -1;
|
||||
active_dirty = false;
|
||||
if ( resize( 0 ) )
|
||||
check( false ); // shrink should never fail
|
||||
joypad_only_ = false;
|
||||
period_ = period * block_size;
|
||||
free( active );
|
||||
active = 0;
|
||||
}
|
||||
|
||||
void Nes_Film_Data::trim( int begin, int new_count )
|
||||
{
|
||||
require( 0 <= begin && begin + new_count <= block_count );
|
||||
require( (unsigned) new_count <= (unsigned) block_count );
|
||||
if ( (unsigned) new_count < (unsigned) block_count )
|
||||
{
|
||||
if ( begin || active_index >= new_count )
|
||||
flush_active();
|
||||
|
||||
if ( begin )
|
||||
{
|
||||
for ( int i = 0; i < begin; i++ )
|
||||
free( blocks [i] );
|
||||
memmove( &blocks [0], &blocks [begin], (block_count - begin) * sizeof *blocks );
|
||||
block_count -= begin;
|
||||
}
|
||||
|
||||
if ( resize( new_count ) )
|
||||
check( false ); // shrink should never fail
|
||||
}
|
||||
}
|
||||
|
||||
Nes_Film_Data::~Nes_Film_Data()
|
||||
{
|
||||
if ( resize( 0 ) )
|
||||
check( false ); // shrink should never fail
|
||||
free( active );
|
||||
delete packer;
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
// Film data manager that keeps data compressed in memory
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_FILM_DATA_H
|
||||
#define NES_FILM_DATA_H
|
||||
|
||||
#include "Nes_State.h"
|
||||
#include "Nes_Film_Packer.h"
|
||||
|
||||
class Nes_Film_Data {
|
||||
public:
|
||||
Nes_Film_Data();
|
||||
~Nes_Film_Data();
|
||||
|
||||
void clear( frame_count_t period );
|
||||
frame_count_t period() const { return period_; }
|
||||
blargg_err_t resize( int new_count );
|
||||
void trim( int begin, int end );
|
||||
|
||||
enum { block_size = 8 }; // 16 helps compression but doubles temp buffer size
|
||||
typedef int index_t;
|
||||
struct block_t
|
||||
{
|
||||
BOOST::uint8_t* joypads [2];
|
||||
|
||||
Nes_State_ states [block_size];
|
||||
|
||||
Nes_Cpu::registers_t cpu [block_size];
|
||||
joypad_state_t joypad [block_size];
|
||||
apu_state_t apu [block_size];
|
||||
ppu_state_t ppu [block_size];
|
||||
mapper_state_t mapper [block_size];
|
||||
BOOST::uint8_t spr_ram [block_size] [Nes_State_::spr_ram_size];
|
||||
BOOST::uint8_t ram [block_size] [Nes_State_::ram_size];
|
||||
BOOST::uint8_t nametable [block_size] [Nes_State_::nametable_max];
|
||||
BOOST::uint32_t garbage0;
|
||||
BOOST::uint8_t chr [block_size] [Nes_State_::chr_max];
|
||||
BOOST::uint32_t garbage1;
|
||||
BOOST::uint8_t sram [block_size] [Nes_State_::sram_max];
|
||||
BOOST::uint32_t garbage2;
|
||||
// garbage values prevent matches in compressor from being longer than 256K
|
||||
BOOST::uint8_t joypad0 [60 * 60L * 60L];
|
||||
};
|
||||
block_t const& read( index_t ) const;
|
||||
block_t* write( index_t ); // NULL if out of memory
|
||||
block_t* alloc_joypad2( index_t i ) { return write( i ); }
|
||||
void joypad_only( bool );
|
||||
|
||||
private:
|
||||
struct comp_block_t
|
||||
{
|
||||
long size;
|
||||
long offset;
|
||||
BOOST::uint8_t data [1024 * 1024L];
|
||||
};
|
||||
comp_block_t** blocks;
|
||||
int block_count;
|
||||
frame_count_t period_;
|
||||
block_t* active;
|
||||
mutable int active_index;
|
||||
mutable bool active_dirty;
|
||||
long joypad_only_;
|
||||
Nes_Film_Packer* packer;
|
||||
|
||||
void debug_packer() const;
|
||||
void flush_active() const;
|
||||
void init_active() const;
|
||||
void init_states() const;
|
||||
void invalidate_active();
|
||||
void access( index_t ) const;
|
||||
// must be multiple of 4 for packer
|
||||
long active_size() const { return (offsetof (block_t,joypad0) + 3) & ~3; }
|
||||
};
|
||||
|
||||
inline Nes_Film_Data::block_t const& Nes_Film_Data::read( int i ) const
|
||||
{
|
||||
//assert( blocks [i] ); // catch reads of uninitialized blocks
|
||||
if ( i != active_index )
|
||||
access( i );
|
||||
return *active;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Film_Packer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
// - On my 400 MHz PowerPC G3, pack() = 230MB/sec, unpack() = 320MB/sec.
|
||||
// - All 32-bit accessess are on 4-byte boundaries of the input/output buffers.
|
||||
// - This would not make a good general-purpose compressor because the match
|
||||
// offset is limited to a multiple of 4.
|
||||
|
||||
#ifdef __MWERKS__
|
||||
static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants
|
||||
#else
|
||||
const unsigned zero = 0; // compile-time constant on other compilers
|
||||
#endif
|
||||
|
||||
void Nes_Film_Packer::prepare( void const* begin, long size )
|
||||
{
|
||||
uint32_t const* end = (uint32_t*) ((byte*) begin + size - 4);
|
||||
uint32_t const** d = dict;
|
||||
for ( int n = dict_size; n--; )
|
||||
*d++ = end;
|
||||
|
||||
uint32_t temp = 0x80000000;
|
||||
assert( (BOOST::int32_t) temp < 0 ); // be sure high bit is sign
|
||||
}
|
||||
|
||||
long Nes_Film_Packer::pack( byte const* in_begin, long in_size, byte* out_begin )
|
||||
{
|
||||
//memcpy( out_begin, in_begin, in_size ); return in_size;
|
||||
|
||||
assert( (in_size & 3) == 0 );
|
||||
uint32_t const* const in_end = (uint32_t*) (in_begin + in_size);
|
||||
uint32_t const* const end = in_end - 2;
|
||||
uint32_t const* in = (uint32_t*) in_begin;
|
||||
uint32_t* out = (uint32_t*) out_begin;
|
||||
|
||||
unsigned long first = *in++;
|
||||
unsigned long offset;
|
||||
uint32_t const* match;
|
||||
|
||||
unsigned long const factor = 0x100801 + zero;
|
||||
|
||||
// spaghetti program flow gives better efficiency
|
||||
|
||||
goto begin;
|
||||
|
||||
// match loop
|
||||
do
|
||||
{
|
||||
if ( match [-1] != first ) break;
|
||||
offset <<= 14;
|
||||
if ( *match != *in ) break;
|
||||
match:
|
||||
// count matching words beyond the first two
|
||||
unsigned long n = (byte*) end - (byte*) in;
|
||||
first = *++in;
|
||||
unsigned long m = *++match;
|
||||
uint32_t const* start = in;
|
||||
for ( n >>= 2; n; --n )
|
||||
{
|
||||
if ( m != first ) break;
|
||||
m = *++match;
|
||||
first = *++in;
|
||||
}
|
||||
|
||||
// encode match offset and length
|
||||
unsigned long length = (byte*) in - (byte*) start;
|
||||
assert( 0 <= length && length <= 0xFFFF << 2 );
|
||||
assert( offset >> 16 <= 0x7FFF );
|
||||
offset |= length >> 2;
|
||||
|
||||
// check for next match
|
||||
unsigned long index = (first * factor) >> dict_shift & (dict_size - 1);
|
||||
match = dict [index];
|
||||
*out++ = offset; // interleved write of previous match
|
||||
offset = (byte*) in - (byte*) match; assert( !(offset & 3) );
|
||||
if ( in >= end ) goto match_end;
|
||||
++in;
|
||||
dict [index] = in;
|
||||
}
|
||||
while ( offset < 0x20000 );
|
||||
|
||||
begin:
|
||||
// start writing next literal
|
||||
out [1] = first;
|
||||
uint32_t* literal;
|
||||
literal = out;
|
||||
out++;
|
||||
|
||||
// literal loop
|
||||
literal:
|
||||
first = *in;
|
||||
do
|
||||
{
|
||||
// check for match
|
||||
unsigned long index = (first * factor) >> dict_shift & (dict_size - 1);
|
||||
*++out = first; // interleved write of current literal
|
||||
match = dict [index];
|
||||
dict [index] = in + 1;
|
||||
if ( in >= end ) goto literal_end;
|
||||
offset = (byte*) in - (byte*) match; assert( !(offset & 3) );
|
||||
++in;
|
||||
if ( match [-1] != first ) goto literal;
|
||||
first = *in;
|
||||
}
|
||||
while ( offset >= 0x20000 || *match != first );
|
||||
|
||||
// set length of completed literal
|
||||
offset <<= 14;
|
||||
*literal = (((byte*) out - (byte*) literal) >> 2) | 0x80000000;
|
||||
goto match;
|
||||
|
||||
match_end:
|
||||
// start new literal for remaining data after final match
|
||||
literal = out++;
|
||||
literal_end:
|
||||
--out;
|
||||
|
||||
// write remaining data to literal
|
||||
assert( in < in_end );
|
||||
do
|
||||
{
|
||||
*++out = *in++;
|
||||
}
|
||||
while ( in < in_end );
|
||||
*literal = (((byte*) out - (byte*) literal) >> 2) + 0x80000001;
|
||||
|
||||
// mark end with zero word
|
||||
*++out = 0x80000000;
|
||||
++out;
|
||||
|
||||
long out_size = (byte*) out - out_begin;
|
||||
assert( (out_size & 3) == 0 );
|
||||
return out_size;
|
||||
}
|
||||
|
||||
long Nes_Film_Packer::unpack( byte const* in_begin, long in_size, byte* out_begin )
|
||||
{
|
||||
//memcpy( out_begin, in_begin, in_size ); return in_size;
|
||||
|
||||
assert( (in_size & 3) == 0 );
|
||||
uint32_t const* in = (uint32_t*) in_begin;
|
||||
uint32_t* out = (uint32_t*) out_begin;
|
||||
long const literal_offset = 0x7FFFFFFE + zero;
|
||||
long count = (BOOST::int32_t) *in++;
|
||||
uint32_t const* m;
|
||||
|
||||
assert( count < 0 ); // first item should be literal
|
||||
goto literal;
|
||||
do
|
||||
{
|
||||
// match
|
||||
do
|
||||
{
|
||||
assert( m - 1 >= (void*) out_begin );
|
||||
assert( m - 1 < out );
|
||||
unsigned long data = m [-1];
|
||||
*out++ = data;
|
||||
data = *m;
|
||||
if ( (count &= 0xFFFF) != 0 )
|
||||
{
|
||||
do
|
||||
{
|
||||
*out++ = data;
|
||||
data = *++m;
|
||||
}
|
||||
while ( --count );
|
||||
}
|
||||
count = (BOOST::int32_t) *in++;
|
||||
*out++ = data;
|
||||
m = out - (count >> 16);
|
||||
}
|
||||
while ( count >= 0 );
|
||||
|
||||
literal:
|
||||
unsigned long data = *in++;
|
||||
*out++ = data;
|
||||
data = *in++;
|
||||
if ( (count += literal_offset) != 0 )
|
||||
{
|
||||
do
|
||||
{
|
||||
*out++ = data;
|
||||
data = *in++;
|
||||
}
|
||||
while ( --count );
|
||||
}
|
||||
|
||||
count = (BOOST::int32_t) data;
|
||||
m = out - (data >> 16);
|
||||
}
|
||||
while ( count >= 0 );
|
||||
|
||||
assert( count == (BOOST::int32_t) 0x80000000 );
|
||||
assert( (byte*) in == in_begin + in_size );
|
||||
long out_size = (byte*) out - out_begin;
|
||||
assert( (out_size & 3) == 0 );
|
||||
return out_size;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
// Fast save state compressor/decompressor for reducing Nes_Film memory usage
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_FILM_PACKER_H
|
||||
#define NES_FILM_PACKER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
class Nes_Film_Packer {
|
||||
public:
|
||||
void prepare( void const* begin, long size );
|
||||
|
||||
// Worst-case output size for given input size
|
||||
long worst_case( long in_size ) const { return in_size + 8; }
|
||||
|
||||
typedef unsigned char byte;
|
||||
long pack( byte const* in, long size, byte* packed_out );
|
||||
|
||||
long unpack( byte const* packed_in, long packed_size, byte* out );
|
||||
private:
|
||||
enum { dict_bits = 12 };
|
||||
enum { dict_size = 1 << dict_bits };
|
||||
enum { dict_shift = 32 - dict_bits };
|
||||
|
||||
typedef BOOST::uint32_t uint32_t;
|
||||
uint32_t const* dict [dict_size];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
|
||||
// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Fme07_Apu.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2003-2005 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
void Nes_Fme07_Apu::reset()
|
||||
{
|
||||
last_time = 0;
|
||||
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
oscs [i].last_amp = 0;
|
||||
|
||||
fme07_snapshot_t* state = this;
|
||||
memset( state, 0, sizeof *state );
|
||||
}
|
||||
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
|
||||
unsigned char Nes_Fme07_Apu::amp_table [16] =
|
||||
{
|
||||
#define ENTRY( n ) (n * amp_range) + 0.5
|
||||
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
|
||||
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
|
||||
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
|
||||
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
|
||||
#undef ENTRY
|
||||
};
|
||||
|
||||
void Nes_Fme07_Apu::run_until( blip_time_t end_time )
|
||||
{
|
||||
require( end_time >= last_time );
|
||||
|
||||
for ( int index = 0; index < osc_count; index++ )
|
||||
{
|
||||
int mode = regs [7] >> index;
|
||||
int vol_mode = regs [010 + index];
|
||||
int volume = amp_table [vol_mode & 0x0f];
|
||||
|
||||
if ( !oscs [index].output )
|
||||
continue;
|
||||
|
||||
// check for unsupported mode
|
||||
#ifndef NDEBUG
|
||||
if ( (mode & 011) <= 001 && vol_mode & 0x1f )
|
||||
dprintf( "FME07 used unimplemented sound mode: %02X, vol_mode: %02X\n",
|
||||
mode, vol_mode & 0x1f );
|
||||
#endif
|
||||
|
||||
if ( (mode & 001) | (vol_mode & 0x10) )
|
||||
volume = 0; // noise and envelope aren't supported
|
||||
|
||||
// period
|
||||
int const period_factor = 16;
|
||||
unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor +
|
||||
regs [index * 2] * period_factor;
|
||||
if ( period < 50 ) // around 22 kHz
|
||||
{
|
||||
volume = 0;
|
||||
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
|
||||
period = period_factor;
|
||||
}
|
||||
|
||||
// current amplitude
|
||||
int amp = volume;
|
||||
if ( !phases [index] )
|
||||
amp = 0;
|
||||
int delta = amp - oscs [index].last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
oscs [index].last_amp = amp;
|
||||
synth.offset( last_time, delta, oscs [index].output );
|
||||
}
|
||||
|
||||
blip_time_t time = last_time + delays [index];
|
||||
if ( time < end_time )
|
||||
{
|
||||
Blip_Buffer* const osc_output = oscs [index].output;
|
||||
int delta = amp * 2 - volume;
|
||||
|
||||
if ( volume )
|
||||
{
|
||||
do
|
||||
{
|
||||
delta = -delta;
|
||||
synth.offset_inline( time, delta, osc_output );
|
||||
time += period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
oscs [index].last_amp = (delta + volume) >> 1;
|
||||
phases [index] = (delta > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// maintain phase when silent
|
||||
int count = (end_time - time + period - 1) / period;
|
||||
phases [index] ^= count & 1;
|
||||
time += (long) count * period;
|
||||
}
|
||||
}
|
||||
|
||||
delays [index] = time - end_time;
|
||||
}
|
||||
|
||||
last_time = end_time;
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
|
||||
// Sunsoft FME-07 sound emulator
|
||||
|
||||
// Nes_Emu 0.5.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
|
||||
|
||||
#ifndef NES_FME07_APU_H
|
||||
#define NES_FME07_APU_H
|
||||
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
struct fme07_snapshot_t
|
||||
{
|
||||
enum { reg_count = 14 };
|
||||
BOOST::uint8_t regs [reg_count];
|
||||
BOOST::uint8_t phases [3]; // 0 or 1
|
||||
BOOST::uint8_t latch;
|
||||
BOOST::uint16_t delays [3]; // a, b, c
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (fme07_snapshot_t) == 24 );
|
||||
|
||||
class Nes_Fme07_Apu : private fme07_snapshot_t {
|
||||
public:
|
||||
Nes_Fme07_Apu();
|
||||
|
||||
// See Nes_Apu.h for reference
|
||||
void reset();
|
||||
void volume( double );
|
||||
void treble_eq( blip_eq_t const& );
|
||||
void output( Blip_Buffer* );
|
||||
enum { osc_count = 3 };
|
||||
void osc_output( int index, Blip_Buffer* );
|
||||
void end_frame( blip_time_t );
|
||||
void save_snapshot( fme07_snapshot_t* ) const;
|
||||
void load_snapshot( fme07_snapshot_t const& );
|
||||
|
||||
// Mask and addresses of registers
|
||||
enum { addr_mask = 0xe000 };
|
||||
enum { data_addr = 0xe000 };
|
||||
enum { latch_addr = 0xc000 };
|
||||
|
||||
// (addr & addr_mask) == latch_addr
|
||||
void write_latch( int );
|
||||
|
||||
// (addr & addr_mask) == data_addr
|
||||
void write_data( blip_time_t, int data );
|
||||
|
||||
// End of public interface
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Fme07_Apu( const Nes_Fme07_Apu& );
|
||||
Nes_Fme07_Apu& operator = ( const Nes_Fme07_Apu& );
|
||||
|
||||
static unsigned char amp_table [16];
|
||||
|
||||
struct {
|
||||
Blip_Buffer* output;
|
||||
int last_amp;
|
||||
} oscs [osc_count];
|
||||
blip_time_t last_time;
|
||||
|
||||
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
|
||||
Blip_Synth<blip_good_quality,1> synth;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
};
|
||||
|
||||
inline void Nes_Fme07_Apu::volume( double v )
|
||||
{
|
||||
synth.volume_unit( 0.38 / amp_range * v ); // to do: fine-tune
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::treble_eq( blip_eq_t const& eq )
|
||||
{
|
||||
synth.treble_eq( eq );
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::output( Blip_Buffer* buf )
|
||||
{
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
osc_output( i, buf );
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::osc_output( int i, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
oscs [i].output = buf;
|
||||
}
|
||||
|
||||
inline Nes_Fme07_Apu::Nes_Fme07_Apu()
|
||||
{
|
||||
output( NULL );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::write_latch( int data ) { latch = data; }
|
||||
|
||||
inline void Nes_Fme07_Apu::write_data( blip_time_t time, int data )
|
||||
{
|
||||
if ( (unsigned) latch >= reg_count )
|
||||
{
|
||||
#ifdef dprintf
|
||||
dprintf( "FME07 write to %02X (past end of sound registers)\n", (int) latch );
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
run_until( time );
|
||||
regs [latch] = data;
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::end_frame( blip_time_t time )
|
||||
{
|
||||
if ( time > last_time )
|
||||
run_until( time );
|
||||
last_time -= time;
|
||||
assert( last_time >= 0 );
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::save_snapshot( fme07_snapshot_t* out ) const
|
||||
{
|
||||
*out = *this;
|
||||
}
|
||||
|
||||
inline void Nes_Fme07_Apu::load_snapshot( fme07_snapshot_t const& in )
|
||||
{
|
||||
reset();
|
||||
fme07_snapshot_t* state = this;
|
||||
*state = in;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Fme7_Apu.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Nes_Fme7_Apu::reset()
|
||||
{
|
||||
last_time = 0;
|
||||
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
oscs [i].last_amp = 0;
|
||||
|
||||
fme7_apu_state_t* state = this;
|
||||
memset( state, 0, sizeof *state );
|
||||
}
|
||||
|
||||
unsigned char Nes_Fme7_Apu::amp_table [16] =
|
||||
{
|
||||
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
|
||||
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
|
||||
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
|
||||
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
|
||||
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
|
||||
#undef ENTRY
|
||||
};
|
||||
|
||||
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
|
||||
{
|
||||
require( end_time >= last_time );
|
||||
|
||||
for ( int index = 0; index < osc_count; index++ )
|
||||
{
|
||||
int mode = regs [7] >> index;
|
||||
int vol_mode = regs [010 + index];
|
||||
int volume = amp_table [vol_mode & 0x0f];
|
||||
|
||||
if ( !oscs [index].output )
|
||||
continue;
|
||||
|
||||
// check for unsupported mode
|
||||
#ifndef NDEBUG
|
||||
if ( (mode & 011) <= 001 && vol_mode & 0x1f )
|
||||
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
|
||||
mode, vol_mode & 0x1f );
|
||||
#endif
|
||||
|
||||
if ( (mode & 001) | (vol_mode & 0x10) )
|
||||
volume = 0; // noise and envelope aren't supported
|
||||
|
||||
// period
|
||||
int const period_factor = 16;
|
||||
unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor +
|
||||
regs [index * 2] * period_factor;
|
||||
if ( period < 50 ) // around 22 kHz
|
||||
{
|
||||
volume = 0;
|
||||
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
|
||||
period = period_factor;
|
||||
}
|
||||
|
||||
// current amplitude
|
||||
int amp = volume;
|
||||
if ( !phases [index] )
|
||||
amp = 0;
|
||||
int delta = amp - oscs [index].last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
oscs [index].last_amp = amp;
|
||||
synth.offset( last_time, delta, oscs [index].output );
|
||||
}
|
||||
|
||||
blip_time_t time = last_time + delays [index];
|
||||
if ( time < end_time )
|
||||
{
|
||||
Blip_Buffer* const osc_output = oscs [index].output;
|
||||
int delta = amp * 2 - volume;
|
||||
|
||||
if ( volume )
|
||||
{
|
||||
do
|
||||
{
|
||||
delta = -delta;
|
||||
synth.offset_inline( time, delta, osc_output );
|
||||
time += period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
oscs [index].last_amp = (delta + volume) >> 1;
|
||||
phases [index] = (delta > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// maintain phase when silent
|
||||
int count = (end_time - time + period - 1) / period;
|
||||
phases [index] ^= count & 1;
|
||||
time += (long) count * period;
|
||||
}
|
||||
}
|
||||
|
||||
delays [index] = time - end_time;
|
||||
}
|
||||
|
||||
last_time = end_time;
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
|
||||
// Sunsoft FME-7 sound emulator
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_FME7_APU_H
|
||||
#define NES_FME7_APU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
struct fme7_apu_state_t
|
||||
{
|
||||
enum { reg_count = 14 };
|
||||
BOOST::uint8_t regs [reg_count];
|
||||
BOOST::uint8_t phases [3]; // 0 or 1
|
||||
BOOST::uint8_t latch;
|
||||
BOOST::uint16_t delays [3]; // a, b, c
|
||||
};
|
||||
BOOST_STATIC_ASSERT( sizeof (fme7_apu_state_t) == 24 );
|
||||
|
||||
class Nes_Fme7_Apu : private fme7_apu_state_t {
|
||||
public:
|
||||
Nes_Fme7_Apu();
|
||||
|
||||
// See Nes_Apu.h for reference
|
||||
void reset();
|
||||
void volume( double );
|
||||
void treble_eq( blip_eq_t const& );
|
||||
void output( Blip_Buffer* );
|
||||
enum { osc_count = 3 };
|
||||
void osc_output( int index, Blip_Buffer* );
|
||||
void end_frame( blip_time_t );
|
||||
void save_state( fme7_apu_state_t* ) const;
|
||||
void load_state( fme7_apu_state_t const& );
|
||||
|
||||
// Mask and addresses of registers
|
||||
enum { addr_mask = 0xe000 };
|
||||
enum { data_addr = 0xe000 };
|
||||
enum { latch_addr = 0xc000 };
|
||||
|
||||
// (addr & addr_mask) == latch_addr
|
||||
void write_latch( int );
|
||||
|
||||
// (addr & addr_mask) == data_addr
|
||||
void write_data( blip_time_t, int data );
|
||||
|
||||
// End of public interface
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Fme7_Apu( const Nes_Fme7_Apu& );
|
||||
Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& );
|
||||
|
||||
static unsigned char amp_table [16];
|
||||
|
||||
struct {
|
||||
Blip_Buffer* output;
|
||||
int last_amp;
|
||||
} oscs [osc_count];
|
||||
blip_time_t last_time;
|
||||
|
||||
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
|
||||
Blip_Synth<blip_good_quality,1> synth;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
};
|
||||
|
||||
inline void Nes_Fme7_Apu::volume( double v )
|
||||
{
|
||||
synth.volume( 0.38 / amp_range * v ); // to do: fine-tune
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
|
||||
{
|
||||
synth.treble_eq( eq );
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
oscs [i].output = buf;
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::output( Blip_Buffer* buf )
|
||||
{
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
osc_output( i, buf );
|
||||
}
|
||||
|
||||
inline Nes_Fme7_Apu::Nes_Fme7_Apu()
|
||||
{
|
||||
output( NULL );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; }
|
||||
|
||||
inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
|
||||
{
|
||||
if ( (unsigned) latch >= reg_count )
|
||||
{
|
||||
#ifdef dprintf
|
||||
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
run_until( time );
|
||||
regs [latch] = data;
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
|
||||
{
|
||||
if ( time > last_time )
|
||||
run_until( time );
|
||||
|
||||
assert( last_time >= time );
|
||||
last_time -= time;
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
|
||||
{
|
||||
*out = *this;
|
||||
}
|
||||
|
||||
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
|
||||
{
|
||||
reset();
|
||||
fme7_apu_state_t* state = this;
|
||||
*state = in;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_Core.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Nes_Mapper::Nes_Mapper()
|
||||
{
|
||||
emu_ = NULL;
|
||||
static char c;
|
||||
state = &c; // TODO: state must not be null?
|
||||
state_size = 0;
|
||||
}
|
||||
|
||||
Nes_Mapper::~Nes_Mapper()
|
||||
{
|
||||
}
|
||||
|
||||
// Sets mirroring, maps first 8K CHR in, first and last 16K of PRG,
|
||||
// intercepts writes to upper half of memory, and clears registered state.
|
||||
void Nes_Mapper::default_reset_state()
|
||||
{
|
||||
int mirroring = cart_->mirroring();
|
||||
if ( mirroring & 8 )
|
||||
mirror_full();
|
||||
else if ( mirroring & 1 )
|
||||
mirror_vert();
|
||||
else
|
||||
mirror_horiz();
|
||||
|
||||
set_chr_bank( 0, bank_8k, 0 );
|
||||
|
||||
set_prg_bank( 0x8000, bank_16k, 0 );
|
||||
set_prg_bank( 0xC000, bank_16k, last_bank );
|
||||
|
||||
intercept_writes( 0x8000, 0x8000 );
|
||||
|
||||
memset( state, 0, state_size );
|
||||
}
|
||||
|
||||
void Nes_Mapper::reset()
|
||||
{
|
||||
default_reset_state();
|
||||
reset_state();
|
||||
apply_mapping();
|
||||
}
|
||||
|
||||
void mapper_state_t::write( const void* p, unsigned long s )
|
||||
{
|
||||
require( s <= max_mapper_state_size );
|
||||
require( !size );
|
||||
size = s;
|
||||
memcpy( data, p, s );
|
||||
}
|
||||
|
||||
int mapper_state_t::read( void* p, unsigned long s ) const
|
||||
{
|
||||
if ( (long) s > size )
|
||||
s = size;
|
||||
memcpy( p, data, s );
|
||||
return s;
|
||||
}
|
||||
|
||||
void Nes_Mapper::save_state( mapper_state_t& out )
|
||||
{
|
||||
out.write( state, state_size );
|
||||
}
|
||||
|
||||
void Nes_Mapper::load_state( mapper_state_t const& in )
|
||||
{
|
||||
default_reset_state();
|
||||
read_state( in );
|
||||
apply_mapping();
|
||||
}
|
||||
|
||||
void Nes_Mapper::read_state( mapper_state_t const& in )
|
||||
{
|
||||
memset( state, 0, state_size );
|
||||
in.read( state, state_size );
|
||||
apply_mapping();
|
||||
}
|
||||
|
||||
// Timing
|
||||
|
||||
void Nes_Mapper::irq_changed() { emu_->irq_changed(); }
|
||||
|
||||
nes_time_t Nes_Mapper::next_irq( nes_time_t ) { return no_irq; }
|
||||
|
||||
void Nes_Mapper::a12_clocked() { }
|
||||
|
||||
void Nes_Mapper::run_until( nes_time_t ) { }
|
||||
|
||||
void Nes_Mapper::end_frame( nes_time_t ) { }
|
||||
|
||||
bool Nes_Mapper::ppu_enabled() const { return emu().ppu.w2001 & 0x08; }
|
||||
|
||||
// Sound
|
||||
|
||||
int Nes_Mapper::channel_count() const { return 0; }
|
||||
|
||||
void Nes_Mapper::set_channel_buf( int, Blip_Buffer* ) { require( false ); }
|
||||
|
||||
void Nes_Mapper::set_treble( blip_eq_t const& ) { }
|
||||
|
||||
// Memory mapping
|
||||
|
||||
void Nes_Mapper::set_prg_bank( nes_addr_t addr, bank_size_t bs, int bank )
|
||||
{
|
||||
require( addr >= 0x2000 ); // can't remap low-memory
|
||||
|
||||
int bank_size = 1 << bs;
|
||||
require( addr % bank_size == 0 ); // must be aligned
|
||||
|
||||
int bank_count = cart_->prg_size() >> bs;
|
||||
if ( bank < 0 )
|
||||
bank += bank_count;
|
||||
|
||||
if ( bank >= bank_count )
|
||||
{
|
||||
check( !(cart_->prg_size() & (cart_->prg_size() - 1)) ); // ensure PRG size is power of 2
|
||||
bank %= bank_count;
|
||||
}
|
||||
|
||||
emu().map_code( addr, bank_size, cart_->prg() + (bank << bs) );
|
||||
|
||||
if ( unsigned (addr - 0x6000) < 0x2000 )
|
||||
emu().enable_prg_6000();
|
||||
}
|
||||
|
||||
void Nes_Mapper::set_chr_bank( nes_addr_t addr, bank_size_t bs, int bank )
|
||||
{
|
||||
emu().ppu.render_until( emu().clock() );
|
||||
emu().ppu.set_chr_bank( addr, 1 << bs, bank << bs );
|
||||
}
|
||||
|
||||
void Nes_Mapper::mirror_manual( int page0, int page1, int page2, int page3 )
|
||||
{
|
||||
emu().ppu.render_bg_until( emu().clock() );
|
||||
emu().ppu.set_nt_banks( page0, page1, page2, page3 );
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data )
|
||||
{
|
||||
if ( emu().Nes_Cpu::get_code( addr ) [0] != data )
|
||||
dprintf( "Mapper write had bus conflict\n" );
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Mapper registration
|
||||
|
||||
int const max_mappers = 32;
|
||||
Nes_Mapper::mapping_t Nes_Mapper::mappers [max_mappers] =
|
||||
{
|
||||
{ 0, Nes_Mapper::make_nrom },
|
||||
{ 1, Nes_Mapper::make_mmc1 },
|
||||
{ 2, Nes_Mapper::make_unrom },
|
||||
{ 3, Nes_Mapper::make_cnrom },
|
||||
{ 4, Nes_Mapper::make_mmc3 },
|
||||
{ 7, Nes_Mapper::make_aorom }
|
||||
};
|
||||
static int mapper_count = 6; // to do: keep synchronized with pre-supplied mappers above
|
||||
|
||||
Nes_Mapper::creator_func_t Nes_Mapper::get_mapper_creator( int code )
|
||||
{
|
||||
for ( int i = 0; i < mapper_count; i++ )
|
||||
{
|
||||
if ( mappers [i].code == code )
|
||||
return mappers [i].func;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Nes_Mapper::register_mapper( int code, creator_func_t func )
|
||||
{
|
||||
// Catch attempted registration of a different creation function for same mapper code
|
||||
require( !get_mapper_creator( code ) || get_mapper_creator( code ) == func );
|
||||
require( mapper_count < max_mappers ); // fixed liming on number of registered mappers
|
||||
|
||||
mapping_t& m = mappers [mapper_count++];
|
||||
m.code = code;
|
||||
m.func = func;
|
||||
}
|
||||
|
||||
Nes_Mapper* Nes_Mapper::create( Nes_Cart const* cart, Nes_Core* emu )
|
||||
{
|
||||
Nes_Mapper::creator_func_t func = get_mapper_creator( cart->mapper_code() );
|
||||
if ( !func )
|
||||
return NULL;
|
||||
|
||||
// to do: out of memory will be reported as unsupported mapper
|
||||
Nes_Mapper* mapper = func();
|
||||
if ( mapper )
|
||||
{
|
||||
mapper->cart_ = cart;
|
||||
mapper->emu_ = emu;
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
|
||||
// NES mapper interface
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_MAPPER
|
||||
#define NES_MAPPER
|
||||
|
||||
#include "Nes_Cart.h"
|
||||
#include "Nes_Cpu.h"
|
||||
#include "nes_data.h"
|
||||
#include "Nes_Core.h"
|
||||
class Blip_Buffer;
|
||||
class blip_eq_t;
|
||||
class Nes_Core;
|
||||
|
||||
class Nes_Mapper {
|
||||
public:
|
||||
// Register function that creates mapper for given code.
|
||||
typedef Nes_Mapper* (*creator_func_t)();
|
||||
static void register_mapper( int code, creator_func_t );
|
||||
|
||||
// Register optional mappers included with Nes_Emu
|
||||
void register_optional_mappers();
|
||||
|
||||
// Create mapper appropriate for cartridge. Returns NULL if it uses unsupported mapper.
|
||||
static Nes_Mapper* create( Nes_Cart const*, Nes_Core* );
|
||||
|
||||
virtual ~Nes_Mapper();
|
||||
|
||||
// Reset mapper to power-up state.
|
||||
virtual void reset();
|
||||
|
||||
// Save snapshot of mapper state. Default saves registered state.
|
||||
virtual void save_state( mapper_state_t& );
|
||||
|
||||
// Resets mapper, loads state, then applies it
|
||||
virtual void load_state( mapper_state_t const& );
|
||||
|
||||
// I/O
|
||||
|
||||
// Read from memory
|
||||
virtual int read( nes_time_t, nes_addr_t );
|
||||
|
||||
// Write to memory
|
||||
virtual void write( nes_time_t, nes_addr_t, int data ) = 0;
|
||||
|
||||
// Write to memory below 0x8000 (returns false if mapper didn't handle write)
|
||||
virtual bool write_intercepted( nes_time_t, nes_addr_t, int data );
|
||||
|
||||
// Timing
|
||||
|
||||
// Time returned when current mapper state won't ever cause an IRQ
|
||||
enum { no_irq = LONG_MAX / 2 };
|
||||
|
||||
// Time next IRQ will occur at
|
||||
virtual nes_time_t next_irq( nes_time_t present );
|
||||
|
||||
// Run mapper until given time
|
||||
virtual void run_until( nes_time_t );
|
||||
|
||||
// End video frame of given length
|
||||
virtual void end_frame( nes_time_t length );
|
||||
|
||||
// Sound
|
||||
|
||||
// Number of sound channels
|
||||
virtual int channel_count() const;
|
||||
|
||||
// Set sound buffer for channel to output to, or NULL to silence channel.
|
||||
virtual void set_channel_buf( int index, Blip_Buffer* );
|
||||
|
||||
// Set treble equalization
|
||||
virtual void set_treble( blip_eq_t const& );
|
||||
|
||||
// Misc
|
||||
|
||||
// Called when bit 12 of PPU's VRAM address changes from 0 to 1 due to
|
||||
// $2006 and $2007 accesses (but not due to PPU scanline rendering).
|
||||
virtual void a12_clocked();
|
||||
|
||||
protected:
|
||||
// Services provided for derived mapper classes
|
||||
Nes_Mapper();
|
||||
|
||||
// Register state data to automatically save and load. Be sure the binary
|
||||
// layout is suitable for use in a file, including any byte-order issues.
|
||||
// Automatically cleared to zero by default reset().
|
||||
void register_state( void*, unsigned );
|
||||
|
||||
// Enable 8K of RAM at 0x6000-0x7FFF, optionally read-only.
|
||||
void enable_sram( bool enabled = true, bool read_only = false );
|
||||
|
||||
// Cause CPU writes within given address range to call mapper's write() function.
|
||||
// Might map a larger address range, which the mapper can ignore and pass to
|
||||
// Nes_Mapper::write(). The range 0x8000-0xffff is always intercepted by the mapper.
|
||||
void intercept_writes( nes_addr_t addr, unsigned size );
|
||||
|
||||
// Cause CPU reads within given address range to call mapper's read() function.
|
||||
// Might map a larger address range, which the mapper can ignore and pass to
|
||||
// Nes_Mapper::read(). CPU opcode/operand reads and low-memory reads always
|
||||
// go directly to memory and cannot be intercepted.
|
||||
void intercept_reads( nes_addr_t addr, unsigned size );
|
||||
|
||||
// Bank sizes for mapping
|
||||
enum bank_size_t { // 1 << bank_Xk = X * 1024
|
||||
bank_1k = 10,
|
||||
bank_2k = 11,
|
||||
bank_4k = 12,
|
||||
bank_8k = 13,
|
||||
bank_16k = 14,
|
||||
bank_32k = 15
|
||||
};
|
||||
|
||||
// Index of last PRG/CHR bank. Last_bank selects last bank, last_bank - 1
|
||||
// selects next-to-last bank, etc.
|
||||
enum { last_bank = -1 };
|
||||
|
||||
// Map 'size' bytes from 'PRG + bank * size' to CPU address space starting at 'addr'
|
||||
void set_prg_bank( nes_addr_t addr, bank_size_t size, int bank );
|
||||
|
||||
// Map 'size' bytes from 'CHR + bank * size' to PPU address space starting at 'addr'
|
||||
void set_chr_bank( nes_addr_t addr, bank_size_t size, int bank );
|
||||
|
||||
// Set PPU mirroring. All mappings implemented using mirror_manual().
|
||||
void mirror_manual( int page0, int page1, int page2, int page3 );
|
||||
void mirror_single( int page );
|
||||
void mirror_horiz( int page = 0 );
|
||||
void mirror_vert( int page = 0 );
|
||||
void mirror_full();
|
||||
|
||||
// True if PPU rendering is enabled. Some mappers watch PPU memory accesses to determine
|
||||
// when scanlines occur, and can only do this when rendering is enabled.
|
||||
bool ppu_enabled() const;
|
||||
|
||||
// Cartridge being emulated
|
||||
Nes_Cart const& cart() const { return *cart_; }
|
||||
|
||||
// Must be called when next_irq()'s return value is earlier than previous,
|
||||
// current CPU run can be stopped earlier. Best to call whenever time may
|
||||
// have changed (no performance impact if called even when time didn't change).
|
||||
void irq_changed();
|
||||
|
||||
// Handle data written to mapper that doesn't handle bus conflict arising due to
|
||||
// PRG also reading data. Returns data that mapper should act as if were
|
||||
// written. Currently always returns 'data' and just checks that data written is
|
||||
// the same as byte in PRG at same address and writes debug message if it doesn't.
|
||||
int handle_bus_conflict( nes_addr_t addr, int data );
|
||||
|
||||
// Reference to emulator that uses this mapper.
|
||||
Nes_Core& emu() const { return *emu_; }
|
||||
|
||||
protected:
|
||||
// Services derived classes provide
|
||||
|
||||
// Read state from snapshot. Default reads data into registered state, then calls
|
||||
// apply_mapping().
|
||||
virtual void read_state( mapper_state_t const& );
|
||||
|
||||
// Apply current mapping state to hardware. Called after reading mapper state
|
||||
// from a snapshot.
|
||||
virtual void apply_mapping() = 0;
|
||||
|
||||
// Called by default reset() before apply_mapping() is called.
|
||||
virtual void reset_state() { }
|
||||
|
||||
// End of general interface
|
||||
private:
|
||||
Nes_Core* emu_;
|
||||
void* state;
|
||||
unsigned state_size;
|
||||
Nes_Cart const* cart_;
|
||||
|
||||
void default_reset_state();
|
||||
|
||||
struct mapping_t {
|
||||
int code;
|
||||
Nes_Mapper::creator_func_t func;
|
||||
};
|
||||
static mapping_t mappers [];
|
||||
static creator_func_t get_mapper_creator( int code );
|
||||
|
||||
// built-in mappers
|
||||
static Nes_Mapper* make_nrom();
|
||||
static Nes_Mapper* make_unrom();
|
||||
static Nes_Mapper* make_aorom();
|
||||
static Nes_Mapper* make_cnrom();
|
||||
static Nes_Mapper* make_mmc1();
|
||||
static Nes_Mapper* make_mmc3();
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct register_mapper {
|
||||
/*void*/ register_mapper( int code ) { Nes_Mapper::register_mapper( code, create ); }
|
||||
static Nes_Mapper* create() { return BLARGG_NEW T; }
|
||||
};
|
||||
|
||||
#ifdef NDEBUG
|
||||
inline int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) { return data; }
|
||||
#endif
|
||||
|
||||
inline void Nes_Mapper::mirror_horiz( int p ) { mirror_manual( p, p, p ^ 1, p ^ 1 ); }
|
||||
inline void Nes_Mapper::mirror_vert( int p ) { mirror_manual( p, p ^ 1, p, p ^ 1 ); }
|
||||
inline void Nes_Mapper::mirror_single( int p ) { mirror_manual( p, p, p, p ); }
|
||||
inline void Nes_Mapper::mirror_full() { mirror_manual( 0, 1, 2, 3 ); }
|
||||
|
||||
inline void Nes_Mapper::register_state( void* p, unsigned s )
|
||||
{
|
||||
assert( s <= max_mapper_state_size );
|
||||
state = p;
|
||||
state_size = s;
|
||||
}
|
||||
|
||||
inline bool Nes_Mapper::write_intercepted( nes_time_t, nes_addr_t, int ) { return false; }
|
||||
|
||||
inline int Nes_Mapper::read( nes_time_t, nes_addr_t ) { return -1; } // signal to caller
|
||||
|
||||
inline void Nes_Mapper::intercept_reads( nes_addr_t addr, unsigned size )
|
||||
{
|
||||
emu().add_mapper_intercept( addr, size, true, false );
|
||||
}
|
||||
|
||||
inline void Nes_Mapper::intercept_writes( nes_addr_t addr, unsigned size )
|
||||
{
|
||||
emu().add_mapper_intercept( addr, size, false, true );
|
||||
}
|
||||
|
||||
inline void Nes_Mapper::enable_sram( bool enabled, bool read_only )
|
||||
{
|
||||
emu_->enable_sram( enabled, read_only );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
class Mapper_Mmc1 : public Nes_Mapper, mmc1_state_t {
|
||||
public:
|
||||
Mapper_Mmc1()
|
||||
{
|
||||
mmc1_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
regs [0] = 0x0f;
|
||||
regs [1] = 0x00;
|
||||
regs [2] = 0x01;
|
||||
regs [3] = 0x00;
|
||||
}
|
||||
|
||||
void register_changed( int );
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
enable_sram(); // early MMC1 always had SRAM enabled
|
||||
register_changed( 0 );
|
||||
}
|
||||
|
||||
virtual void write( nes_time_t, nes_addr_t addr, int data )
|
||||
{
|
||||
if ( !(data & 0x80) )
|
||||
{
|
||||
buf |= (data & 1) << bit;
|
||||
bit++;
|
||||
|
||||
if ( bit >= 5 )
|
||||
{
|
||||
int reg = addr >> 13 & 3;
|
||||
regs [reg] = buf & 0x1f;
|
||||
|
||||
bit = 0;
|
||||
buf = 0;
|
||||
|
||||
register_changed( reg );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bit = 0;
|
||||
buf = 0;
|
||||
regs [0] |= 0x0c;
|
||||
register_changed( 0 );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Mapper_Mmc1::register_changed( int reg )
|
||||
{
|
||||
// Mirroring
|
||||
if ( reg == 0 )
|
||||
{
|
||||
int mode = regs [0] & 3;
|
||||
if ( mode < 2 )
|
||||
mirror_single( mode & 1 );
|
||||
else if ( mode == 2 )
|
||||
mirror_vert();
|
||||
else
|
||||
mirror_horiz();
|
||||
}
|
||||
|
||||
// CHR
|
||||
if ( reg < 3 && cart().chr_size() > 0 )
|
||||
{
|
||||
if ( regs [0] & 0x10 )
|
||||
{
|
||||
set_chr_bank( 0x0000, bank_4k, regs [1] );
|
||||
set_chr_bank( 0x1000, bank_4k, regs [2] );
|
||||
}
|
||||
else
|
||||
{
|
||||
set_chr_bank( 0, bank_8k, regs [1] >> 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// PRG
|
||||
int bank = (regs [1] & 0x10) | (regs [3] & 0x0f);
|
||||
if ( !(regs [0] & 0x08) )
|
||||
{
|
||||
set_prg_bank( 0x8000, bank_32k, bank >> 1 );
|
||||
}
|
||||
else if ( regs [0] & 0x04 )
|
||||
{
|
||||
set_prg_bank( 0x8000, bank_16k, bank );
|
||||
set_prg_bank( 0xC000, bank_16k, bank | 0x0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
set_prg_bank( 0x8000, bank_16k, bank & ~0x0f );
|
||||
set_prg_bank( 0xC000, bank_16k, bank );
|
||||
}
|
||||
}
|
||||
|
||||
Nes_Mapper* Nes_Mapper::make_mmc1()
|
||||
{
|
||||
return BLARGG_NEW Mapper_Mmc1;
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Mapper.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_Core.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// 264 or less breaks Gargoyle's Quest II
|
||||
// 267 or less breaks Magician
|
||||
int const irq_fine_tune = 268;
|
||||
nes_time_t const first_scanline = 20 * Nes_Ppu::scanline_len + irq_fine_tune;
|
||||
nes_time_t const last_scanline = first_scanline + 240 * Nes_Ppu::scanline_len;
|
||||
|
||||
class Mapper_Mmc3 : public Nes_Mapper, mmc3_state_t {
|
||||
nes_time_t next_time;
|
||||
int counter_just_clocked; // used only for debugging
|
||||
public:
|
||||
Mapper_Mmc3()
|
||||
{
|
||||
mmc3_state_t* state = this;
|
||||
register_state( state, sizeof *state );
|
||||
}
|
||||
|
||||
virtual void reset_state()
|
||||
{
|
||||
memcpy( banks, "\0\2\4\5\6\7\0\1", sizeof banks );
|
||||
|
||||
counter_just_clocked = 0;
|
||||
next_time = 0;
|
||||
mirror = 1;
|
||||
if ( cart().mirroring() & 1 )
|
||||
{
|
||||
mirror = 0;
|
||||
//dprintf( "cart specified vertical mirroring\n" );
|
||||
}
|
||||
}
|
||||
|
||||
void update_chr_banks();
|
||||
void update_prg_banks();
|
||||
void write_irq( nes_addr_t addr, int data );
|
||||
void write( nes_time_t, nes_addr_t, int );
|
||||
|
||||
void start_frame() { next_time = first_scanline; }
|
||||
|
||||
virtual void apply_mapping()
|
||||
{
|
||||
write( 0, 0xA000, mirror );
|
||||
write( 0, 0xA001, sram_mode );
|
||||
update_chr_banks();
|
||||
update_prg_banks();
|
||||
start_frame();
|
||||
}
|
||||
|
||||
void clock_counter()
|
||||
{
|
||||
if ( counter_just_clocked )
|
||||
counter_just_clocked--;
|
||||
|
||||
if ( !irq_ctr-- )
|
||||
{
|
||||
irq_ctr = irq_latch;
|
||||
//if ( !irq_latch )
|
||||
//dprintf( "MMC3 IRQ counter reloaded with 0\n" );
|
||||
}
|
||||
|
||||
//dprintf( "%6d MMC3 IRQ clocked\n", time / ppu_overclock );
|
||||
if ( irq_ctr == 0 )
|
||||
{
|
||||
//if ( irq_enabled && !irq_flag )
|
||||
//dprintf( "%6d MMC3 IRQ triggered: %f\n", time / ppu_overclock, time / scanline_len.0 - 20 );
|
||||
irq_flag = irq_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void run_until( nes_time_t );
|
||||
|
||||
virtual void a12_clocked()
|
||||
{
|
||||
clock_counter();
|
||||
if ( irq_enabled )
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
virtual void end_frame( nes_time_t end_time )
|
||||
{
|
||||
run_until( end_time );
|
||||
start_frame();
|
||||
}
|
||||
|
||||
virtual nes_time_t next_irq( nes_time_t present )
|
||||
{
|
||||
run_until( present );
|
||||
|
||||
if ( !irq_enabled )
|
||||
return no_irq;
|
||||
|
||||
if ( irq_flag )
|
||||
return 0;
|
||||
|
||||
if ( !ppu_enabled() )
|
||||
return no_irq;
|
||||
|
||||
int remain = irq_ctr - 1;
|
||||
if ( remain < 0 )
|
||||
remain = irq_latch;
|
||||
|
||||
assert( remain >= 0 );
|
||||
|
||||
long time = remain * 341L + next_time;
|
||||
if ( time > last_scanline )
|
||||
return no_irq;
|
||||
|
||||
return time / ppu_overclock + 1;
|
||||
}
|
||||
};
|
||||
|
||||
void Mapper_Mmc3::run_until( nes_time_t end_time )
|
||||
{
|
||||
bool bg_enabled = ppu_enabled();
|
||||
|
||||
end_time *= ppu_overclock;
|
||||
while ( next_time < end_time && next_time <= last_scanline )
|
||||
{
|
||||
if ( bg_enabled )
|
||||
clock_counter();
|
||||
next_time += Nes_Ppu::scanline_len;
|
||||
}
|
||||
}
|
||||
|
||||
void Mapper_Mmc3::update_chr_banks()
|
||||
{
|
||||
int chr_xor = (mode >> 7 & 1) * 0x1000;
|
||||
set_chr_bank( 0x0000 ^ chr_xor, bank_2k, banks [0] >> 1 );
|
||||
set_chr_bank( 0x0800 ^ chr_xor, bank_2k, banks [1] >> 1 );
|
||||
set_chr_bank( 0x1000 ^ chr_xor, bank_1k, banks [2] );
|
||||
set_chr_bank( 0x1400 ^ chr_xor, bank_1k, banks [3] );
|
||||
set_chr_bank( 0x1800 ^ chr_xor, bank_1k, banks [4] );
|
||||
set_chr_bank( 0x1c00 ^ chr_xor, bank_1k, banks [5] );
|
||||
}
|
||||
|
||||
void Mapper_Mmc3::update_prg_banks()
|
||||
{
|
||||
set_prg_bank( 0xA000, bank_8k, banks [7] );
|
||||
nes_addr_t addr = 0x8000 + 0x4000 * (mode >> 6 & 1);
|
||||
set_prg_bank( addr, bank_8k, banks [6] );
|
||||
set_prg_bank( addr ^ 0x4000, bank_8k, last_bank - 1 );
|
||||
}
|
||||
|
||||
void Mapper_Mmc3::write_irq( nes_addr_t addr, int data )
|
||||
{
|
||||
switch ( addr & 0xE001 )
|
||||
{
|
||||
case 0xC000:
|
||||
irq_latch = data;
|
||||
break;
|
||||
|
||||
case 0xC001:
|
||||
if ( counter_just_clocked == 1 )
|
||||
dprintf( "MMC3 IRQ counter pathological behavior triggered\n" );
|
||||
counter_just_clocked = 2;
|
||||
irq_ctr = 0;
|
||||
break;
|
||||
|
||||
case 0xE000:
|
||||
irq_flag = false;
|
||||
irq_enabled = false;
|
||||
break;
|
||||
|
||||
case 0xE001:
|
||||
irq_enabled = true;
|
||||
break;
|
||||
}
|
||||
if ( irq_enabled )
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
void Mapper_Mmc3::write( nes_time_t time, nes_addr_t addr, int data )
|
||||
{
|
||||
check( !(addr & ~0xe001) ); // writes to mirrored registers are rare
|
||||
//dprintf( "%6d %02X->%04X\n", time, data, addr );
|
||||
|
||||
switch ( addr & 0xE001 )
|
||||
{
|
||||
case 0x8000: {
|
||||
int changed = mode ^ data;
|
||||
mode = data;
|
||||
// avoid unnecessary bank updates
|
||||
if ( changed & 0x80 )
|
||||
update_chr_banks();
|
||||
if ( changed & 0x40 )
|
||||
update_prg_banks();
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x8001: {
|
||||
int bank = mode & 7;
|
||||
banks [bank] = data;
|
||||
if ( bank < 6 )
|
||||
update_chr_banks();
|
||||
else
|
||||
update_prg_banks();
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xA000:
|
||||
mirror = data;
|
||||
if ( !(cart().mirroring() & 0x08) )
|
||||
{
|
||||
if ( mirror & 1 )
|
||||
mirror_horiz();
|
||||
else
|
||||
mirror_vert();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xA001:
|
||||
sram_mode = data;
|
||||
//dprintf( "%02X->%04X\n", data, addr );
|
||||
|
||||
// Startropics 1 & 2 use MMC6 and always enable low 512 bytes of SRAM
|
||||
if ( (data & 0x3F) == 0x30 )
|
||||
enable_sram( true );
|
||||
else
|
||||
enable_sram( data & 0x80, data & 0x40 );
|
||||
break;
|
||||
|
||||
default:
|
||||
run_until( time );
|
||||
write_irq( addr, data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Nes_Mapper* Nes_Mapper::make_mmc3()
|
||||
{
|
||||
return BLARGG_NEW Mapper_Mmc3;
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
|
||||
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Namco_Apu.h"
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Nes_Namco_Apu::Nes_Namco_Apu()
|
||||
{
|
||||
output( NULL );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
}
|
||||
|
||||
Nes_Namco_Apu::~Nes_Namco_Apu()
|
||||
{
|
||||
}
|
||||
|
||||
void Nes_Namco_Apu::reset()
|
||||
{
|
||||
last_time = 0;
|
||||
addr_reg = 0;
|
||||
|
||||
int i;
|
||||
for ( i = 0; i < reg_count; i++ )
|
||||
reg [i] = 0;
|
||||
|
||||
for ( i = 0; i < osc_count; i++ )
|
||||
{
|
||||
Namco_Osc& osc = oscs [i];
|
||||
osc.delay = 0;
|
||||
osc.last_amp = 0;
|
||||
osc.wave_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Namco_Apu::output( Blip_Buffer* buf )
|
||||
{
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
osc_output( i, buf );
|
||||
}
|
||||
|
||||
/*
|
||||
void Nes_Namco_Apu::reflect_state( Tagged_Data& data )
|
||||
{
|
||||
reflect_int16( data, 'ADDR', &addr_reg );
|
||||
|
||||
static const char hex [17] = "0123456789ABCDEF";
|
||||
int i;
|
||||
for ( i = 0; i < reg_count; i++ )
|
||||
reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] );
|
||||
|
||||
for ( i = 0; i < osc_count; i++ )
|
||||
{
|
||||
reflect_int32( data, 'DLY0' + i, &oscs [i].delay );
|
||||
reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos );
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void Nes_Namco_Apu::end_frame( nes_time_t time )
|
||||
{
|
||||
if ( time > last_time )
|
||||
run_until( time );
|
||||
|
||||
assert( last_time >= time );
|
||||
last_time -= time;
|
||||
}
|
||||
|
||||
void Nes_Namco_Apu::run_until( nes_time_t nes_end_time )
|
||||
{
|
||||
int active_oscs = (reg [0x7F] >> 4 & 7) + 1;
|
||||
for ( int i = osc_count - active_oscs; i < osc_count; i++ )
|
||||
{
|
||||
Namco_Osc& osc = oscs [i];
|
||||
Blip_Buffer* output = osc.output;
|
||||
if ( !output )
|
||||
continue;
|
||||
|
||||
blip_resampled_time_t time =
|
||||
output->resampled_time( last_time ) + osc.delay;
|
||||
blip_resampled_time_t end_time = output->resampled_time( nes_end_time );
|
||||
osc.delay = 0;
|
||||
if ( time < end_time )
|
||||
{
|
||||
const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40];
|
||||
if ( !(osc_reg [4] & 0xE0) )
|
||||
continue;
|
||||
|
||||
int volume = osc_reg [7] & 15;
|
||||
if ( !volume )
|
||||
continue;
|
||||
|
||||
long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0];
|
||||
if ( freq < 64 * active_oscs )
|
||||
continue; // prevent low frequencies from excessively delaying freq changes
|
||||
blip_resampled_time_t period =
|
||||
output->resampled_duration( 983040 ) / freq * active_oscs;
|
||||
|
||||
int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4;
|
||||
if ( !wave_size )
|
||||
continue;
|
||||
|
||||
int last_amp = osc.last_amp;
|
||||
int wave_pos = osc.wave_pos;
|
||||
|
||||
do
|
||||
{
|
||||
// read wave sample
|
||||
int addr = wave_pos + osc_reg [6];
|
||||
int sample = reg [addr >> 1] >> (addr << 2 & 4);
|
||||
wave_pos++;
|
||||
sample = (sample & 15) * volume;
|
||||
|
||||
// output impulse if amplitude changed
|
||||
int delta = sample - last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
last_amp = sample;
|
||||
synth.offset_resampled( time, delta, output );
|
||||
}
|
||||
|
||||
// next sample
|
||||
time += period;
|
||||
if ( wave_pos >= wave_size )
|
||||
wave_pos = 0;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
osc.wave_pos = wave_pos;
|
||||
osc.last_amp = last_amp;
|
||||
}
|
||||
osc.delay = time - end_time;
|
||||
}
|
||||
|
||||
last_time = nes_end_time;
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
|
||||
// Namco 106 sound chip emulator
|
||||
|
||||
// Nes_Snd_Emu 0.1.7
|
||||
|
||||
#ifndef NES_NAMCO_APU_H
|
||||
#define NES_NAMCO_APU_H
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
struct namco_state_t;
|
||||
|
||||
class Nes_Namco_Apu {
|
||||
public:
|
||||
Nes_Namco_Apu();
|
||||
~Nes_Namco_Apu();
|
||||
|
||||
// See Nes_Apu.h for reference.
|
||||
void volume( double );
|
||||
void treble_eq( const blip_eq_t& );
|
||||
void output( Blip_Buffer* );
|
||||
enum { osc_count = 8 };
|
||||
void osc_output( int index, Blip_Buffer* );
|
||||
void reset();
|
||||
void end_frame( nes_time_t );
|
||||
|
||||
// Read/write data register is at 0x4800
|
||||
enum { data_reg_addr = 0x4800 };
|
||||
void write_data( nes_time_t, int );
|
||||
int read_data();
|
||||
|
||||
// Write-only address register is at 0xF800
|
||||
enum { addr_reg_addr = 0xF800 };
|
||||
void write_addr( int );
|
||||
|
||||
// to do: implement save/restore
|
||||
void save_state( namco_state_t* out ) const;
|
||||
void load_state( namco_state_t const& );
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Namco_Apu( const Nes_Namco_Apu& );
|
||||
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
|
||||
|
||||
struct Namco_Osc {
|
||||
long delay;
|
||||
Blip_Buffer* output;
|
||||
short last_amp;
|
||||
short wave_pos;
|
||||
};
|
||||
|
||||
Namco_Osc oscs [osc_count];
|
||||
|
||||
nes_time_t last_time;
|
||||
int addr_reg;
|
||||
|
||||
enum { reg_count = 0x80 };
|
||||
BOOST::uint8_t reg [reg_count];
|
||||
Blip_Synth<blip_good_quality,15> synth;
|
||||
|
||||
BOOST::uint8_t& access();
|
||||
void run_until( nes_time_t );
|
||||
};
|
||||
/*
|
||||
struct namco_state_t
|
||||
{
|
||||
BOOST::uint8_t regs [0x80];
|
||||
BOOST::uint8_t addr;
|
||||
BOOST::uint8_t unused;
|
||||
BOOST::uint8_t positions [8];
|
||||
BOOST::uint32_t delays [8];
|
||||
};
|
||||
*/
|
||||
|
||||
inline BOOST::uint8_t& Nes_Namco_Apu::access()
|
||||
{
|
||||
int addr = addr_reg & 0x7f;
|
||||
if ( addr_reg & 0x80 )
|
||||
addr_reg = (addr + 1) | 0x80;
|
||||
return reg [addr];
|
||||
}
|
||||
|
||||
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); }
|
||||
|
||||
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
|
||||
|
||||
inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
|
||||
|
||||
inline int Nes_Namco_Apu::read_data() { return access(); }
|
||||
|
||||
inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
oscs [i].output = buf;
|
||||
}
|
||||
|
||||
inline void Nes_Namco_Apu::write_data( nes_time_t time, int data )
|
||||
{
|
||||
run_until( time );
|
||||
access() = data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
|
||||
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
|
||||
|
||||
#include "Nes_Nonlinearizer.h"
|
||||
|
||||
/* Library Copyright (C) 2003-2005 Shay Green. This library is free software;
|
||||
you can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
Nes_Nonlinearizer::Nes_Nonlinearizer() : Multi_Buffer( 1 )
|
||||
{
|
||||
enable_nonlinearity( true );
|
||||
}
|
||||
|
||||
Nes_Nonlinearizer::~Nes_Nonlinearizer()
|
||||
{
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Nonlinearizer::sample_rate( long rate, int msec )
|
||||
{
|
||||
BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) );
|
||||
BLARGG_RETURN_ERR( tnd.sample_rate( rate, msec ) );
|
||||
return Multi_Buffer::sample_rate( buf.sample_rate(), buf.length() );
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::clock_rate( long rate )
|
||||
{
|
||||
buf.clock_rate( rate );
|
||||
tnd.clock_rate( rate );
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::bass_freq( int freq )
|
||||
{
|
||||
buf.bass_freq( freq );
|
||||
tnd.bass_freq( freq );
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::clear()
|
||||
{
|
||||
accum = 0x8000;
|
||||
buf.clear();
|
||||
tnd.clear();
|
||||
}
|
||||
|
||||
Nes_Nonlinearizer::channel_t Nes_Nonlinearizer::channel( int i )
|
||||
{
|
||||
channel_t c;
|
||||
c.center = (2 <= i && i <= 4) ? &tnd : &buf;
|
||||
c.left = c.center;
|
||||
c.right = c.center;
|
||||
return c;
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::end_frame( blip_time_t length, bool )
|
||||
{
|
||||
buf.end_frame( length );
|
||||
tnd.end_frame( length );
|
||||
}
|
||||
|
||||
long Nes_Nonlinearizer::samples_avail() const
|
||||
{
|
||||
return buf.samples_avail();
|
||||
}
|
||||
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
|
||||
void Nes_Nonlinearizer::enable_nonlinearity( bool b )
|
||||
{
|
||||
require( b ); // to do: implement non-linear output
|
||||
double gain = 0x7fff * 0.742467605 * 1.2;
|
||||
for ( int i = 0; i < half * 2; i++ )
|
||||
{
|
||||
int out = i << shift;
|
||||
if ( i > half )
|
||||
{
|
||||
double n = 202.0 / (half - 1) * (i - half);
|
||||
double d = 163.67 / (24329.0 / n + 100);
|
||||
out = int (d * gain) + 0x8000;
|
||||
}
|
||||
table [i] = out;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Nonlinearizer::make_nonlinear( long count )
|
||||
{
|
||||
const int zero_offset = 0x7f7f; // to do: use private constant from Blip_Buffer.h
|
||||
|
||||
#define ENTRY( s ) (table [((s) >> shift) & entry_mask])
|
||||
|
||||
BOOST::uint16_t* p = tnd.buffer_;
|
||||
unsigned prev = ENTRY( accum );
|
||||
long accum = this->accum;
|
||||
|
||||
for ( unsigned n = count; n--; )
|
||||
{
|
||||
accum += (long) *p - zero_offset;
|
||||
if ( (accum >> shift) >= half * 2 )
|
||||
{
|
||||
// to do: extend table to handle overflow better
|
||||
check( false ); // overflowed
|
||||
accum = (half * 2 - 1) << shift;
|
||||
}
|
||||
unsigned entry = ENTRY( accum );
|
||||
*p++ = entry - prev + zero_offset;
|
||||
prev = entry;
|
||||
}
|
||||
|
||||
this->accum = accum;
|
||||
}
|
||||
|
||||
long Nes_Nonlinearizer::read_samples( blip_sample_t* out, long count )
|
||||
{
|
||||
long avail = buf.samples_avail();
|
||||
assert( tnd.samples_avail() == avail );
|
||||
if ( count > avail )
|
||||
count = avail;
|
||||
|
||||
if ( count )
|
||||
{
|
||||
make_nonlinear( count );
|
||||
|
||||
Blip_Reader lin;
|
||||
Blip_Reader nonlin;
|
||||
|
||||
int lin_bass = lin.begin( buf );
|
||||
int nonlin_bass = nonlin.begin( tnd );
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
int s = lin.read() + nonlin.read();
|
||||
lin.next( lin_bass );
|
||||
nonlin.next( nonlin_bass );
|
||||
*out++ = s;
|
||||
|
||||
if ( (BOOST::int16_t) s != s )
|
||||
out [-1] = 0x7FFF - (s >> 24);
|
||||
}
|
||||
|
||||
lin.end( buf );
|
||||
nonlin.end( tnd );
|
||||
|
||||
buf.remove_samples( count );
|
||||
tnd.remove_samples( count );
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
// NES non-linear audio output
|
||||
|
||||
// Nes_Emu 0.5.0. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
|
||||
|
||||
#ifndef NES_NONLINEARIZER_H
|
||||
#define NES_NONLINEARIZER_H
|
||||
|
||||
#include "Multi_Buffer.h"
|
||||
|
||||
class Nes_Nonlinearizer : public Multi_Buffer {
|
||||
public:
|
||||
Nes_Nonlinearizer();
|
||||
~Nes_Nonlinearizer();
|
||||
|
||||
// Enable non-linear output
|
||||
void enable_nonlinearity( bool = true );
|
||||
|
||||
// See Multi_Buffer.h
|
||||
blargg_err_t sample_rate( long rate, int msec = blip_default_length );
|
||||
Multi_Buffer::sample_rate;
|
||||
void clock_rate( long );
|
||||
void bass_freq( int );
|
||||
void clear();
|
||||
channel_t channel( int );
|
||||
void end_frame( blip_time_t, bool unused = true );
|
||||
long samples_avail() const;
|
||||
long read_samples( blip_sample_t*, long );
|
||||
|
||||
// End of public interface
|
||||
private:
|
||||
enum { shift = 5 };
|
||||
enum { half = 0x8000 >> shift };
|
||||
enum { entry_mask = half * 2 - 1 };
|
||||
Blip_Buffer buf;
|
||||
Blip_Buffer tnd;
|
||||
long accum;
|
||||
BOOST::uint16_t table [half * 2];
|
||||
|
||||
void make_nonlinear( long );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,545 @@
|
|||
|
||||
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
// Nes_Osc
|
||||
|
||||
void Nes_Osc::clock_length( int halt_mask )
|
||||
{
|
||||
if ( length_counter && !(regs [0] & halt_mask) )
|
||||
length_counter--;
|
||||
}
|
||||
|
||||
void Nes_Envelope::clock_envelope()
|
||||
{
|
||||
int period = regs [0] & 15;
|
||||
if ( reg_written [3] ) {
|
||||
reg_written [3] = false;
|
||||
env_delay = period;
|
||||
envelope = 15;
|
||||
}
|
||||
else if ( --env_delay < 0 ) {
|
||||
env_delay = period;
|
||||
if ( envelope | (regs [0] & 0x20) )
|
||||
envelope = (envelope - 1) & 15;
|
||||
}
|
||||
}
|
||||
|
||||
int Nes_Envelope::volume() const
|
||||
{
|
||||
return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope;
|
||||
}
|
||||
|
||||
// Nes_Square
|
||||
|
||||
void Nes_Square::clock_sweep( int negative_adjust )
|
||||
{
|
||||
int sweep = regs [1];
|
||||
|
||||
if ( --sweep_delay < 0 )
|
||||
{
|
||||
reg_written [1] = true;
|
||||
|
||||
int period = this->period();
|
||||
int shift = sweep & shift_mask;
|
||||
if ( shift && (sweep & 0x80) && period >= 8 )
|
||||
{
|
||||
int offset = period >> shift;
|
||||
|
||||
if ( sweep & negate_flag )
|
||||
offset = negative_adjust - offset;
|
||||
|
||||
if ( period + offset < 0x800 )
|
||||
{
|
||||
period += offset;
|
||||
// rewrite period
|
||||
regs [2] = period & 0xff;
|
||||
regs [3] = (regs [3] & ~7) | ((period >> 8) & 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( reg_written [1] ) {
|
||||
reg_written [1] = false;
|
||||
sweep_delay = (sweep >> 4) & 7;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: clean up
|
||||
inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||
nes_time_t timer_period )
|
||||
{
|
||||
long remain = end_time - time;
|
||||
if ( remain > 0 )
|
||||
{
|
||||
int count = (remain + timer_period - 1) / timer_period;
|
||||
phase = (phase + count) & (phase_range - 1);
|
||||
time += (long) count * timer_period;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
void Nes_Square::run( nes_time_t time, nes_time_t end_time )
|
||||
{
|
||||
const int period = this->period();
|
||||
const int timer_period = (period + 1) * 2;
|
||||
|
||||
if ( !output )
|
||||
{
|
||||
delay = maintain_phase( time + delay, end_time, timer_period ) - end_time;
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = period >> (regs [1] & shift_mask);
|
||||
if ( regs [1] & negate_flag )
|
||||
offset = 0;
|
||||
|
||||
const int volume = this->volume();
|
||||
if ( volume == 0 || period < 8 || (period + offset) >= 0x800 )
|
||||
{
|
||||
if ( last_amp ) {
|
||||
synth.offset( time, -last_amp, output );
|
||||
last_amp = 0;
|
||||
}
|
||||
|
||||
time += delay;
|
||||
time = maintain_phase( time, end_time, timer_period );
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle duty select
|
||||
int duty_select = (regs [0] >> 6) & 3;
|
||||
int duty = 1 << duty_select; // 1, 2, 4, 2
|
||||
int amp = 0;
|
||||
if ( duty_select == 3 ) {
|
||||
duty = 2; // negated 25%
|
||||
amp = volume;
|
||||
}
|
||||
if ( phase < duty )
|
||||
amp ^= volume;
|
||||
|
||||
int delta = update_amp( amp );
|
||||
if ( delta )
|
||||
synth.offset( time, delta, output );
|
||||
|
||||
time += delay;
|
||||
if ( time < end_time )
|
||||
{
|
||||
Blip_Buffer* const output = this->output;
|
||||
const Synth& synth = this->synth;
|
||||
int delta = amp * 2 - volume;
|
||||
int phase = this->phase;
|
||||
|
||||
do {
|
||||
phase = (phase + 1) & (phase_range - 1);
|
||||
if ( phase == 0 || phase == duty ) {
|
||||
delta = -delta;
|
||||
synth.offset_inline( time, delta, output );
|
||||
}
|
||||
time += timer_period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
last_amp = (delta + volume) >> 1;
|
||||
this->phase = phase;
|
||||
}
|
||||
}
|
||||
|
||||
delay = time - end_time;
|
||||
}
|
||||
|
||||
// Nes_Triangle
|
||||
|
||||
void Nes_Triangle::clock_linear_counter()
|
||||
{
|
||||
if ( reg_written [3] )
|
||||
linear_counter = regs [0] & 0x7f;
|
||||
else if ( linear_counter )
|
||||
linear_counter--;
|
||||
|
||||
if ( !(regs [0] & 0x80) )
|
||||
reg_written [3] = false;
|
||||
}
|
||||
|
||||
inline int Nes_Triangle::calc_amp() const
|
||||
{
|
||||
int amp = phase_range - phase;
|
||||
if ( amp < 0 )
|
||||
amp = phase - (phase_range + 1);
|
||||
return amp;
|
||||
}
|
||||
|
||||
// TODO: clean up
|
||||
inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||
nes_time_t timer_period )
|
||||
{
|
||||
long remain = end_time - time;
|
||||
if ( remain > 0 )
|
||||
{
|
||||
int count = (remain + timer_period - 1) / timer_period;
|
||||
phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1);
|
||||
phase++;
|
||||
time += (long) count * timer_period;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
void Nes_Triangle::run( nes_time_t time, nes_time_t end_time )
|
||||
{
|
||||
const int timer_period = period() + 1;
|
||||
if ( !output )
|
||||
{
|
||||
time += delay;
|
||||
delay = 0;
|
||||
if ( length_counter && linear_counter && timer_period >= 3 )
|
||||
delay = maintain_phase( time, end_time, timer_period ) - end_time;
|
||||
return;
|
||||
}
|
||||
|
||||
// to do: track phase when period < 3
|
||||
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
|
||||
|
||||
int delta = update_amp( calc_amp() );
|
||||
if ( delta )
|
||||
synth.offset( time, delta, output );
|
||||
|
||||
time += delay;
|
||||
if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 )
|
||||
{
|
||||
time = end_time;
|
||||
}
|
||||
else if ( time < end_time )
|
||||
{
|
||||
Blip_Buffer* const output = this->output;
|
||||
|
||||
int phase = this->phase;
|
||||
int volume = 1;
|
||||
if ( phase > phase_range ) {
|
||||
phase -= phase_range;
|
||||
volume = -volume;
|
||||
}
|
||||
|
||||
do {
|
||||
if ( --phase == 0 ) {
|
||||
phase = phase_range;
|
||||
volume = -volume;
|
||||
}
|
||||
else {
|
||||
synth.offset_inline( time, volume, output );
|
||||
}
|
||||
|
||||
time += timer_period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
if ( volume < 0 )
|
||||
phase += phase_range;
|
||||
this->phase = phase;
|
||||
last_amp = calc_amp();
|
||||
}
|
||||
delay = time - end_time;
|
||||
}
|
||||
|
||||
// Nes_Dmc
|
||||
|
||||
void Nes_Dmc::reset()
|
||||
{
|
||||
address = 0;
|
||||
dac = 0;
|
||||
buf = 0;
|
||||
bits_remain = 1;
|
||||
bits = 0;
|
||||
buf_full = false;
|
||||
silence = true;
|
||||
next_irq = Nes_Apu::no_irq;
|
||||
irq_flag = false;
|
||||
irq_enabled = false;
|
||||
|
||||
Nes_Osc::reset();
|
||||
period = 0x1ac;
|
||||
}
|
||||
|
||||
void Nes_Dmc::recalc_irq()
|
||||
{
|
||||
nes_time_t irq = Nes_Apu::no_irq;
|
||||
if ( irq_enabled && length_counter )
|
||||
irq = apu->last_dmc_time + delay +
|
||||
((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1;
|
||||
if ( irq != next_irq ) {
|
||||
next_irq = irq;
|
||||
apu->irq_changed();
|
||||
}
|
||||
}
|
||||
|
||||
int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const
|
||||
{
|
||||
if ( last_read )
|
||||
*last_read = time;
|
||||
|
||||
if ( length_counter == 0 )
|
||||
return 0; // not reading
|
||||
|
||||
long first_read = next_read_time();
|
||||
long avail = time - first_read;
|
||||
if ( avail <= 0 )
|
||||
return 0;
|
||||
|
||||
int count = (avail - 1) / (period * 8) + 1;
|
||||
if ( !(regs [0] & loop_flag) && count > length_counter )
|
||||
count = length_counter;
|
||||
|
||||
if ( last_read ) {
|
||||
*last_read = first_read + (count - 1) * (period * 8) + 1;
|
||||
assert( *last_read <= time );
|
||||
assert( count == count_reads( *last_read, NULL ) );
|
||||
assert( count - 1 == count_reads( *last_read - 1, NULL ) );
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const short dmc_period_table [2] [16] = {
|
||||
{0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC
|
||||
0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036},
|
||||
|
||||
{0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested)
|
||||
0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods
|
||||
};
|
||||
|
||||
inline void Nes_Dmc::reload_sample()
|
||||
{
|
||||
address = 0x4000 + regs [2] * 0x40;
|
||||
length_counter = regs [3] * 0x10 + 1;
|
||||
}
|
||||
|
||||
static const unsigned char dac_table [128] =
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14,
|
||||
15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27,
|
||||
27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38,
|
||||
39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49,
|
||||
50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59,
|
||||
59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67,
|
||||
68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75,
|
||||
76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83,
|
||||
};
|
||||
|
||||
void Nes_Dmc::write_register( int addr, int data )
|
||||
{
|
||||
if ( addr == 0 )
|
||||
{
|
||||
period = dmc_period_table [pal_mode] [data & 15];
|
||||
irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled
|
||||
irq_flag &= irq_enabled;
|
||||
recalc_irq();
|
||||
}
|
||||
else if ( addr == 1 )
|
||||
{
|
||||
int old_dac = dac;
|
||||
dac = data & 0x7F;
|
||||
|
||||
// adjust last_amp so that "pop" amplitude will be properly non-linear
|
||||
// with respect to change in dac
|
||||
int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]);
|
||||
if ( !nonlinear )
|
||||
last_amp = faked_nonlinear;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Dmc::start()
|
||||
{
|
||||
reload_sample();
|
||||
fill_buffer();
|
||||
recalc_irq();
|
||||
}
|
||||
|
||||
void Nes_Dmc::fill_buffer()
|
||||
{
|
||||
if ( !buf_full && length_counter )
|
||||
{
|
||||
require( prg_reader ); // prg_reader must be set
|
||||
buf = prg_reader( prg_reader_data, 0x8000u + address );
|
||||
address = (address + 1) & 0x7FFF;
|
||||
buf_full = true;
|
||||
if ( --length_counter == 0 )
|
||||
{
|
||||
if ( regs [0] & loop_flag ) {
|
||||
reload_sample();
|
||||
}
|
||||
else {
|
||||
apu->osc_enables &= ~0x10;
|
||||
irq_flag = irq_enabled;
|
||||
next_irq = Nes_Apu::no_irq;
|
||||
apu->irq_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
|
||||
{
|
||||
int delta = update_amp( dac );
|
||||
if ( !output )
|
||||
silence = true;
|
||||
else if ( delta )
|
||||
synth.offset( time, delta, output );
|
||||
|
||||
time += delay;
|
||||
if ( time < end_time )
|
||||
{
|
||||
int bits_remain = this->bits_remain;
|
||||
if ( silence && !buf_full )
|
||||
{
|
||||
int count = (end_time - time + period - 1) / period;
|
||||
bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1;
|
||||
time += count * period;
|
||||
}
|
||||
else
|
||||
{
|
||||
Blip_Buffer* const output = this->output;
|
||||
const int period = this->period;
|
||||
int bits = this->bits;
|
||||
int dac = this->dac;
|
||||
|
||||
do
|
||||
{
|
||||
if ( !silence )
|
||||
{
|
||||
int step = (bits & 1) * 4 - 2;
|
||||
bits >>= 1;
|
||||
if ( unsigned (dac + step) <= 0x7F ) {
|
||||
dac += step;
|
||||
synth.offset_inline( time, step, output );
|
||||
}
|
||||
}
|
||||
|
||||
time += period;
|
||||
|
||||
if ( --bits_remain == 0 )
|
||||
{
|
||||
bits_remain = 8;
|
||||
if ( !buf_full ) {
|
||||
silence = true;
|
||||
}
|
||||
else {
|
||||
silence = false;
|
||||
bits = buf;
|
||||
buf_full = false;
|
||||
if ( !output )
|
||||
silence = true;
|
||||
fill_buffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
this->dac = dac;
|
||||
this->last_amp = dac;
|
||||
this->bits = bits;
|
||||
}
|
||||
this->bits_remain = bits_remain;
|
||||
}
|
||||
delay = time - end_time;
|
||||
}
|
||||
|
||||
// Nes_Noise
|
||||
|
||||
static const short noise_period_table [16] = {
|
||||
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
|
||||
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
|
||||
};
|
||||
|
||||
void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
|
||||
{
|
||||
int period = noise_period_table [regs [2] & 15];
|
||||
#if NES_APU_NOISE_LOW_CPU
|
||||
if ( period < 8 )
|
||||
{
|
||||
period = 8;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( !output )
|
||||
{
|
||||
// TODO: clean up
|
||||
time += delay;
|
||||
delay = time + (end_time - time + period - 1) / period * period - end_time;
|
||||
return;
|
||||
}
|
||||
|
||||
const int volume = this->volume();
|
||||
int amp = (noise & 1) ? volume : 0;
|
||||
int delta = update_amp( amp );
|
||||
if ( delta )
|
||||
synth.offset( time, delta, output );
|
||||
|
||||
time += delay;
|
||||
if ( time < end_time )
|
||||
{
|
||||
const int mode_flag = 0x80;
|
||||
|
||||
if ( !volume )
|
||||
{
|
||||
// round to next multiple of period
|
||||
time += (end_time - time + period - 1) / period * period;
|
||||
|
||||
// approximate noise cycling while muted, by shuffling up noise register
|
||||
// to do: precise muted noise cycling?
|
||||
if ( !(regs [2] & mode_flag) ) {
|
||||
int feedback = (noise << 13) ^ (noise << 14);
|
||||
noise = (feedback & 0x4000) | (noise >> 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Blip_Buffer* const output = this->output;
|
||||
|
||||
// using resampled time avoids conversion in synth.offset()
|
||||
blip_resampled_time_t rperiod = output->resampled_duration( period );
|
||||
blip_resampled_time_t rtime = output->resampled_time( time );
|
||||
|
||||
int noise = this->noise;
|
||||
int delta = amp * 2 - volume;
|
||||
const int tap = (regs [2] & mode_flag ? 8 : 13);
|
||||
|
||||
do {
|
||||
int feedback = (noise << tap) ^ (noise << 14);
|
||||
time += period;
|
||||
|
||||
if ( (noise + 1) & 2 ) {
|
||||
// bits 0 and 1 of noise differ
|
||||
delta = -delta;
|
||||
synth.offset_resampled( rtime, delta, output );
|
||||
}
|
||||
|
||||
rtime += rperiod;
|
||||
noise = (feedback & 0x4000) | (noise >> 1);
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
last_amp = (delta + volume) >> 1;
|
||||
this->noise = noise;
|
||||
}
|
||||
}
|
||||
|
||||
delay = time - end_time;
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
// Private oscillators used by Nes_Apu
|
||||
|
||||
// Nes_Snd_Emu 0.1.7
|
||||
|
||||
#ifndef NES_OSCS_H
|
||||
#define NES_OSCS_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
class Nes_Apu;
|
||||
|
||||
struct Nes_Osc
|
||||
{
|
||||
unsigned char regs [4];
|
||||
bool reg_written [4];
|
||||
Blip_Buffer* output;
|
||||
int length_counter;// length counter (0 if unused by oscillator)
|
||||
int delay; // delay until next (potential) transition
|
||||
int last_amp; // last amplitude oscillator was outputting
|
||||
|
||||
void clock_length( int halt_mask );
|
||||
int period() const {
|
||||
return (regs [3] & 7) * 0x100 + (regs [2] & 0xff);
|
||||
}
|
||||
void reset() {
|
||||
delay = 0;
|
||||
last_amp = 0;
|
||||
}
|
||||
int update_amp( int amp ) {
|
||||
int delta = amp - last_amp;
|
||||
last_amp = amp;
|
||||
return delta;
|
||||
}
|
||||
};
|
||||
|
||||
struct Nes_Envelope : Nes_Osc
|
||||
{
|
||||
int envelope;
|
||||
int env_delay;
|
||||
|
||||
void clock_envelope();
|
||||
int volume() const;
|
||||
void reset() {
|
||||
envelope = 0;
|
||||
env_delay = 0;
|
||||
Nes_Osc::reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Nes_Square
|
||||
struct Nes_Square : Nes_Envelope
|
||||
{
|
||||
enum { negate_flag = 0x08 };
|
||||
enum { shift_mask = 0x07 };
|
||||
enum { phase_range = 8 };
|
||||
int phase;
|
||||
int sweep_delay;
|
||||
|
||||
typedef Blip_Synth<blip_good_quality,1> Synth;
|
||||
Synth const& synth; // shared between squares
|
||||
|
||||
Nes_Square( Synth const* s ) : synth( *s ) { }
|
||||
|
||||
void clock_sweep( int adjust );
|
||||
void run( nes_time_t, nes_time_t );
|
||||
void reset() {
|
||||
sweep_delay = 0;
|
||||
Nes_Envelope::reset();
|
||||
}
|
||||
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||
nes_time_t timer_period );
|
||||
};
|
||||
|
||||
// Nes_Triangle
|
||||
struct Nes_Triangle : Nes_Osc
|
||||
{
|
||||
enum { phase_range = 16 };
|
||||
int phase;
|
||||
int linear_counter;
|
||||
Blip_Synth<blip_med_quality,1> synth;
|
||||
|
||||
int calc_amp() const;
|
||||
void run( nes_time_t, nes_time_t );
|
||||
void clock_linear_counter();
|
||||
void reset() {
|
||||
linear_counter = 0;
|
||||
phase = 1;
|
||||
Nes_Osc::reset();
|
||||
}
|
||||
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||
nes_time_t timer_period );
|
||||
};
|
||||
|
||||
// Nes_Noise
|
||||
struct Nes_Noise : Nes_Envelope
|
||||
{
|
||||
int noise;
|
||||
Blip_Synth<blip_med_quality,1> synth;
|
||||
|
||||
void run( nes_time_t, nes_time_t );
|
||||
void reset() {
|
||||
noise = 1 << 14;
|
||||
Nes_Envelope::reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Nes_Dmc
|
||||
struct Nes_Dmc : Nes_Osc
|
||||
{
|
||||
int address; // address of next byte to read
|
||||
int period;
|
||||
//int length_counter; // bytes remaining to play (already defined in Nes_Osc)
|
||||
int buf;
|
||||
int bits_remain;
|
||||
int bits;
|
||||
bool buf_full;
|
||||
bool silence;
|
||||
|
||||
enum { loop_flag = 0x40 };
|
||||
|
||||
int dac;
|
||||
|
||||
nes_time_t next_irq;
|
||||
bool irq_enabled;
|
||||
bool irq_flag;
|
||||
bool pal_mode;
|
||||
bool nonlinear;
|
||||
|
||||
int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function
|
||||
void* prg_reader_data;
|
||||
|
||||
Nes_Apu* apu;
|
||||
|
||||
Blip_Synth<blip_med_quality,1> synth;
|
||||
|
||||
void start();
|
||||
void write_register( int, int );
|
||||
void run( nes_time_t, nes_time_t );
|
||||
void recalc_irq();
|
||||
void fill_buffer();
|
||||
void reload_sample();
|
||||
void reset();
|
||||
int count_reads( nes_time_t, nes_time_t* ) const;
|
||||
nes_time_t next_read_time() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,669 @@
|
|||
|
||||
// Timing and behavior of PPU
|
||||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Ppu.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "Nes_State.h"
|
||||
#include "Nes_Mapper.h"
|
||||
#include "Nes_Core.h"
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
// to do: remove unnecessary run_until() calls
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// Timing
|
||||
|
||||
ppu_time_t const scanline_len = Nes_Ppu::scanline_len;
|
||||
|
||||
// if non-zero, report sprite max at fixed time rather than calculating it
|
||||
nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock;
|
||||
int const sprite_max_cpu_offset = 2420 + 3;
|
||||
|
||||
ppu_time_t const t_to_v_time = 20 * scanline_len + 302;
|
||||
ppu_time_t const even_odd_time = 20 * scanline_len + 328;
|
||||
|
||||
ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied
|
||||
ppu_time_t const first_hblank_time = 21 * scanline_len + 252;
|
||||
|
||||
ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock;
|
||||
ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set
|
||||
|
||||
nes_time_t const vbl_end_time = 2272;
|
||||
ppu_time_t const max_frame_length = 262 * scanline_len;
|
||||
//ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync
|
||||
nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10;
|
||||
|
||||
// Scanline rendering
|
||||
|
||||
void Nes_Ppu::render_bg_until_( nes_time_t cpu_time )
|
||||
{
|
||||
ppu_time_t time = ppu_time( cpu_time );
|
||||
ppu_time_t const frame_duration = scanline_len * 261;
|
||||
if ( time > frame_duration )
|
||||
time = frame_duration;
|
||||
|
||||
// one-time events
|
||||
if ( frame_phase <= 1 )
|
||||
{
|
||||
if ( frame_phase < 1 )
|
||||
{
|
||||
// vtemp->vaddr
|
||||
frame_phase = 1;
|
||||
if ( w2001 & 0x08 )
|
||||
vram_addr = vram_temp;
|
||||
}
|
||||
|
||||
// variable-length scanline
|
||||
if ( time <= even_odd_time )
|
||||
{
|
||||
next_bg_time = nes_time( even_odd_time );
|
||||
return;
|
||||
}
|
||||
frame_phase = 2;
|
||||
if ( !(w2001 & 0x08) || emu.nes.frame_count & 1 )
|
||||
{
|
||||
if ( --frame_length_extra < 0 )
|
||||
{
|
||||
frame_length_extra = 2;
|
||||
frame_length_++;
|
||||
}
|
||||
burst_phase--;
|
||||
}
|
||||
burst_phase = (burst_phase + 2) % 3;
|
||||
}
|
||||
|
||||
// scanlines
|
||||
if ( scanline_time < time )
|
||||
{
|
||||
int count = (time - scanline_time + scanline_len) / scanline_len;
|
||||
|
||||
// hblank before next scanline
|
||||
if ( hblank_time < scanline_time )
|
||||
{
|
||||
hblank_time += scanline_len;
|
||||
run_hblank( 1 );
|
||||
}
|
||||
|
||||
scanline_time += count * scanline_len;
|
||||
|
||||
hblank_time += scanline_len * (count - 1);
|
||||
int saved_vaddr = vram_addr;
|
||||
|
||||
int start = scanline_count;
|
||||
scanline_count += count;
|
||||
draw_background( start, count );
|
||||
|
||||
vram_addr = saved_vaddr; // to do: this is cheap
|
||||
run_hblank( count - 1 );
|
||||
}
|
||||
|
||||
// hblank after current scanline
|
||||
ppu_time_t next_ppu_time = hblank_time;
|
||||
if ( hblank_time < time )
|
||||
{
|
||||
hblank_time += scanline_len;
|
||||
run_hblank( 1 );
|
||||
next_ppu_time = scanline_time; // scanline will run next
|
||||
}
|
||||
assert( time <= hblank_time );
|
||||
|
||||
// either hblank or scanline comes next
|
||||
next_bg_time = nes_time( next_ppu_time );
|
||||
}
|
||||
|
||||
void Nes_Ppu::render_until_( nes_time_t time )
|
||||
{
|
||||
// render bg scanlines then render sprite scanlines up to wherever bg was rendered to
|
||||
|
||||
render_bg_until( time );
|
||||
next_sprites_time = nes_time( scanline_time );
|
||||
if ( host_pixels )
|
||||
{
|
||||
int start = next_sprites_scanline;
|
||||
int count = scanline_count - start;
|
||||
if ( count > 0 )
|
||||
{
|
||||
next_sprites_scanline += count;
|
||||
draw_sprites( start, count );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Frame events
|
||||
|
||||
inline void Nes_Ppu::end_vblank()
|
||||
{
|
||||
// clear VBL, sprite hit, and max sprites flags first time after 20 scanlines
|
||||
r2002 &= end_vbl_mask;
|
||||
end_vbl_mask = ~0;
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::run_end_frame( nes_time_t time )
|
||||
{
|
||||
if ( !frame_ended )
|
||||
{
|
||||
// update frame_length
|
||||
render_bg_until( time );
|
||||
|
||||
// set VBL when end of frame is reached
|
||||
nes_time_t len = frame_length();
|
||||
if ( time >= len )
|
||||
{
|
||||
r2002 |= 0x80;
|
||||
frame_ended = true;
|
||||
if ( w2000 & 0x80 )
|
||||
nmi_time_ = len + 2 - (frame_length_extra >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite max
|
||||
|
||||
inline void Nes_Ppu::invalidate_sprite_max_()
|
||||
{
|
||||
next_sprite_max_run = earliest_sprite_max / ppu_overclock;
|
||||
sprite_max_set_time = 0;
|
||||
}
|
||||
|
||||
void Nes_Ppu::run_sprite_max_( nes_time_t cpu_time )
|
||||
{
|
||||
end_vblank(); // might get run outside $2002 handler
|
||||
|
||||
// 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is
|
||||
int start_scanline = next_sprite_max_scanline;
|
||||
next_sprite_max_scanline = unsigned ((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u;
|
||||
assert( next_sprite_max_scanline >= 0 && next_sprite_max_scanline <= last_sprite_max_scanline );
|
||||
|
||||
if ( !sprite_max_set_time )
|
||||
{
|
||||
if ( !(w2001 & 0x18) )
|
||||
return;
|
||||
|
||||
long t = recalc_sprite_max( start_scanline );
|
||||
sprite_max_set_time = indefinite_time;
|
||||
if ( t > 0 )
|
||||
sprite_max_set_time = t / 3 + sprite_max_cpu_offset;
|
||||
next_sprite_max_run = sprite_max_set_time;
|
||||
//dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time );
|
||||
}
|
||||
|
||||
if ( cpu_time > sprite_max_set_time )
|
||||
{
|
||||
r2002 |= 0x20;
|
||||
//dprintf( "Sprite max flag set: %d\n", sprite_max_set_time );
|
||||
next_sprite_max_run = indefinite_time;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::run_sprite_max( nes_time_t t )
|
||||
{
|
||||
if ( !fixed_sprite_max_time && t > next_sprite_max_run )
|
||||
run_sprite_max_( t );
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::invalidate_sprite_max( nes_time_t t )
|
||||
{
|
||||
if ( !fixed_sprite_max_time && !(r2002 & 0x20) )
|
||||
{
|
||||
run_sprite_max( t );
|
||||
invalidate_sprite_max_();
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite 0 hit
|
||||
|
||||
inline int Nes_Ppu_Impl::first_opaque_sprite_line() const
|
||||
{
|
||||
// advance earliest time if sprite has blank lines at beginning
|
||||
byte const* p = map_chr( sprite_tile_index( spr_ram ) * 16 );
|
||||
int twice = w2000 >> 5 & 1; // loop twice if double height is set
|
||||
int line = 0;
|
||||
do
|
||||
{
|
||||
for ( int n = 8; n--; p++ )
|
||||
{
|
||||
if ( p [0] | p [8] )
|
||||
return line;
|
||||
line++;
|
||||
}
|
||||
|
||||
p += 8;
|
||||
}
|
||||
while ( !--twice );
|
||||
return line;
|
||||
}
|
||||
|
||||
void Nes_Ppu::update_sprite_hit( nes_time_t cpu_time )
|
||||
{
|
||||
ppu_time_t earliest = earliest_sprite_hit + spr_ram [0] * scanline_len + spr_ram [3];
|
||||
//ppu_time_t latest = earliest + sprite_height() * scanline_len;
|
||||
|
||||
earliest += first_opaque_sprite_line() * scanline_len;
|
||||
|
||||
ppu_time_t time = ppu_time( cpu_time );
|
||||
next_sprite_hit_check = indefinite_time;
|
||||
|
||||
if ( false )
|
||||
if ( earliest < time )
|
||||
{
|
||||
r2002 |= 0x40;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( time < earliest )
|
||||
{
|
||||
next_sprite_hit_check = nes_time( earliest );
|
||||
return;
|
||||
}
|
||||
|
||||
// within possible range; render scanline and compare pixels
|
||||
int count_needed = 2 + (time - earliest_sprite_hit - spr_ram [3]) / scanline_len;
|
||||
if ( count_needed > 240 )
|
||||
count_needed = 240;
|
||||
while ( scanline_count < count_needed )
|
||||
render_bg_until( max( cpu_time, next_bg_time + 1 ) );
|
||||
|
||||
if ( sprite_hit_found < 0 )
|
||||
return; // sprite won't hit
|
||||
|
||||
if ( !sprite_hit_found )
|
||||
{
|
||||
// check again next scanline
|
||||
next_sprite_hit_check = nes_time( earliest_sprite_hit + spr_ram [3] +
|
||||
(scanline_count - 1) * scanline_len );
|
||||
}
|
||||
else
|
||||
{
|
||||
// hit found
|
||||
ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len;
|
||||
|
||||
if ( time < hit_time )
|
||||
{
|
||||
next_sprite_hit_check = nes_time( hit_time );
|
||||
return;
|
||||
}
|
||||
|
||||
//dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n",
|
||||
// sprite_hit_found % 341, sprite_hit_found / 341, scanline_count );
|
||||
|
||||
r2002 |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
// $2002
|
||||
|
||||
inline void Nes_Ppu::query_until( nes_time_t time )
|
||||
{
|
||||
end_vblank();
|
||||
|
||||
// sprite hit
|
||||
if ( time > next_sprite_hit_check )
|
||||
update_sprite_hit( time );
|
||||
|
||||
// sprite max
|
||||
if ( !fixed_sprite_max_time )
|
||||
run_sprite_max( time );
|
||||
else if ( time >= fixed_sprite_max_time )
|
||||
r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20);
|
||||
}
|
||||
|
||||
int Nes_Ppu::read_2002( nes_time_t time )
|
||||
{
|
||||
nes_time_t next = next_status_event;
|
||||
next_status_event = vbl_end_time;
|
||||
int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0;
|
||||
if ( time > next && time > vbl_end_time + extra_clock )
|
||||
{
|
||||
query_until( time );
|
||||
|
||||
next_status_event = next_sprite_hit_check;
|
||||
nes_time_t const next_max = fixed_sprite_max_time ?
|
||||
fixed_sprite_max_time : next_sprite_max_run;
|
||||
if ( next_status_event > next_max )
|
||||
next_status_event = next_max;
|
||||
|
||||
if ( time > earliest_open_bus_decay() )
|
||||
{
|
||||
next_status_event = earliest_open_bus_decay();
|
||||
update_open_bus( time );
|
||||
}
|
||||
|
||||
if ( time > earliest_vbl_end_time )
|
||||
{
|
||||
if ( next_status_event > earliest_vbl_end_time )
|
||||
next_status_event = earliest_vbl_end_time;
|
||||
run_end_frame( time );
|
||||
|
||||
// special vbl behavior when read is just before or at clock when it's set
|
||||
if ( extra_clocks != 1 )
|
||||
{
|
||||
if ( time == frame_length() )
|
||||
{
|
||||
nmi_time_ = indefinite_time;
|
||||
//dprintf( "Suppressed NMI\n" );
|
||||
}
|
||||
}
|
||||
else if ( time == frame_length() - 1 )
|
||||
{
|
||||
r2002 &= ~0x80;
|
||||
frame_ended = true;
|
||||
nmi_time_ = indefinite_time;
|
||||
//dprintf( "Suppressed NMI\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
emu.set_ppu_2002_time( next_status_event );
|
||||
|
||||
int result = r2002;
|
||||
second_write = false;
|
||||
r2002 = result & ~0x80;
|
||||
poke_open_bus( time, result, 0xE0 );
|
||||
update_open_bus( time );
|
||||
return ( result & 0xE0 ) | ( open_bus & 0x1F );
|
||||
}
|
||||
|
||||
void Nes_Ppu::dma_sprites( nes_time_t time, void const* in )
|
||||
{
|
||||
//dprintf( "%d sprites written\n", time );
|
||||
render_until( time );
|
||||
|
||||
invalidate_sprite_max( time );
|
||||
// catch anything trying to dma while rendering is enabled
|
||||
check( time + 513 <= vbl_end_time || !(w2001 & 0x18) );
|
||||
|
||||
memcpy( spr_ram + w2003, in, 0x100 - w2003 );
|
||||
memcpy( spr_ram, (char*) in + 0x100 - w2003, w2003 );
|
||||
}
|
||||
|
||||
// Read
|
||||
|
||||
inline int Nes_Ppu_Impl::read_2007( int addr )
|
||||
{
|
||||
int result = r2007;
|
||||
if ( addr < 0x2000 )
|
||||
{
|
||||
r2007 = *map_chr( addr );
|
||||
}
|
||||
else
|
||||
{
|
||||
r2007 = get_nametable( addr ) [addr & 0x3ff];
|
||||
if ( addr >= 0x3f00 )
|
||||
{
|
||||
return palette [map_palette( addr )] | ( open_bus & 0xC0 );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int Nes_Ppu::read( unsigned addr, nes_time_t time )
|
||||
{
|
||||
if ( addr & ~0x2007 )
|
||||
dprintf( "Read from mirrored PPU register 0x%04X\n", addr );
|
||||
|
||||
switch ( addr & 7 )
|
||||
{
|
||||
// status
|
||||
case 2: // handled inline
|
||||
return read_2002( time );
|
||||
|
||||
// sprite ram
|
||||
case 4: {
|
||||
int result = spr_ram [w2003];
|
||||
if ( (w2003 & 3) == 2 )
|
||||
result &= 0xe3;
|
||||
poke_open_bus( time, result, ~0 );
|
||||
return result;
|
||||
}
|
||||
|
||||
// video ram
|
||||
case 7: {
|
||||
render_bg_until( time );
|
||||
int addr = vram_addr;
|
||||
int new_addr = addr + addr_inc;
|
||||
vram_addr = new_addr;
|
||||
if ( ~addr & new_addr & vaddr_clock_mask )
|
||||
{
|
||||
emu.mapper->a12_clocked();
|
||||
addr = vram_addr - addr_inc; // avoid having to save across func call
|
||||
}
|
||||
int result = read_2007( addr & 0x3fff );
|
||||
poke_open_bus( time, result, ( ( addr & 0x3fff ) >= 0x3f00 ) ? 0x3F : ~0 );
|
||||
return result;
|
||||
}
|
||||
|
||||
default:
|
||||
dprintf( "Read from unimplemented PPU register 0x%04X\n", addr );
|
||||
break;
|
||||
}
|
||||
|
||||
update_open_bus( time );
|
||||
|
||||
return open_bus;
|
||||
}
|
||||
|
||||
// Write
|
||||
|
||||
void Nes_Ppu::write( nes_time_t time, unsigned addr, int data )
|
||||
{
|
||||
if ( addr & ~0x2007 )
|
||||
dprintf( "Wrote to mirrored PPU register 0x%04X\n", addr );
|
||||
|
||||
switch ( addr & 7 )
|
||||
{
|
||||
case 0:{// control
|
||||
int changed = w2000 ^ data;
|
||||
|
||||
if ( changed & 0x28 )
|
||||
render_until( time ); // obj height or pattern addr changed
|
||||
else if ( changed & 0x10 )
|
||||
render_bg_until( time ); // bg pattern addr changed
|
||||
else if ( ((data << 10) ^ vram_temp) & 0x0C00 )
|
||||
render_bg_until( time ); // nametable changed
|
||||
|
||||
if ( changed & 0x80 )
|
||||
{
|
||||
if ( time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1) )
|
||||
end_vblank(); // to do: clean this up
|
||||
|
||||
if ( data & 0x80 & r2002 )
|
||||
{
|
||||
nmi_time_ = time + 2;
|
||||
emu.event_changed();
|
||||
}
|
||||
if ( time >= earliest_vbl_end_time )
|
||||
run_end_frame( time - 1 + (extra_clocks & 1) );
|
||||
}
|
||||
|
||||
// nametable select
|
||||
vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400);
|
||||
|
||||
if ( changed & 0x20 ) // sprite height changed
|
||||
invalidate_sprite_max( time );
|
||||
w2000 = data;
|
||||
addr_inc = data & 4 ? 32 : 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:{// sprites, bg enable
|
||||
int changed = w2001 ^ data;
|
||||
|
||||
if ( changed & 0xE1 )
|
||||
{
|
||||
render_until( time + 1 ); // emphasis/monochrome bits changed
|
||||
palette_changed = 0x18;
|
||||
}
|
||||
|
||||
if ( changed & 0x14 )
|
||||
render_until( time + 1 ); // sprite enable/clipping changed
|
||||
else if ( changed & 0x0A )
|
||||
render_bg_until( time + 1 ); // bg enable/clipping changed
|
||||
|
||||
if ( changed & 0x08 ) // bg enabled changed
|
||||
emu.mapper->run_until( time );
|
||||
|
||||
if ( !(w2001 & 0x18) != !(data & 0x18) )
|
||||
invalidate_sprite_max( time ); // all rendering just turned on or off
|
||||
|
||||
w2001 = data;
|
||||
|
||||
if ( changed & 0x08 )
|
||||
emu.irq_changed();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: // spr addr
|
||||
w2003 = data;
|
||||
poke_open_bus( time, w2003, ~0 );
|
||||
break;
|
||||
|
||||
case 4:
|
||||
//dprintf( "%d sprites written\n", time );
|
||||
if ( time > first_scanline_time / ppu_overclock )
|
||||
{
|
||||
render_until( time );
|
||||
invalidate_sprite_max( time );
|
||||
}
|
||||
spr_ram [w2003] = data;
|
||||
w2003 = (w2003 + 1) & 0xff;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
render_bg_until( time );
|
||||
if ( (second_write ^= 1) )
|
||||
{
|
||||
pixel_x = data & 7;
|
||||
vram_temp = (vram_temp & ~0x1f) | (data >> 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
vram_temp = (vram_temp & ~0x73e0) |
|
||||
(data << 12 & 0x7000) | (data << 2 & 0x03e0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
render_bg_until( time );
|
||||
if ( (second_write ^= 1) )
|
||||
{
|
||||
vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00);
|
||||
}
|
||||
else
|
||||
{
|
||||
int changed = ~vram_addr & vram_temp;
|
||||
vram_addr = vram_temp = (vram_temp & 0xff00) | data;
|
||||
if ( changed & vaddr_clock_mask )
|
||||
emu.mapper->a12_clocked();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dprintf( "Wrote to unimplemented PPU register 0x%04X\n", addr );
|
||||
break;
|
||||
}
|
||||
|
||||
poke_open_bus( time, data, ~0 );
|
||||
}
|
||||
|
||||
// Frame begin/end
|
||||
|
||||
nes_time_t Nes_Ppu::begin_frame( ppu_time_t timestamp )
|
||||
{
|
||||
// current time
|
||||
int cpu_timestamp = timestamp / ppu_overclock;
|
||||
extra_clocks = timestamp - cpu_timestamp * ppu_overclock;
|
||||
|
||||
// frame end
|
||||
ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks;
|
||||
frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock;
|
||||
frame_length_extra = frame_length_ * ppu_overclock - frame_end;
|
||||
assert( (unsigned) frame_length_extra < 3 );
|
||||
|
||||
// nmi
|
||||
nmi_time_ = indefinite_time;
|
||||
if ( w2000 & 0x80 & r2002 )
|
||||
nmi_time_ = 2 - (extra_clocks >> 1);
|
||||
|
||||
// bg rendering
|
||||
frame_phase = 0;
|
||||
scanline_count = 0;
|
||||
hblank_time = first_hblank_time;
|
||||
scanline_time = first_scanline_time;
|
||||
next_bg_time = nes_time( t_to_v_time );
|
||||
|
||||
// sprite rendering
|
||||
next_sprites_scanline = 0;
|
||||
next_sprites_time = 0;
|
||||
|
||||
// status register
|
||||
frame_ended = false;
|
||||
end_vbl_mask = ~0xE0;
|
||||
next_status_event = 0;
|
||||
sprite_hit_found = 0;
|
||||
next_sprite_hit_check = 0;
|
||||
next_sprite_max_scanline = 0;
|
||||
invalidate_sprite_max_();
|
||||
|
||||
decay_low += cpu_timestamp;
|
||||
decay_high += cpu_timestamp;
|
||||
|
||||
base::begin_frame();
|
||||
|
||||
//dprintf( "cpu_timestamp: %d\n", cpu_timestamp );
|
||||
return cpu_timestamp;
|
||||
}
|
||||
|
||||
ppu_time_t Nes_Ppu::end_frame( nes_time_t end_time )
|
||||
{
|
||||
render_bg_until( end_time );
|
||||
render_until( end_time );
|
||||
query_until( end_time );
|
||||
run_end_frame( end_time );
|
||||
|
||||
update_open_bus( end_time );
|
||||
decay_low -= end_time;
|
||||
decay_high -= end_time;
|
||||
|
||||
// to do: do more PPU RE to get exact behavior
|
||||
if ( w2001 & 0x08 )
|
||||
{
|
||||
unsigned a = vram_addr + 2;
|
||||
if ( (vram_addr & 0xff) >= 0xfe )
|
||||
a = (vram_addr ^ 0x400) - 0x1e;
|
||||
vram_addr = a;
|
||||
}
|
||||
|
||||
if ( w2001 & 0x10 )
|
||||
w2003 = 0;
|
||||
|
||||
suspend_rendering();
|
||||
|
||||
return (end_time - frame_length_) * ppu_overclock + frame_length_extra;
|
||||
}
|
||||
|
||||
void Nes_Ppu::poke_open_bus( nes_time_t time, int data, int mask )
|
||||
{
|
||||
open_bus = ( open_bus & ~mask ) | ( data & mask );
|
||||
if ( mask & 0x1F ) decay_low = time + scanline_len * 100 / ppu_overclock;
|
||||
if ( mask & 0xE0 ) decay_high = time + scanline_len * 100 / ppu_overclock;
|
||||
}
|
||||
|
||||
const nes_time_t Nes_Ppu::earliest_open_bus_decay() const
|
||||
{
|
||||
return ( decay_low < decay_high ) ? decay_low : decay_high;
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
|
||||
// NES PPU emulator
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_PPU_H
|
||||
#define NES_PPU_H
|
||||
|
||||
#include "Nes_Ppu_Rendering.h"
|
||||
class Nes_Mapper;
|
||||
class Nes_Core;
|
||||
|
||||
typedef long nes_time_t;
|
||||
typedef long ppu_time_t; // ppu_time_t = nes_time_t * ppu_overclock
|
||||
|
||||
ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock
|
||||
|
||||
class Nes_Ppu : public Nes_Ppu_Rendering {
|
||||
typedef Nes_Ppu_Rendering base;
|
||||
public:
|
||||
Nes_Ppu( Nes_Core* );
|
||||
|
||||
// Begin PPU frame and return beginning CPU timestamp
|
||||
nes_time_t begin_frame( ppu_time_t );
|
||||
|
||||
nes_time_t nmi_time() { return nmi_time_; }
|
||||
void acknowledge_nmi() { nmi_time_ = LONG_MAX / 2 + 1; }
|
||||
|
||||
int read_2002( nes_time_t );
|
||||
int read( unsigned addr, nes_time_t );
|
||||
void write( nes_time_t, unsigned addr, int );
|
||||
|
||||
void render_bg_until( nes_time_t );
|
||||
void render_until( nes_time_t );
|
||||
|
||||
// CPU time that frame will have ended by
|
||||
int frame_length() const { return frame_length_; }
|
||||
|
||||
// End frame rendering and return PPU timestamp for next frame
|
||||
ppu_time_t end_frame( nes_time_t );
|
||||
|
||||
// Do direct memory copy to sprite RAM
|
||||
void dma_sprites( nes_time_t, void const* in );
|
||||
|
||||
int burst_phase;
|
||||
|
||||
private:
|
||||
|
||||
Nes_Core& emu;
|
||||
|
||||
enum { indefinite_time = LONG_MAX / 2 + 1 };
|
||||
|
||||
void suspend_rendering();
|
||||
int read_( unsigned addr, nes_time_t ); // note swapped arguments!
|
||||
|
||||
// NES<->PPU time conversion
|
||||
int extra_clocks;
|
||||
ppu_time_t ppu_time( nes_time_t t ) const { return t * ppu_overclock + extra_clocks; }
|
||||
nes_time_t nes_time( ppu_time_t t ) const { return (t - extra_clocks) / ppu_overclock; }
|
||||
|
||||
// frame
|
||||
nes_time_t nmi_time_;
|
||||
int end_vbl_mask;
|
||||
int frame_length_;
|
||||
int frame_length_extra;
|
||||
bool frame_ended;
|
||||
void end_vblank();
|
||||
void run_end_frame( nes_time_t );
|
||||
|
||||
// bg rendering
|
||||
nes_time_t next_bg_time;
|
||||
ppu_time_t scanline_time;
|
||||
ppu_time_t hblank_time;
|
||||
int scanline_count;
|
||||
int frame_phase;
|
||||
void render_bg_until_( nes_time_t );
|
||||
void run_scanlines( int count );
|
||||
|
||||
// sprite rendering
|
||||
ppu_time_t next_sprites_time;
|
||||
int next_sprites_scanline;
|
||||
void render_until_( nes_time_t );
|
||||
|
||||
// $2002 status register
|
||||
nes_time_t next_status_event;
|
||||
void query_until( nes_time_t );
|
||||
|
||||
// sprite hit
|
||||
nes_time_t next_sprite_hit_check;
|
||||
void update_sprite_hit( nes_time_t );
|
||||
|
||||
// open bus decay
|
||||
void update_open_bus( nes_time_t );
|
||||
void poke_open_bus( nes_time_t, int, int mask );
|
||||
const nes_time_t earliest_open_bus_decay() const;
|
||||
|
||||
// sprite max
|
||||
nes_time_t next_sprite_max_run; // doesn't need to run until this time
|
||||
nes_time_t sprite_max_set_time; // if 0, needs to be recalculated
|
||||
int next_sprite_max_scanline;
|
||||
void run_sprite_max_( nes_time_t );
|
||||
void run_sprite_max( nes_time_t );
|
||||
void invalidate_sprite_max_();
|
||||
void invalidate_sprite_max( nes_time_t );
|
||||
|
||||
friend int nes_cpu_read_likely_ppu( class Nes_Core*, unsigned, nes_time_t );
|
||||
};
|
||||
|
||||
inline void Nes_Ppu::suspend_rendering()
|
||||
{
|
||||
next_bg_time = indefinite_time;
|
||||
next_sprites_time = indefinite_time;
|
||||
extra_clocks = 0;
|
||||
}
|
||||
|
||||
inline Nes_Ppu::Nes_Ppu( Nes_Core* e ) : emu( *e )
|
||||
{
|
||||
burst_phase = 0;
|
||||
suspend_rendering();
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::render_until( nes_time_t t )
|
||||
{
|
||||
if ( t > next_sprites_time )
|
||||
render_until_( t );
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::render_bg_until( nes_time_t t )
|
||||
{
|
||||
if ( t > next_bg_time )
|
||||
render_bg_until_( t );
|
||||
}
|
||||
|
||||
inline void Nes_Ppu::update_open_bus( nes_time_t time )
|
||||
{
|
||||
if ( time >= decay_low ) open_bus &= ~0x1F;
|
||||
if ( time >= decay_high ) open_bus &= ~0xE0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
while ( true )
|
||||
{
|
||||
while ( count-- )
|
||||
{
|
||||
int attrib = attr_table [addr >> 2 & 0x07];
|
||||
attrib >>= (addr >> 4 & 4) | (addr & 2);
|
||||
unsigned long offset = (attrib & 3) * attrib_factor + this->palette_offset;
|
||||
|
||||
// draw one tile
|
||||
cache_t const* lines = this->get_bg_tile( nametable [addr] + bg_bank );
|
||||
byte* p = pixels;
|
||||
addr++;
|
||||
pixels += 8; // next tile
|
||||
|
||||
if ( !clipped )
|
||||
{
|
||||
// optimal case: no clipping
|
||||
for ( int n = 4; n--; )
|
||||
{
|
||||
unsigned long line = *lines++;
|
||||
((uint32_t*) p) [0] = (line >> 4 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line & mask) + offset;
|
||||
p += row_bytes;
|
||||
((uint32_t*) p) [0] = (line >> 6 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line >> 2 & mask) + offset;
|
||||
p += row_bytes;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lines += fine_y >> 1;
|
||||
|
||||
if ( fine_y & 1 )
|
||||
{
|
||||
unsigned long line = *lines++;
|
||||
((uint32_t*) p) [0] = (line >> 6 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line >> 2 & mask) + offset;
|
||||
p += row_bytes;
|
||||
}
|
||||
|
||||
for ( int n = height >> 1; n--; )
|
||||
{
|
||||
unsigned long line = *lines++;
|
||||
((uint32_t*) p) [0] = (line >> 4 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line & mask) + offset;
|
||||
p += row_bytes;
|
||||
((uint32_t*) p) [0] = (line >> 6 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line >> 2 & mask) + offset;
|
||||
p += row_bytes;
|
||||
}
|
||||
|
||||
if ( height & 1 )
|
||||
{
|
||||
unsigned long line = *lines;
|
||||
((uint32_t*) p) [0] = (line >> 4 & mask) + offset;
|
||||
((uint32_t*) p) [1] = (line & mask) + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count = count2;
|
||||
count2 = 0;
|
||||
addr -= 32;
|
||||
attr_table = attr_table - nametable + nametable2;
|
||||
nametable = nametable2;
|
||||
if ( !count )
|
||||
break;
|
||||
}
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
|
||||
// NES PPU register read/write and frame timing
|
||||
|
||||
// Nes_Emu 0.5.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Ppu.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2005 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
// to do: implement junk in unused bits when reading registers
|
||||
|
||||
// to do: put in common or something
|
||||
template<class T>
|
||||
inline const T& min( const T& x, const T& y )
|
||||
{
|
||||
if ( x < y )
|
||||
return x;
|
||||
return y;
|
||||
}
|
||||
|
||||
typedef BOOST::uint8_t byte;
|
||||
|
||||
ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock
|
||||
ppu_time_t const scanline_duration = 341;
|
||||
ppu_time_t const t_to_v_time = 20 * scanline_duration + 293;
|
||||
//ppu_time_t const t_to_v_time = 19 * scanline_duration + 330; // 322 - 339 passes test
|
||||
ppu_time_t const first_scanline_time = 21 * scanline_duration + 128;
|
||||
ppu_time_t const first_hblank_time = 21 * scanline_duration + 256;
|
||||
|
||||
void Nes_Ppu::start_frame()
|
||||
{
|
||||
scanline_time = first_scanline_time;
|
||||
hblank_time = first_hblank_time;
|
||||
next_time = t_to_v_time / ppu_overclock;
|
||||
next_scanline = 0;
|
||||
frame_phase = 0;
|
||||
query_phase = 0;
|
||||
w2003 = 0;
|
||||
memset( sprite_scanlines, 64 - max_sprites, sizeof sprite_scanlines );
|
||||
}
|
||||
|
||||
void Nes_Ppu::query_until( nes_time_t time )
|
||||
{
|
||||
// nothing happens until scanline 20
|
||||
if ( time > 2271 )
|
||||
{
|
||||
// clear VBL flag and sprite hit after 20 scanlines
|
||||
if ( query_phase < 1 )
|
||||
{
|
||||
query_phase = 1;
|
||||
r2002 &= ~0xc0;
|
||||
}
|
||||
|
||||
// update sprite hit
|
||||
if ( query_phase < 2 && update_sprite_hit( time ) )
|
||||
query_phase = 2;
|
||||
|
||||
// set VBL flag a few clocks before the end of the frame (cheap hack)
|
||||
if ( query_phase < 3 && time > 29777 )
|
||||
{
|
||||
query_phase = 3;
|
||||
r2002 |= 0x80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu::end_frame( nes_time_t end_time )
|
||||
{
|
||||
render_until( end_time );
|
||||
query_until( end_time );
|
||||
// to do: remove (shows number of sprites per line graphically)
|
||||
if ( false )
|
||||
if ( base_pixels )
|
||||
{
|
||||
for ( int i = 0; i < image_height; i++ )
|
||||
{
|
||||
int n = sprite_scanlines [i] - (64 - max_sprites);
|
||||
memset( base_pixels + 16 + i * row_bytes, (n <= 8 ? 3 : 6), n );
|
||||
}
|
||||
}
|
||||
// dprintf( "End of frame\n" );
|
||||
start_frame();
|
||||
}
|
||||
|
||||
inline byte const* Nes_Ppu::map_chr( int addr ) const
|
||||
{
|
||||
return &chr_rom [map_chr_addr( addr )];
|
||||
}
|
||||
|
||||
// Read/write
|
||||
|
||||
inline byte* Nes_Ppu::map_palette( int addr )
|
||||
{
|
||||
if ( (addr & 3) == 0 )
|
||||
addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c
|
||||
|
||||
return &palette [addr & 0x1f];
|
||||
}
|
||||
|
||||
int Nes_Ppu::read( nes_time_t time, unsigned addr )
|
||||
{
|
||||
// Don't catch rendering up to present since status reads don't affect
|
||||
// rendering and status is often polled in a tight loop.
|
||||
|
||||
switch ( addr & 7 )
|
||||
{
|
||||
// status
|
||||
case 2: {
|
||||
second_write = false;
|
||||
query_until( time );
|
||||
int result = r2002;
|
||||
r2002 &= ~0x80;
|
||||
return result;
|
||||
}
|
||||
|
||||
// sprite ram
|
||||
case 4: {
|
||||
int result = spr_ram [w2003];
|
||||
if ( (w2003 & 3) == 2 )
|
||||
result &= 0xe3;
|
||||
return result;
|
||||
}
|
||||
|
||||
// video ram
|
||||
case 7: {
|
||||
render_until( time ); // changes to vram_addr affect rendering
|
||||
int result = r2007;
|
||||
int a = vram_addr & 0x3fff;
|
||||
vram_addr = a + ((w2000 & 4) ? 32 : 1);
|
||||
if ( a < 0x2000 )
|
||||
{
|
||||
r2007 = *map_chr( a );
|
||||
}
|
||||
else
|
||||
{
|
||||
r2007 = get_nametable( a ) [a & 0x3ff];
|
||||
|
||||
// palette doesn't use read buffer, but it's still filled with nametable contents
|
||||
if ( a >= 0x3f00 )
|
||||
result = *map_palette( a );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Ppu::write( nes_time_t time, unsigned addr, int data )
|
||||
{
|
||||
if ( addr > 0x2007 )
|
||||
printf( "Write to mirrored $200x\n" );
|
||||
|
||||
int reg = (addr & 7);
|
||||
if ( reg == 0 )
|
||||
{
|
||||
// render only if changes to register could affect it
|
||||
int new_temp = (vram_temp & ~0x0c00) | ((data & 3) * 0x400);
|
||||
if ( (new_temp - vram_temp) | ((w2000 ^ data) & 0x38) )
|
||||
render_until( time );
|
||||
|
||||
vram_temp = new_temp;
|
||||
w2000 = data;
|
||||
return;
|
||||
}
|
||||
|
||||
render_until( time );
|
||||
switch ( reg )
|
||||
{
|
||||
//case 0: // control (handled above)
|
||||
|
||||
case 1: // sprites, bg enable
|
||||
w2001 = data;
|
||||
break;
|
||||
|
||||
case 3: // spr addr
|
||||
w2003 = data;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
spr_ram [w2003] = data;
|
||||
w2003 = (w2003 + 1) & 0xff;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if ( second_write ) {
|
||||
vram_temp = (vram_temp & ~0x73e0) |
|
||||
((data & 0xf8) << 2) | ((data & 7) << 12);
|
||||
}
|
||||
else {
|
||||
pixel_x = data & 7;
|
||||
vram_temp = (vram_temp & ~0x001f) | (data >> 3);
|
||||
}
|
||||
second_write ^= 1;
|
||||
break;
|
||||
|
||||
case 6: {
|
||||
unsigned old_addr = vram_addr;
|
||||
if ( second_write )
|
||||
{
|
||||
vram_addr = vram_temp = (vram_temp & ~0x00ff) | data;
|
||||
// if ( time >= 2271 )
|
||||
// dprintf( "%d VRAM fine: %d, tile: %d\n",
|
||||
// (int) time, int (vram_addr >> 12), int ((vram_addr >> 5) & 31) );
|
||||
}
|
||||
else
|
||||
{
|
||||
vram_temp = (vram_temp & ~0xff00) | ((data << 8) & 0x3f00);
|
||||
}
|
||||
second_write ^= 1;
|
||||
// if ( (vram_addr & old_addr) & 0x2000 )
|
||||
// dprintf( "%d Toggled A13\n", time );
|
||||
break;
|
||||
}
|
||||
|
||||
case 7:
|
||||
{
|
||||
int a = vram_addr & 0x3fff;
|
||||
vram_addr = a + ((w2000 & 4) ? 32 : 1);
|
||||
if ( a < 0x2000 )
|
||||
{
|
||||
a = map_chr_addr( a );
|
||||
BOOST::uint8_t& b = impl->chr_ram [a];
|
||||
if ( (b ^ data) & chr_write_mask )
|
||||
{
|
||||
b = data;
|
||||
assert( a < sizeof impl->chr_ram );
|
||||
tiles_modified [(unsigned) a / bytes_per_tile] = true;
|
||||
any_tiles_modified = true;
|
||||
}
|
||||
}
|
||||
else if ( a < 0x3f00 )
|
||||
{
|
||||
get_nametable( a ) [a & 0x3ff] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
*map_palette( a ) = data & 0x3f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Frame rendering
|
||||
|
||||
// returns true when sprite hit checking is done for the frame (hit flag won't change any more)
|
||||
bool Nes_Ppu::update_sprite_hit( nes_time_t cpu_time )
|
||||
{
|
||||
// earliest time of hit empirically determined by testing on NES
|
||||
ppu_time_t const delay = 21 * scanline_duration + 333;
|
||||
if ( cpu_time < delay / ppu_overclock )
|
||||
return false;
|
||||
|
||||
long time = cpu_time * ppu_overclock - delay;
|
||||
long low_bound = spr_ram [0] * scanline_duration + spr_ram [3];
|
||||
if ( time < low_bound )
|
||||
return false;
|
||||
|
||||
int tile = spr_ram [1] + ((w2000 << 5) & 0x100);
|
||||
int height = 1;
|
||||
if ( w2000 & 0x20 )
|
||||
{
|
||||
height = 2;
|
||||
tile = (tile & 1) * 0x100 + (tile & 0xfe);
|
||||
}
|
||||
byte const* data = map_chr( tile * bytes_per_tile );
|
||||
for ( int n = height; n--; )
|
||||
{
|
||||
for ( int n = 8; n--; )
|
||||
{
|
||||
if ( time < low_bound )
|
||||
return false;
|
||||
|
||||
if ( data [0] | data [8] )
|
||||
{
|
||||
r2002 |= 0x40;
|
||||
return true;
|
||||
}
|
||||
|
||||
data++;
|
||||
low_bound += scanline_duration;
|
||||
}
|
||||
|
||||
data += 8;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Nes_Ppu::run_hblank( int n )
|
||||
{
|
||||
hblank_time += scanline_duration * n;
|
||||
if ( w2001 & 0x08 )
|
||||
{
|
||||
// vram_addr = (vram_addr & ~0x41f) | (vram_temp & 0x41f);
|
||||
long addr = vram_addr + n * 0x1000;
|
||||
if ( addr >= 0x8000 )
|
||||
{
|
||||
addr &= 0x7fff;
|
||||
|
||||
int const mask = 0x3e0;
|
||||
int a = (addr + 0x20) & mask;
|
||||
if ( a == 30 * 0x20 )
|
||||
{
|
||||
a &= 0x1f;
|
||||
addr ^= 0x800;
|
||||
}
|
||||
addr = (addr & ~mask) | (a & mask);
|
||||
}
|
||||
assert( addr < 0x8000 );
|
||||
vram_addr = addr;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu::render_until_( nes_time_t cpu_time )
|
||||
{
|
||||
ppu_time_t time = cpu_time * ppu_overclock;
|
||||
ppu_time_t const frame_duration = scanline_duration * 261;
|
||||
if ( time > frame_duration )
|
||||
time = frame_duration;
|
||||
|
||||
if ( frame_phase == 0 )
|
||||
{
|
||||
frame_phase = 1;
|
||||
if ( w2001 & 0x08 )
|
||||
vram_addr = vram_temp;
|
||||
// else
|
||||
// dprintf( "PPU off\n" );
|
||||
}
|
||||
|
||||
if ( hblank_time < scanline_time && hblank_time < time )
|
||||
run_hblank( 1 );
|
||||
|
||||
int count = 0;
|
||||
while ( scanline_time < time )
|
||||
{
|
||||
scanline_time += scanline_duration;
|
||||
count++;
|
||||
}
|
||||
|
||||
if ( count )
|
||||
{
|
||||
int start = next_scanline;
|
||||
int end = start + count;
|
||||
assert( end <= image_height );
|
||||
next_scanline = end;
|
||||
|
||||
if ( base_pixels )
|
||||
{
|
||||
if ( start == 0 )
|
||||
{
|
||||
memcpy( host_palette, palette, 32 );
|
||||
int bg = palette [0];
|
||||
for ( int i = 0; i < 32; i += 4 )
|
||||
host_palette [i] = bg;
|
||||
memcpy( host_palette + 32, host_palette, 32 );
|
||||
}
|
||||
|
||||
if ( w2001 & 0x18 && any_tiles_modified )
|
||||
{
|
||||
any_tiles_modified = false;
|
||||
update_tiles( 0 );
|
||||
}
|
||||
|
||||
if ( w2001 & 0x08 )
|
||||
{
|
||||
draw_background( start, end );
|
||||
}
|
||||
else
|
||||
{
|
||||
run_hblank( end - start - 1 );
|
||||
black_background( start, end );
|
||||
}
|
||||
|
||||
// when clipping just sprites, save left strip then restore after drawing sprites
|
||||
int const obj_mask = 0x04;
|
||||
int const bg_mask = 0x02;
|
||||
int clip_mode = ~w2001 & (obj_mask | bg_mask);
|
||||
|
||||
if ( clip_mode == obj_mask )
|
||||
save_left( start, end );
|
||||
else if ( clip_mode == bg_mask )
|
||||
clip_left( start, end );
|
||||
|
||||
if ( w2001 & 0x10 )
|
||||
draw_sprites( start, end );
|
||||
|
||||
if ( clip_mode == obj_mask )
|
||||
restore_left( start, end );
|
||||
else if ( clip_mode == (obj_mask | bg_mask) )
|
||||
clip_left( start, end );
|
||||
}
|
||||
else
|
||||
{
|
||||
run_hblank( end - start - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( hblank_time < time )
|
||||
run_hblank( 1 );
|
||||
assert( time <= hblank_time );
|
||||
|
||||
next_time = min( scanline_time, hblank_time ) / ppu_overclock;
|
||||
}
|
||||
|
|
@ -0,0 +1,483 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Ppu_Impl.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "blargg_endian.h"
|
||||
#include "Nes_State.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const cache_line_size = 128; // tile cache is kept aligned to this boundary
|
||||
|
||||
Nes_Ppu_Impl::Nes_Ppu_Impl()
|
||||
{
|
||||
impl = NULL;
|
||||
chr_data = NULL;
|
||||
chr_size = 0;
|
||||
tile_cache = NULL;
|
||||
host_palette = NULL;
|
||||
max_palette_size = 0;
|
||||
tile_cache_mem = NULL;
|
||||
ppu_state_t::unused = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// verify that unaligned accesses work
|
||||
static unsigned char b [19] = { 0 };
|
||||
static unsigned char b2 [19] = { 1,2,3,4,0,5,6,7,8,0,9,0,1,2,0,3,4,5,6 };
|
||||
for ( int i = 0; i < 19; i += 5 )
|
||||
*(volatile BOOST::uint32_t*) &b [i] = *(volatile BOOST::uint32_t*) &b2 [i];
|
||||
assert( !memcmp( b, b2, 19 ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
Nes_Ppu_Impl::~Nes_Ppu_Impl()
|
||||
{
|
||||
close_chr();
|
||||
delete impl;
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::all_tiles_modified()
|
||||
{
|
||||
any_tiles_modified = true;
|
||||
memset( modified_tiles, ~0, sizeof modified_tiles );
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Ppu_Impl::open_chr( byte const* new_chr, long chr_data_size )
|
||||
{
|
||||
close_chr();
|
||||
|
||||
if ( !impl )
|
||||
{
|
||||
impl = BLARGG_NEW impl_t;
|
||||
CHECK_ALLOC( impl );
|
||||
chr_ram = impl->chr_ram;
|
||||
}
|
||||
|
||||
chr_data = new_chr;
|
||||
chr_size = chr_data_size;
|
||||
chr_is_writable = false;
|
||||
|
||||
if ( chr_data_size == 0 )
|
||||
{
|
||||
// CHR RAM
|
||||
chr_data = impl->chr_ram;
|
||||
chr_size = sizeof impl->chr_ram;
|
||||
chr_is_writable = true;
|
||||
}
|
||||
|
||||
// allocate aligned memory for cache
|
||||
assert( chr_size % chr_addr_size == 0 );
|
||||
long tile_count = chr_size / bytes_per_tile;
|
||||
tile_cache_mem = BLARGG_NEW byte [tile_count * sizeof (cached_tile_t) * 2 + cache_line_size];
|
||||
CHECK_ALLOC( tile_cache_mem );
|
||||
tile_cache = (cached_tile_t*) (tile_cache_mem + cache_line_size -
|
||||
(uintptr_t) tile_cache_mem % cache_line_size);
|
||||
flipped_tiles = tile_cache + tile_count;
|
||||
|
||||
// rebuild cache
|
||||
all_tiles_modified();
|
||||
if ( !chr_is_writable )
|
||||
{
|
||||
any_tiles_modified = false;
|
||||
rebuild_chr( 0, chr_size );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::close_chr()
|
||||
{
|
||||
delete [] tile_cache_mem;
|
||||
tile_cache_mem = NULL;
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::set_chr_bank( int addr, int size, long data )
|
||||
{
|
||||
check( !chr_is_writable || addr == data ); // to do: is CHR RAM ever bank-switched?
|
||||
//dprintf( "Tried to set CHR RAM bank at %04X to CHR+%04X\n", addr, data );
|
||||
|
||||
if ( data + size > chr_size )
|
||||
data %= chr_size;
|
||||
|
||||
int count = (unsigned) size / chr_page_size;
|
||||
assert( chr_page_size * count == size );
|
||||
assert( addr + size <= chr_addr_size );
|
||||
|
||||
int page = (unsigned) addr / chr_page_size;
|
||||
while ( count-- )
|
||||
{
|
||||
chr_pages [page] = data - page * chr_page_size;
|
||||
page++;
|
||||
data += chr_page_size;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::save_state( Nes_State_* out ) const
|
||||
{
|
||||
*out->ppu = *this;
|
||||
out->ppu_valid = true;
|
||||
|
||||
memcpy( out->spr_ram, spr_ram, out->spr_ram_size );
|
||||
out->spr_ram_valid = true;
|
||||
|
||||
out->nametable_size = 0x800;
|
||||
memcpy( out->nametable, impl->nt_ram, 0x800 );
|
||||
if ( nt_banks [3] >= &impl->nt_ram [0xC00] )
|
||||
{
|
||||
// save extra nametable data in chr
|
||||
out->nametable_size = 0x1000;
|
||||
memcpy( out->chr, &impl->nt_ram [0x800], 0x800 );
|
||||
}
|
||||
|
||||
out->chr_size = 0;
|
||||
if ( chr_is_writable )
|
||||
{
|
||||
out->chr_size = chr_size;
|
||||
check( out->nametable_size <= 0x800 );
|
||||
assert( out->nametable_size <= 0x800 );
|
||||
assert( out->chr_size <= out->chr_max );
|
||||
memcpy( out->chr, impl->chr_ram, out->chr_size );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::load_state( Nes_State_ const& in )
|
||||
{
|
||||
set_nt_banks( 0, 0, 0, 0 );
|
||||
set_chr_bank( 0, 0x2000, 0 );
|
||||
|
||||
if ( in.ppu_valid )
|
||||
STATIC_CAST(ppu_state_t&,*this) = *in.ppu;
|
||||
|
||||
if ( in.spr_ram_valid )
|
||||
memcpy( spr_ram, in.spr_ram, sizeof spr_ram );
|
||||
|
||||
assert( in.nametable_size <= (int) sizeof impl->nt_ram );
|
||||
if ( in.nametable_size >= 0x800 )
|
||||
{
|
||||
if ( in.nametable_size > 0x800 )
|
||||
memcpy( &impl->nt_ram [0x800], in.chr, 0x800 );
|
||||
memcpy( impl->nt_ram, in.nametable, 0x800 );
|
||||
}
|
||||
|
||||
if ( chr_is_writable && in.chr_size )
|
||||
{
|
||||
assert( in.chr_size <= (int) sizeof impl->chr_ram );
|
||||
memcpy( impl->chr_ram, in.chr, in.chr_size );
|
||||
all_tiles_modified();
|
||||
}
|
||||
}
|
||||
|
||||
static BOOST::uint8_t const initial_palette [0x20] =
|
||||
{
|
||||
0x0f,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C,
|
||||
0x00,0x01,0x34,0x03,0x00,0x04,0x00,0x14,0x00,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08
|
||||
};
|
||||
|
||||
void Nes_Ppu_Impl::reset( bool full_reset )
|
||||
{
|
||||
w2000 = 0;
|
||||
w2001 = 0;
|
||||
r2002 = 0x80;
|
||||
r2007 = 0;
|
||||
open_bus = 0;
|
||||
decay_low = 0;
|
||||
decay_high = 0;
|
||||
second_write = false;
|
||||
vram_temp = 0;
|
||||
pixel_x = 0;
|
||||
|
||||
if ( full_reset )
|
||||
{
|
||||
vram_addr = 0;
|
||||
w2003 = 0;
|
||||
memset( impl->chr_ram, 0xff, sizeof impl->chr_ram );
|
||||
memset( impl->nt_ram, 0xff, sizeof impl->nt_ram );
|
||||
memcpy( palette, initial_palette, sizeof palette );
|
||||
}
|
||||
|
||||
set_nt_banks( 0, 0, 0, 0 );
|
||||
set_chr_bank( 0, chr_addr_size, 0 );
|
||||
memset( spr_ram, 0xff, sizeof spr_ram );
|
||||
all_tiles_modified();
|
||||
if ( max_palette_size > 0 )
|
||||
memset( host_palette, 0, max_palette_size * sizeof *host_palette );
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::capture_palette()
|
||||
{
|
||||
if ( palette_size + palette_increment <= max_palette_size )
|
||||
{
|
||||
palette_offset = (palette_begin + palette_size) * 0x01010101;
|
||||
|
||||
short* out = host_palette + palette_size;
|
||||
palette_size += palette_increment;
|
||||
|
||||
int i;
|
||||
|
||||
int emph = w2001 << 1 & 0x1C0;
|
||||
int mono = (w2001 & 1 ? 0x30 : 0x3F);
|
||||
|
||||
for ( i = 0; i < 32; i++ )
|
||||
out [i] = (palette [i] & mono) | emph;
|
||||
|
||||
int bg = out [0];
|
||||
for ( i = 4; i < 32; i += 4 )
|
||||
out [i] = bg;
|
||||
|
||||
memcpy( out + 32, out, 32 * sizeof *out );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::run_hblank( int count )
|
||||
{
|
||||
require( count >= 0 );
|
||||
|
||||
long addr = (vram_addr & 0x7be0) + (vram_temp & 0x41f) + (count * 0x1000);
|
||||
if ( w2001 & 0x08 )
|
||||
{
|
||||
while ( addr >= 0x8000 )
|
||||
{
|
||||
int y = (addr + 0x20) & 0x3e0;
|
||||
addr = (addr - 0x8000) & ~0x3e0;
|
||||
if ( y == 30 * 0x20 )
|
||||
y = 0x800;
|
||||
addr ^= y;
|
||||
}
|
||||
vram_addr = addr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __MWERKS__
|
||||
#pragma ppc_unroll_factor_limit 1 // messes up calc_sprite_max_scanlines loop
|
||||
static int zero = 0;
|
||||
#else
|
||||
const int zero = 0;
|
||||
#endif
|
||||
|
||||
// Tile cache
|
||||
|
||||
inline unsigned long reorder( unsigned long n )
|
||||
{
|
||||
n |= n << 7;
|
||||
return ((n << 14) | n);
|
||||
}
|
||||
|
||||
inline void Nes_Ppu_Impl::update_tile( int index )
|
||||
{
|
||||
const byte* in = chr_data + (index) * bytes_per_tile;
|
||||
byte* out = (byte*) tile_cache [index];
|
||||
byte* flipped_out = (byte*) flipped_tiles [index];
|
||||
|
||||
unsigned long bit_mask = 0x11111111 + zero;
|
||||
|
||||
for ( int n = 4; n--; )
|
||||
{
|
||||
// Reorder two lines of two-bit pixels. No bits are wasted, so
|
||||
// reordered version is also four bytes.
|
||||
//
|
||||
// 12345678 to A0E4B1F5C2G6D3H7
|
||||
// ABCDEFGH
|
||||
unsigned long c =
|
||||
((reorder( in [0] ) & bit_mask) << 0) |
|
||||
((reorder( in [8] ) & bit_mask) << 1) |
|
||||
((reorder( in [1] ) & bit_mask) << 2) |
|
||||
((reorder( in [9] ) & bit_mask) << 3);
|
||||
in += 2;
|
||||
|
||||
SET_BE32( out, c );
|
||||
out += 4;
|
||||
|
||||
// make horizontally-flipped version
|
||||
c = ((c >> 28) & 0x000f) |
|
||||
((c >> 20) & 0x00f0) |
|
||||
((c >> 12) & 0x0f00) |
|
||||
((c >> 4) & 0xf000) |
|
||||
((c & 0xf000) << 4) |
|
||||
((c & 0x0f00) << 12) |
|
||||
((c & 0x00f0) << 20) |
|
||||
((c & 0x000f) << 28);
|
||||
SET_BE32( flipped_out, c );
|
||||
flipped_out += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::rebuild_chr( unsigned long begin, unsigned long end )
|
||||
{
|
||||
unsigned end_index = (end + bytes_per_tile - 1) / bytes_per_tile;
|
||||
for ( unsigned index = begin / bytes_per_tile; index < end_index; index++ )
|
||||
update_tile( index );
|
||||
}
|
||||
|
||||
void Nes_Ppu_Impl::update_tiles( int first_tile )
|
||||
{
|
||||
int chunk = 0;
|
||||
do
|
||||
{
|
||||
if ( !(uint32_t&) modified_tiles [chunk] )
|
||||
{
|
||||
chunk += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
int modified = modified_tiles [chunk];
|
||||
if ( modified )
|
||||
{
|
||||
modified_tiles [chunk] = 0;
|
||||
|
||||
int index = first_tile + chunk * 8;
|
||||
do
|
||||
{
|
||||
if ( modified & 1 )
|
||||
update_tile( index );
|
||||
index++;
|
||||
}
|
||||
while ( (modified >>= 1) != 0 );
|
||||
}
|
||||
}
|
||||
while ( ++chunk & 3 );
|
||||
}
|
||||
}
|
||||
while ( chunk < chr_tile_count / 8 );
|
||||
}
|
||||
|
||||
// Sprite max
|
||||
|
||||
template<int height>
|
||||
struct calc_sprite_max_scanlines
|
||||
{
|
||||
static unsigned long func( byte const* sprites, byte* scanlines, int begin )
|
||||
{
|
||||
typedef BOOST::uint32_t uint32_t;
|
||||
|
||||
unsigned long any_hits = 0;
|
||||
unsigned long const offset = 0x01010101 + zero;
|
||||
unsigned limit = 239 + height - begin;
|
||||
for ( int n = 64; n; --n )
|
||||
{
|
||||
int top = *sprites;
|
||||
sprites += 4;
|
||||
byte* p = scanlines + top;
|
||||
if ( (unsigned) (239 - top) < limit )
|
||||
{
|
||||
unsigned long p0 = (uint32_t&) p [0] + offset;
|
||||
unsigned long p4 = (uint32_t&) p [4] + offset;
|
||||
(uint32_t&) p [0] = p0;
|
||||
any_hits |= p0;
|
||||
(uint32_t&) p [4] = p4;
|
||||
any_hits |= p4;
|
||||
if ( height > 8 )
|
||||
{
|
||||
unsigned long p0 = (uint32_t&) p [ 8] + offset;
|
||||
unsigned long p4 = (uint32_t&) p [12] + offset;
|
||||
(uint32_t&) p [ 8] = p0;
|
||||
any_hits |= p0;
|
||||
(uint32_t&) p [12] = p4;
|
||||
any_hits |= p4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return any_hits;
|
||||
}
|
||||
};
|
||||
|
||||
long Nes_Ppu_Impl::recalc_sprite_max( int scanline )
|
||||
{
|
||||
int const max_scanline_count = image_height;
|
||||
|
||||
byte sprite_max_scanlines [256 + 16];
|
||||
|
||||
// recalculate sprites per scanline
|
||||
memset( sprite_max_scanlines + scanline, 0x78, last_sprite_max_scanline - scanline );
|
||||
unsigned long any_hits;
|
||||
if ( w2000 & 0x20 )
|
||||
any_hits = calc_sprite_max_scanlines<16>::func( spr_ram, sprite_max_scanlines, scanline );
|
||||
else
|
||||
any_hits = calc_sprite_max_scanlines<8 >::func( spr_ram, sprite_max_scanlines, scanline );
|
||||
|
||||
// cause search to terminate past max_scanline_count if none have 8 or more sprites
|
||||
(uint32_t&) sprite_max_scanlines [max_scanline_count] = 0;
|
||||
sprite_max_scanlines [max_scanline_count + 3] = 0x80;
|
||||
|
||||
// avoid scan if no possible hits
|
||||
if ( !(any_hits & 0x80808080) )
|
||||
return 0;
|
||||
|
||||
// find soonest scanline with 8 or more sprites
|
||||
while ( true )
|
||||
{
|
||||
unsigned long const mask = 0x80808080 + zero;
|
||||
|
||||
// check four at a time
|
||||
byte* pos = &sprite_max_scanlines [scanline];
|
||||
unsigned long n = (uint32_t&) *pos;
|
||||
while ( 1 )
|
||||
{
|
||||
unsigned long x = n & mask;
|
||||
pos += 4;
|
||||
n = (uint32_t&) *pos;
|
||||
if ( x )
|
||||
break;
|
||||
}
|
||||
|
||||
int height = sprite_height();
|
||||
int remain = 8;
|
||||
int i = 0;
|
||||
|
||||
// find which of the four
|
||||
pos -= 3 + (pos [-4] >> 7 & 1);
|
||||
pos += 1 - (*pos >> 7 & 1);
|
||||
pos += 1 - (*pos >> 7 & 1);
|
||||
assert( *pos & 0x80 );
|
||||
|
||||
scanline = pos - sprite_max_scanlines;
|
||||
if ( scanline >= max_scanline_count )
|
||||
break;
|
||||
|
||||
// find time that max sprites flag is set (or that it won't be set)
|
||||
do
|
||||
{
|
||||
int relative = scanline - spr_ram [i];
|
||||
i += 4;
|
||||
if ( (unsigned) relative < (unsigned) height && !--remain )
|
||||
{
|
||||
// now use screwey search for 9th sprite
|
||||
int offset = 0;
|
||||
while ( i < 0x100 )
|
||||
{
|
||||
int relative = scanline - spr_ram [i + offset];
|
||||
//dprintf( "Checking sprite %d [%d]\n", i / 4, offset );
|
||||
i += 4;
|
||||
offset = (offset + 1) & 3;
|
||||
if ( (unsigned) relative < (unsigned) height )
|
||||
{
|
||||
//dprintf( "sprite max on scanline %d\n", scanline );
|
||||
return scanline * scanline_len + (unsigned) i / 2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ( i < 0x100 );
|
||||
scanline++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
|
||||
// NES PPU misc functions and setup
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_PPU_IMPL_H
|
||||
#define NES_PPU_IMPL_H
|
||||
|
||||
#include "nes_data.h"
|
||||
class Nes_State_;
|
||||
|
||||
class Nes_Ppu_Impl : public ppu_state_t {
|
||||
public:
|
||||
typedef BOOST::uint8_t byte;
|
||||
typedef BOOST::uint32_t uint32_t;
|
||||
|
||||
Nes_Ppu_Impl();
|
||||
~Nes_Ppu_Impl();
|
||||
|
||||
void reset( bool full_reset );
|
||||
|
||||
// Setup
|
||||
blargg_err_t open_chr( const byte*, long size );
|
||||
void rebuild_chr( unsigned long begin, unsigned long end );
|
||||
void close_chr();
|
||||
void save_state( Nes_State_* out ) const;
|
||||
void load_state( Nes_State_ const& );
|
||||
|
||||
enum { image_width = 256 };
|
||||
enum { image_height = 240 };
|
||||
enum { image_left = 8 };
|
||||
enum { buffer_width = image_width + 16 };
|
||||
enum { buffer_height = image_height };
|
||||
|
||||
int write_2007( int );
|
||||
|
||||
// Host palette
|
||||
enum { palette_increment = 64 };
|
||||
short* host_palette;
|
||||
int palette_begin;
|
||||
int max_palette_size;
|
||||
int palette_size; // set after frame is rendered
|
||||
|
||||
// Mapping
|
||||
enum { vaddr_clock_mask = 0x1000 };
|
||||
void set_nt_banks( int bank0, int bank1, int bank2, int bank3 );
|
||||
void set_chr_bank( int addr, int size, long data );
|
||||
|
||||
// Nametable and CHR RAM
|
||||
enum { nt_ram_size = 0x1000 };
|
||||
enum { chr_addr_size = 0x2000 };
|
||||
enum { bytes_per_tile = 16 };
|
||||
enum { chr_tile_count = chr_addr_size / bytes_per_tile };
|
||||
enum { mini_offscreen_height = 16 }; // double-height sprite
|
||||
struct impl_t
|
||||
{
|
||||
byte nt_ram [nt_ram_size];
|
||||
byte chr_ram [chr_addr_size];
|
||||
union {
|
||||
BOOST::uint32_t clip_buf [256 * 2];
|
||||
byte mini_offscreen [buffer_width * mini_offscreen_height];
|
||||
};
|
||||
};
|
||||
impl_t* impl;
|
||||
enum { scanline_len = 341 };
|
||||
|
||||
protected:
|
||||
byte spr_ram [0x100];
|
||||
void begin_frame();
|
||||
void run_hblank( int );
|
||||
int sprite_height() const { return (w2000 >> 2 & 8) + 8; }
|
||||
|
||||
protected: //friend class Nes_Ppu; private:
|
||||
|
||||
int addr_inc; // pre-calculated $2007 increment (based on w2001 & 0x04)
|
||||
int read_2007( int addr );
|
||||
|
||||
enum { last_sprite_max_scanline = 240 };
|
||||
long recalc_sprite_max( int scanline );
|
||||
int first_opaque_sprite_line() const;
|
||||
|
||||
protected: //friend class Nes_Ppu_Rendering; private:
|
||||
|
||||
unsigned long palette_offset;
|
||||
int palette_changed;
|
||||
void capture_palette();
|
||||
|
||||
bool any_tiles_modified;
|
||||
bool chr_is_writable;
|
||||
void update_tiles( int first_tile );
|
||||
|
||||
typedef uint32_t cache_t;
|
||||
typedef cache_t cached_tile_t [4];
|
||||
cached_tile_t const& get_bg_tile( int index ) const;
|
||||
cached_tile_t const& get_sprite_tile( byte const* sprite ) const;
|
||||
byte* get_nametable( int addr ) { return nt_banks [addr >> 10 & 3]; };
|
||||
|
||||
private:
|
||||
|
||||
static int map_palette( int addr );
|
||||
int sprite_tile_index( byte const* sprite ) const;
|
||||
|
||||
// Mapping
|
||||
enum { chr_page_size = 0x400 };
|
||||
long chr_pages [chr_addr_size / chr_page_size];
|
||||
long map_chr_addr( unsigned a ) const { return chr_pages [a / chr_page_size] + a; }
|
||||
byte* nt_banks [4];
|
||||
|
||||
// CHR data
|
||||
byte const* chr_data; // points to chr ram when there is no read-only data
|
||||
byte* chr_ram; // always points to impl->chr_ram; makes write_2007() faster
|
||||
long chr_size;
|
||||
byte const* map_chr( int addr ) const { return &chr_data [map_chr_addr( addr )]; }
|
||||
|
||||
// CHR cache
|
||||
cached_tile_t* tile_cache;
|
||||
cached_tile_t* flipped_tiles;
|
||||
byte* tile_cache_mem;
|
||||
union {
|
||||
byte modified_tiles [chr_tile_count / 8];
|
||||
uint32_t align_;
|
||||
};
|
||||
void all_tiles_modified();
|
||||
void update_tile( int index );
|
||||
};
|
||||
|
||||
inline void Nes_Ppu_Impl::set_nt_banks( int bank0, int bank1, int bank2, int bank3 )
|
||||
{
|
||||
byte* nt_ram = impl->nt_ram;
|
||||
nt_banks [0] = &nt_ram [bank0 * 0x400];
|
||||
nt_banks [1] = &nt_ram [bank1 * 0x400];
|
||||
nt_banks [2] = &nt_ram [bank2 * 0x400];
|
||||
nt_banks [3] = &nt_ram [bank3 * 0x400];
|
||||
}
|
||||
|
||||
inline int Nes_Ppu_Impl::map_palette( int addr )
|
||||
{
|
||||
if ( (addr & 3) == 0 )
|
||||
addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c
|
||||
return addr & 0x1f;
|
||||
}
|
||||
|
||||
inline int Nes_Ppu_Impl::sprite_tile_index( byte const* sprite ) const
|
||||
{
|
||||
int tile = sprite [1] + (w2000 << 5 & 0x100);
|
||||
if ( w2000 & 0x20 )
|
||||
tile = (tile & 1) * 0x100 + (tile & 0xfe);
|
||||
return tile;
|
||||
}
|
||||
|
||||
inline int Nes_Ppu_Impl::write_2007( int data )
|
||||
{
|
||||
int addr = vram_addr;
|
||||
byte* chr_ram = this->chr_ram; // pre-read
|
||||
int changed = addr + addr_inc;
|
||||
unsigned const divisor = bytes_per_tile * 8;
|
||||
int mod_index = (unsigned) addr / divisor % (0x4000 / divisor);
|
||||
vram_addr = changed;
|
||||
changed ^= addr;
|
||||
addr &= 0x3fff;
|
||||
|
||||
// use index into modified_tiles [] since it's calculated sooner than addr is masked
|
||||
if ( (unsigned) mod_index < 0x2000 / divisor )
|
||||
{
|
||||
// Avoid overhead of checking for read-only CHR; if that is the case,
|
||||
// this modification will be ignored.
|
||||
int mod = modified_tiles [mod_index];
|
||||
chr_ram [addr] = data;
|
||||
any_tiles_modified = true;
|
||||
modified_tiles [mod_index] = mod | (1 << ((unsigned) addr / bytes_per_tile % 8));
|
||||
}
|
||||
else if ( addr < 0x3f00 )
|
||||
{
|
||||
get_nametable( addr ) [addr & 0x3ff] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
data &= 0x3f;
|
||||
byte& entry = palette [map_palette( addr )];
|
||||
int changed = entry ^ data;
|
||||
entry = data;
|
||||
if ( changed )
|
||||
palette_changed = 0x18;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline void Nes_Ppu_Impl::begin_frame()
|
||||
{
|
||||
palette_changed = 0x18;
|
||||
palette_size = 0;
|
||||
palette_offset = palette_begin * 0x01010101;
|
||||
addr_inc = w2000 & 4 ? 32 : 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Ppu_Rendering.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
#ifdef __MWERKS__
|
||||
static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants
|
||||
#else
|
||||
const unsigned zero = 0; // compile-time constant on other compilers
|
||||
#endif
|
||||
|
||||
// Nes_Ppu_Impl
|
||||
|
||||
inline Nes_Ppu_Impl::cached_tile_t const&
|
||||
Nes_Ppu_Impl::get_sprite_tile( byte const* sprite ) const
|
||||
{
|
||||
cached_tile_t* tiles = tile_cache;
|
||||
if ( sprite [2] & 0x40 )
|
||||
tiles = flipped_tiles;
|
||||
int index = sprite_tile_index( sprite );
|
||||
|
||||
// use index directly, since cached tile is same size as native tile
|
||||
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
|
||||
return *(Nes_Ppu_Impl::cached_tile_t*)
|
||||
((byte*) tiles + map_chr_addr( index * bytes_per_tile ));
|
||||
}
|
||||
|
||||
inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) const
|
||||
{
|
||||
// use index directly, since cached tile is same size as native tile
|
||||
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
|
||||
return *(Nes_Ppu_Impl::cached_tile_t*)
|
||||
((byte*) tile_cache + map_chr_addr( index * bytes_per_tile ));
|
||||
}
|
||||
|
||||
// Fill
|
||||
|
||||
void Nes_Ppu_Rendering::fill_background( int count )
|
||||
{
|
||||
ptrdiff_t const next_line = scanline_row_bytes - image_width;
|
||||
uint32_t* pixels = (uint32_t*) scanline_pixels;
|
||||
|
||||
unsigned long fill = palette_offset;
|
||||
if ( (vram_addr & 0x3f00) == 0x3f00 )
|
||||
{
|
||||
// PPU uses current palette entry if addr is within palette ram
|
||||
int color = vram_addr & 0x1f;
|
||||
if ( !(color & 3) )
|
||||
color &= 0x0f;
|
||||
fill += color * 0x01010101;
|
||||
}
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
for ( int n = image_width / 16; n--; )
|
||||
{
|
||||
pixels [0] = fill;
|
||||
pixels [1] = fill;
|
||||
pixels [2] = fill;
|
||||
pixels [3] = fill;
|
||||
pixels += 4;
|
||||
}
|
||||
pixels = (uint32_t*) ((byte*) pixels + next_line);
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::clip_left( int count )
|
||||
{
|
||||
ptrdiff_t next_line = scanline_row_bytes;
|
||||
byte* p = scanline_pixels;
|
||||
unsigned long fill = palette_offset;
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
((uint32_t*) p) [0] = fill;
|
||||
((uint32_t*) p) [1] = fill;
|
||||
p += next_line;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::save_left( int count )
|
||||
{
|
||||
ptrdiff_t next_line = scanline_row_bytes;
|
||||
byte* in = scanline_pixels;
|
||||
uint32_t* out = impl->clip_buf;
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
unsigned long in0 = ((uint32_t*) in) [0];
|
||||
unsigned long in1 = ((uint32_t*) in) [1];
|
||||
in += next_line;
|
||||
out [0] = in0;
|
||||
out [1] = in1;
|
||||
out += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::restore_left( int count )
|
||||
{
|
||||
ptrdiff_t next_line = scanline_row_bytes;
|
||||
byte* out = scanline_pixels;
|
||||
uint32_t* in = impl->clip_buf;
|
||||
|
||||
for ( int n = count; n--; )
|
||||
{
|
||||
unsigned long in0 = in [0];
|
||||
unsigned long in1 = in [1];
|
||||
in += 2;
|
||||
((uint32_t*) out) [0] = in0;
|
||||
((uint32_t*) out) [1] = in1;
|
||||
out += next_line;
|
||||
}
|
||||
}
|
||||
|
||||
// Background
|
||||
|
||||
void Nes_Ppu_Rendering::draw_background_( int remain )
|
||||
{
|
||||
// Draws 'remain' background scanlines. Does not modify vram_addr.
|
||||
|
||||
int vram_addr = this->vram_addr & 0x7fff;
|
||||
byte* row_pixels = scanline_pixels - pixel_x;
|
||||
int left_clip = (w2001 >> 1 & 1) ^ 1;
|
||||
row_pixels += left_clip * 8;
|
||||
do
|
||||
{
|
||||
// scanlines until next row
|
||||
int height = 8 - (vram_addr >> 12);
|
||||
if ( height > remain )
|
||||
height = remain;
|
||||
|
||||
// handle hscroll change before next scanline
|
||||
int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f;
|
||||
int addr = vram_addr;
|
||||
if ( hscroll_changed )
|
||||
{
|
||||
vram_addr ^= hscroll_changed;
|
||||
height = 1; // hscroll will change after first line
|
||||
}
|
||||
remain -= height;
|
||||
|
||||
// increment address for next row
|
||||
vram_addr += height << 12;
|
||||
assert( vram_addr < 0x10000 );
|
||||
if ( vram_addr & 0x8000 )
|
||||
{
|
||||
int y = (vram_addr + 0x20) & 0x3e0;
|
||||
vram_addr &= 0x7fff & ~0x3e0;
|
||||
if ( y == 30 * 0x20 )
|
||||
y = 0x800; // toggle vertical nametable
|
||||
vram_addr ^= y;
|
||||
}
|
||||
|
||||
// nametable change usually occurs in middle of row
|
||||
byte const* nametable = get_nametable( addr );
|
||||
byte const* nametable2 = get_nametable( addr ^ 0x400 );
|
||||
int count2 = addr & 31;
|
||||
int count = 32 - count2 - left_clip;
|
||||
if ( pixel_x )
|
||||
count2++;
|
||||
|
||||
byte const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)];
|
||||
int bg_bank = (w2000 << 4) & 0x100;
|
||||
addr += left_clip;
|
||||
|
||||
// output pixels
|
||||
ptrdiff_t const row_bytes = scanline_row_bytes;
|
||||
byte* pixels = row_pixels;
|
||||
row_pixels += height * row_bytes;
|
||||
|
||||
unsigned long const mask = 0x03030303 + zero;
|
||||
unsigned long const attrib_factor = 0x04040404 + zero;
|
||||
|
||||
if ( height == 8 )
|
||||
{
|
||||
// unclipped
|
||||
assert( (addr >> 12) == 0 );
|
||||
addr &= 0x03ff;
|
||||
int const fine_y = 0;
|
||||
int const clipped = false;
|
||||
#include "Nes_Ppu_Bg.h"
|
||||
}
|
||||
else
|
||||
{
|
||||
// clipped
|
||||
int const fine_y = addr >> 12;
|
||||
addr &= 0x03ff;
|
||||
height -= fine_y & 1;
|
||||
int const clipped = true;
|
||||
#include "Nes_Ppu_Bg.h"
|
||||
}
|
||||
}
|
||||
while ( remain );
|
||||
}
|
||||
|
||||
// Sprites
|
||||
|
||||
void Nes_Ppu_Rendering::draw_sprites_( int begin, int end )
|
||||
{
|
||||
// Draws sprites on scanlines begin through end - 1. Handles clipping.
|
||||
|
||||
int const sprite_height = this->sprite_height();
|
||||
int end_minus_one = end - 1;
|
||||
int begin_minus_one = begin - 1;
|
||||
int index = 0;
|
||||
do
|
||||
{
|
||||
byte const* sprite = &spr_ram [index];
|
||||
index += 4;
|
||||
|
||||
// find if sprite is visible
|
||||
int top_minus_one = sprite [0];
|
||||
int visible = end_minus_one - top_minus_one;
|
||||
if ( visible <= 0 )
|
||||
continue; // off bottom
|
||||
|
||||
// quickly determine whether sprite is unclipped
|
||||
int neg_vis = visible - sprite_height;
|
||||
int neg_skip = top_minus_one - begin_minus_one;
|
||||
if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0
|
||||
{
|
||||
// unclipped
|
||||
#ifndef NDEBUG
|
||||
int top = sprite [0] + 1;
|
||||
assert( (top + sprite_height) > begin && top < end );
|
||||
assert( begin <= top && top + sprite_height <= end );
|
||||
#endif
|
||||
|
||||
int const skip = 0;
|
||||
int visible = sprite_height;
|
||||
|
||||
#define CLIPPED 0
|
||||
#include "Nes_Ppu_Sprites.h"
|
||||
}
|
||||
else
|
||||
{
|
||||
// clipped
|
||||
if ( neg_vis > 0 )
|
||||
visible -= neg_vis;
|
||||
|
||||
if ( neg_skip > 0 )
|
||||
neg_skip = 0;
|
||||
visible += neg_skip;
|
||||
|
||||
if ( visible <= 0 )
|
||||
continue; // off top
|
||||
|
||||
// visible and clipped
|
||||
#ifndef NDEBUG
|
||||
int top = sprite [0] + 1;
|
||||
assert( (top + sprite_height) > begin && top < end );
|
||||
assert( top < begin || top + sprite_height > end );
|
||||
#endif
|
||||
|
||||
int skip = -neg_skip;
|
||||
|
||||
//dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n",
|
||||
// begin, end, top_minus_one + 1, skip, visible );
|
||||
|
||||
#define CLIPPED 1
|
||||
#include "Nes_Ppu_Sprites.h"
|
||||
}
|
||||
}
|
||||
while ( index < 0x100 );
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end )
|
||||
{
|
||||
// Checks for sprite 0 hit on scanlines begin through end - 1.
|
||||
// Updates sprite_hit_found. Background (but not sprites) must have
|
||||
// already been rendered for the scanlines.
|
||||
|
||||
// clip
|
||||
int top = spr_ram [0] + 1;
|
||||
int skip = begin - top;
|
||||
if ( skip < 0 )
|
||||
skip = 0;
|
||||
|
||||
top += skip;
|
||||
int visible = end - top;
|
||||
if ( visible <= 0 )
|
||||
return; // not visible
|
||||
|
||||
int height = sprite_height();
|
||||
if ( visible >= height )
|
||||
{
|
||||
visible = height;
|
||||
sprite_hit_found = -1; // signal that no more hit checking will take place
|
||||
}
|
||||
|
||||
// pixels
|
||||
ptrdiff_t next_row = this->scanline_row_bytes;
|
||||
byte const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row;
|
||||
cache_t const* lines = get_sprite_tile( spr_ram );
|
||||
|
||||
// left edge clipping
|
||||
int start_x = 0;
|
||||
if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e )
|
||||
{
|
||||
if ( spr_ram [3] == 0 )
|
||||
return; // won't hit
|
||||
start_x = 8 - spr_ram [3];
|
||||
}
|
||||
|
||||
// vertical flip
|
||||
int final = skip + visible;
|
||||
if ( spr_ram [2] & 0x80 )
|
||||
{
|
||||
skip += height - 1;
|
||||
final = skip - visible;
|
||||
}
|
||||
|
||||
// check each line
|
||||
unsigned long const mask = 0x01010101 + zero;
|
||||
do
|
||||
{
|
||||
// get pixels for line
|
||||
unsigned long line = lines [skip >> 1];
|
||||
unsigned long hit0 = ((uint32_t*) bg) [0];
|
||||
unsigned long hit1 = ((uint32_t*) bg) [1];
|
||||
bg += next_row;
|
||||
line >>= skip << 1 & 2;
|
||||
line |= line >> 1;
|
||||
|
||||
// check for hits
|
||||
hit0 = ((hit0 >> 1) | hit0) & (line >> 4);
|
||||
hit1 = ((hit1 >> 1) | hit1) & line;
|
||||
if ( (hit0 | hit1) & mask )
|
||||
{
|
||||
// write to memory to avoid endian issues
|
||||
uint32_t quads [3];
|
||||
quads [0] = hit0;
|
||||
quads [1] = hit1;
|
||||
|
||||
// find which pixel hit
|
||||
int x = start_x;
|
||||
do
|
||||
{
|
||||
if ( ((byte*) quads) [x] & 1 )
|
||||
{
|
||||
x += spr_ram [3];
|
||||
if ( x >= 255 )
|
||||
break; // ignore right edge
|
||||
|
||||
if ( spr_ram [2] & 0x80 )
|
||||
skip = height - 1 - skip; // vertical flip
|
||||
int y = spr_ram [0] + 1 + skip;
|
||||
sprite_hit_found = y * scanline_len + x;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
while ( x++ < 7 );
|
||||
}
|
||||
if ( skip > final )
|
||||
skip -= 2;
|
||||
skip++;
|
||||
}
|
||||
while ( skip != final );
|
||||
}
|
||||
|
||||
// Draw scanlines
|
||||
|
||||
inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const
|
||||
{
|
||||
return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18;
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::draw_scanlines( int start, int count,
|
||||
byte* pixels, long pitch, int mode )
|
||||
{
|
||||
assert( start + count <= image_height );
|
||||
assert( pixels );
|
||||
|
||||
scanline_pixels = pixels + image_left;
|
||||
scanline_row_bytes = pitch;
|
||||
|
||||
int const obj_mask = 2;
|
||||
int const bg_mask = 1;
|
||||
int draw_mode = (w2001 >> 3) & 3;
|
||||
int clip_mode = (~w2001 >> 1) & draw_mode;
|
||||
|
||||
if ( !(draw_mode & bg_mask) )
|
||||
{
|
||||
// no background
|
||||
clip_mode |= bg_mask; // avoid unnecessary save/restore
|
||||
if ( mode & bg_mask )
|
||||
fill_background( count );
|
||||
}
|
||||
|
||||
if ( start == 0 && mode & 1 )
|
||||
memset( sprite_scanlines, max_sprites - sprite_limit, 240 );
|
||||
|
||||
if ( (draw_mode &= mode) )
|
||||
{
|
||||
// sprites and/or background are being rendered
|
||||
|
||||
if ( any_tiles_modified && chr_is_writable )
|
||||
{
|
||||
any_tiles_modified = false;
|
||||
update_tiles( 0 );
|
||||
}
|
||||
|
||||
if ( draw_mode & bg_mask )
|
||||
{
|
||||
//dprintf( "bg %3d-%3d\n", start, start + count - 1 );
|
||||
draw_background_( count );
|
||||
|
||||
if ( clip_mode == bg_mask )
|
||||
clip_left( count );
|
||||
|
||||
if ( sprite_hit_possible( start + count ) )
|
||||
check_sprite_hit( start, start + count );
|
||||
}
|
||||
|
||||
if ( draw_mode & obj_mask )
|
||||
{
|
||||
// when clipping just sprites, save left strip then restore after drawing them
|
||||
if ( clip_mode == obj_mask )
|
||||
save_left( count );
|
||||
|
||||
//dprintf( "obj %3d-%3d\n", start, start + count - 1 );
|
||||
|
||||
draw_sprites_( start, start + count );
|
||||
|
||||
if ( clip_mode == obj_mask )
|
||||
restore_left( count );
|
||||
|
||||
if ( clip_mode == (obj_mask | bg_mask) )
|
||||
clip_left( count );
|
||||
}
|
||||
}
|
||||
|
||||
scanline_pixels = NULL;
|
||||
}
|
||||
|
||||
void Nes_Ppu_Rendering::draw_background( int start, int count )
|
||||
{
|
||||
// always capture palette at least once per frame
|
||||
if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) )
|
||||
{
|
||||
palette_changed = false;
|
||||
capture_palette();
|
||||
}
|
||||
|
||||
if ( host_pixels )
|
||||
{
|
||||
draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 );
|
||||
}
|
||||
else if ( sprite_hit_possible( start + count ) )
|
||||
{
|
||||
// not rendering, but still handle sprite hit using mini graphics buffer
|
||||
int y = spr_ram [0] + 1;
|
||||
int skip = min( count, max( y - start, 0 ) );
|
||||
int visible = min( count - skip, sprite_height() );
|
||||
|
||||
assert( skip + visible <= count );
|
||||
assert( visible <= mini_offscreen_height );
|
||||
|
||||
if ( visible > 0 )
|
||||
{
|
||||
run_hblank( skip );
|
||||
draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
// NES PPU emulator graphics rendering
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_PPU_RENDERING_H
|
||||
#define NES_PPU_RENDERING_H
|
||||
|
||||
#include "Nes_Ppu_Impl.h"
|
||||
|
||||
class Nes_Ppu_Rendering : public Nes_Ppu_Impl {
|
||||
typedef Nes_Ppu_Impl base;
|
||||
public:
|
||||
Nes_Ppu_Rendering();
|
||||
|
||||
int sprite_limit;
|
||||
|
||||
byte* host_pixels;
|
||||
long host_row_bytes;
|
||||
|
||||
protected:
|
||||
|
||||
long sprite_hit_found; // -1: sprite 0 didn't hit, 0: no hit so far, > 0: y * 341 + x
|
||||
void draw_background( int start, int count );
|
||||
void draw_sprites( int start, int count );
|
||||
|
||||
private:
|
||||
|
||||
void draw_scanlines( int start, int count, byte* pixels, long pitch, int mode );
|
||||
void draw_background_( int count );
|
||||
|
||||
// destination for draw functions; avoids extra parameters
|
||||
byte* scanline_pixels;
|
||||
long scanline_row_bytes;
|
||||
|
||||
// fill/copy
|
||||
void fill_background( int count );
|
||||
void clip_left( int count );
|
||||
void save_left( int count );
|
||||
void restore_left( int count );
|
||||
|
||||
// sprites
|
||||
enum { max_sprites = 64 };
|
||||
byte sprite_scanlines [image_height]; // number of sprites on each scanline
|
||||
void draw_sprites_( int start, int count );
|
||||
bool sprite_hit_possible( int scanline ) const;
|
||||
void check_sprite_hit( int begin, int end );
|
||||
};
|
||||
|
||||
inline Nes_Ppu_Rendering::Nes_Ppu_Rendering()
|
||||
{
|
||||
sprite_limit = 8;
|
||||
host_pixels = NULL;
|
||||
}
|
||||
|
||||
inline void Nes_Ppu_Rendering::draw_sprites( int start, int count )
|
||||
{
|
||||
assert( host_pixels );
|
||||
draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 2 );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
|
||||
int sprite_2 = sprite [2];
|
||||
|
||||
// pixels
|
||||
ptrdiff_t next_row = this->scanline_row_bytes;
|
||||
byte* out = this->scanline_pixels + sprite [3] +
|
||||
(top_minus_one + skip - begin_minus_one) * next_row;
|
||||
cache_t const* lines = get_sprite_tile( sprite );
|
||||
|
||||
int dir = 1;
|
||||
byte* scanlines = this->sprite_scanlines + 1 + top_minus_one + skip;
|
||||
|
||||
if ( sprite_2 & 0x80 )
|
||||
{
|
||||
// vertical flip
|
||||
out -= next_row;
|
||||
out += visible * next_row;
|
||||
next_row = -next_row;
|
||||
dir = -1;
|
||||
scanlines += visible - 1;
|
||||
#if CLIPPED
|
||||
int height = this->sprite_height();
|
||||
skip = height - skip - visible;
|
||||
assert( skip + visible <= height );
|
||||
#endif
|
||||
}
|
||||
|
||||
// attributes
|
||||
unsigned long offset = (sprite_2 & 3) * 0x04040404 + (this->palette_offset + 0x10101010);
|
||||
|
||||
unsigned long const mask = 0x03030303 + zero;
|
||||
unsigned long const maskgen = 0x80808080 + zero;
|
||||
|
||||
#define DRAW_PAIR( shift ) { \
|
||||
int sprite_count = *scanlines; \
|
||||
CALC_FOUR( ((uint32_t*) out) [0], (line >> (shift + 4)), out0 ) \
|
||||
CALC_FOUR( ((uint32_t*) out) [1], (line >> shift), out1 ) \
|
||||
if ( sprite_count < this->max_sprites ) { \
|
||||
((uint32_t*) out) [0] = out0; \
|
||||
((uint32_t*) out) [1] = out1; \
|
||||
} \
|
||||
if ( CLIPPED ) visible--; \
|
||||
out += next_row; \
|
||||
*scanlines = sprite_count + 1; \
|
||||
scanlines += dir; \
|
||||
if ( CLIPPED && !visible ) break; \
|
||||
}
|
||||
|
||||
if ( !(sprite_2 & 0x20) )
|
||||
{
|
||||
// front
|
||||
unsigned long const maskgen2 = 0x7f7f7f7f + zero;
|
||||
|
||||
#define CALC_FOUR( in, line, out ) \
|
||||
unsigned long out; \
|
||||
{ \
|
||||
unsigned long bg = in; \
|
||||
unsigned long sp = line & mask; \
|
||||
unsigned long bgm = maskgen2 + ((bg >> 4) & mask); \
|
||||
unsigned long spm = (maskgen - sp) & maskgen2; \
|
||||
unsigned long m = (bgm & spm) >> 2; \
|
||||
out = (bg & ~m) | ((sp + offset) & m); \
|
||||
}
|
||||
|
||||
#if CLIPPED
|
||||
lines += skip >> 1;
|
||||
unsigned long line = *lines++;
|
||||
if ( skip & 1 )
|
||||
goto front_skip;
|
||||
|
||||
while ( true )
|
||||
{
|
||||
DRAW_PAIR( 0 )
|
||||
front_skip:
|
||||
DRAW_PAIR( 2 )
|
||||
line = *lines++;
|
||||
}
|
||||
#else
|
||||
for ( int n = visible >> 1; n--; )
|
||||
{
|
||||
unsigned long line = *lines++;
|
||||
DRAW_PAIR( 0 )
|
||||
DRAW_PAIR( 2 )
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef CALC_FOUR
|
||||
}
|
||||
else
|
||||
{
|
||||
// behind
|
||||
unsigned long const omask = 0x20202020 + zero;
|
||||
unsigned long const bg_or = 0xc3c3c3c3 + zero;
|
||||
|
||||
#define CALC_FOUR( in, line, out ) \
|
||||
unsigned long out; \
|
||||
{ \
|
||||
unsigned long bg = in; \
|
||||
unsigned long sp = line & mask; \
|
||||
unsigned long bgm = maskgen - (bg & mask); \
|
||||
unsigned long spm = maskgen - sp; \
|
||||
out = (bg & (bgm | bg_or)) | (spm & omask) | \
|
||||
(((offset & spm) + sp) & ~(bgm >> 2)); \
|
||||
}
|
||||
|
||||
#if CLIPPED
|
||||
lines += skip >> 1;
|
||||
unsigned long line = *lines++;
|
||||
if ( skip & 1 )
|
||||
goto back_skip;
|
||||
|
||||
while ( true )
|
||||
{
|
||||
DRAW_PAIR( 0 )
|
||||
back_skip:
|
||||
DRAW_PAIR( 2 )
|
||||
line = *lines++;
|
||||
}
|
||||
#else
|
||||
for ( int n = visible >> 1; n--; )
|
||||
{
|
||||
unsigned long line = *lines++;
|
||||
DRAW_PAIR( 0 )
|
||||
DRAW_PAIR( 2 )
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef CALC_FOUR
|
||||
}
|
||||
|
||||
#undef CLIPPED
|
||||
#undef DRAW_PAIR
|
|
@ -0,0 +1,472 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Recorder.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const joypad_sync_value = 0xFF; // joypad data on frames it's never read
|
||||
|
||||
Nes_Recorder::Nes_Recorder()
|
||||
{
|
||||
cache = 0;
|
||||
film_ = 0;
|
||||
frames = 0;
|
||||
resync_enabled = false;
|
||||
disable_reverse( 1 ); // sets cache_period_ and cache_size
|
||||
reverse_allowed = true;
|
||||
buffer_height_ = Nes_Ppu::buffer_height * frames_size + 2;
|
||||
}
|
||||
|
||||
Nes_Recorder::~Nes_Recorder()
|
||||
{
|
||||
free( frames );
|
||||
delete [] cache;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Recorder::init_()
|
||||
{
|
||||
RETURN_ERR( base::init_() );
|
||||
|
||||
cache = new Nes_State [cache_size];
|
||||
|
||||
int frame_count = reverse_allowed ? frames_size : 1;
|
||||
CHECK_ALLOC( frames = (saved_frame_t*) calloc( sizeof *frames, frame_count ) );
|
||||
for ( int i = 0; i < frame_count; i++ )
|
||||
frames [i].top = (long) i * Nes_Ppu::buffer_height + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Recorder::clear_cache()
|
||||
{
|
||||
ready_to_resync = false;
|
||||
reverse_enabled = false;
|
||||
for ( int i = 0; i < cache_size; i++ )
|
||||
cache [i].set_timestamp( invalid_frame_count );
|
||||
}
|
||||
|
||||
void Nes_Recorder::set_film( Nes_Film* new_film, frame_count_t time )
|
||||
{
|
||||
require( new_film );
|
||||
film_ = new_film;
|
||||
clear_cache();
|
||||
if ( !film_->blank() )
|
||||
{
|
||||
tell_ = film_->constrain( time );
|
||||
check( tell_ == time ); // catch seeks outside film
|
||||
seek_( tell_ );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Recorder::reset( bool full_reset, bool erase_battery_ram )
|
||||
{
|
||||
base::reset( full_reset, erase_battery_ram );
|
||||
tell_ = 0;
|
||||
film_->clear();
|
||||
clear_cache();
|
||||
}
|
||||
|
||||
void Nes_Recorder::loading_state( Nes_State const& in )
|
||||
{
|
||||
reset();
|
||||
tell_ = in.timestamp();
|
||||
}
|
||||
|
||||
// Frame emulation
|
||||
|
||||
inline int Nes_Recorder::cache_index( frame_count_t t ) const
|
||||
{
|
||||
return (t / cache_period_) % cache_size;
|
||||
}
|
||||
|
||||
void Nes_Recorder::emulate_frame_( Nes_Film::joypad_t joypad )
|
||||
{
|
||||
if ( base::timestamp() % cache_period_ == 0 )
|
||||
save_state( &cache [cache_index( base::timestamp() )] );
|
||||
|
||||
if ( base::emulate_frame( joypad & 0xFF, (joypad >> 8) & 0xFF ) ) { }
|
||||
}
|
||||
|
||||
void Nes_Recorder::replay_frame_( Nes_Film::joypad_t joypad )
|
||||
{
|
||||
if ( base::timestamp() % film_->period() == 0 )
|
||||
{
|
||||
if ( film_->read_snapshot( base::timestamp() ).timestamp() == invalid_frame_count )
|
||||
{
|
||||
Nes_State_* ss = film_->modify_snapshot( base::timestamp() );
|
||||
if ( ss )
|
||||
save_state( ss );
|
||||
else
|
||||
check( false ); // out of memory simply causes lack of caching
|
||||
}
|
||||
}
|
||||
|
||||
emulate_frame_( joypad );
|
||||
}
|
||||
|
||||
int Nes_Recorder::replay_frame()
|
||||
{
|
||||
frame_count_t start_time = base::timestamp();
|
||||
int joypad = film_->get_joypad( start_time );
|
||||
if ( !film_->has_joypad_sync() )
|
||||
{
|
||||
replay_frame_( joypad );
|
||||
}
|
||||
else if ( (joypad & 0xFF) != joypad_sync_value )
|
||||
{
|
||||
// joypad should be read
|
||||
replay_frame_( joypad );
|
||||
if ( !joypad_read_count() )
|
||||
{
|
||||
// emulator has fallen behind
|
||||
dprintf( "Fell behind joypad data \n" );
|
||||
base::set_timestamp( start_time );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get joypad for next frame in case emulator gets ahead
|
||||
if ( film_->contains_frame( start_time + 1 ) )
|
||||
{
|
||||
joypad = film_->get_joypad( start_time + 1 );
|
||||
if ( (joypad & 0xFF) == joypad_sync_value )
|
||||
joypad = 0; // next frame shouldn't read joypad either, so just give it nothing
|
||||
}
|
||||
|
||||
// joypad should not be read
|
||||
replay_frame_( joypad );
|
||||
if ( joypad_read_count() )
|
||||
{
|
||||
// emulator is ahead
|
||||
dprintf( "Ahead of joypad data \n" );
|
||||
base::set_timestamp( film_->constrain( base::timestamp() + 1 ) );
|
||||
}
|
||||
}
|
||||
return base::timestamp() - start_time;
|
||||
}
|
||||
|
||||
// Film handling
|
||||
|
||||
Nes_State_ const* Nes_Recorder::nearest_snapshot( frame_count_t time ) const
|
||||
{
|
||||
Nes_State_ const* ss = film_->nearest_snapshot( time );
|
||||
if ( ss )
|
||||
{
|
||||
// check cache for any snapshots more recent than film_'s
|
||||
for ( frame_count_t t = time - time % cache_period_;
|
||||
ss->timestamp() < t; t -= cache_period_ )
|
||||
{
|
||||
Nes_State_ const& cache_ss = cache [cache_index( t )];
|
||||
if ( cache_ss.timestamp() == t )
|
||||
return &cache_ss;
|
||||
}
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
void Nes_Recorder::seek_( frame_count_t time )
|
||||
{
|
||||
Nes_State_ const* ss = nearest_snapshot( time );
|
||||
if ( !film_->contains( time ) || !ss )
|
||||
{
|
||||
require( false ); // tried to seek outside recording
|
||||
return;
|
||||
}
|
||||
|
||||
base::load_state( *ss );
|
||||
frame_ = 0; // don't render graphics
|
||||
frame_count_t max_iter = (time - base::timestamp()) * 2; // don't seek forever
|
||||
while ( base::timestamp() < time && max_iter-- )
|
||||
replay_frame();
|
||||
}
|
||||
|
||||
void Nes_Recorder::seek( frame_count_t time )
|
||||
{
|
||||
check( film_->contains( time ) );
|
||||
time = film_->constrain( time );
|
||||
if ( time != tell() )
|
||||
{
|
||||
reverse_enabled = false;
|
||||
seek_( time );
|
||||
tell_ = base::timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
inline frame_count_t Nes_Recorder::advancing_frame()
|
||||
{
|
||||
if ( reverse_enabled )
|
||||
{
|
||||
reverse_enabled = false;
|
||||
if ( !film_->blank() )
|
||||
seek_( tell_ );
|
||||
}
|
||||
frame_ = &frames [0];
|
||||
tell_ = base::timestamp();
|
||||
return tell_++;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Recorder::emulate_frame( int joypad, int joypad2 )
|
||||
{
|
||||
frame_count_t time = advancing_frame();
|
||||
require( film_->blank() || film_->contains( time ) );
|
||||
|
||||
Nes_Film::joypad_t joypads = joypad2 * 0x100 + joypad;
|
||||
|
||||
Nes_State_* ss = 0;
|
||||
RETURN_ERR( film_->record_frame( time, joypads, &ss ) );
|
||||
if ( ss )
|
||||
save_state( ss );
|
||||
|
||||
emulate_frame_( joypads );
|
||||
|
||||
if ( film_->has_joypad_sync() && !joypad_read_count() )
|
||||
RETURN_ERR( film_->set_joypad( time, joypad_sync_value ) );
|
||||
|
||||
// avoid stale cache snapshot after trimming film
|
||||
if ( base::timestamp() % cache_period_ == 0 )
|
||||
cache [cache_index( base::timestamp() )].set_timestamp( invalid_frame_count );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Recorder::next_frame()
|
||||
{
|
||||
if ( tell() >= film_->end() )
|
||||
{
|
||||
check( false ); // tried to go past end
|
||||
return;
|
||||
}
|
||||
frame_count_t time = advancing_frame();
|
||||
assert( base::timestamp() == time || base::timestamp() == time + 1 );
|
||||
|
||||
// ready_to_resync avoids endless resyncing if joypad isn't getting read when
|
||||
// it should, thus the timestamp never incrementing.
|
||||
if ( ready_to_resync )
|
||||
{
|
||||
Nes_State_ const& ss = film_->read_snapshot( time );
|
||||
if ( ss.timestamp() == time )
|
||||
{
|
||||
// todo: remove
|
||||
#if !defined (NDEBUG) && 1
|
||||
dprintf( "Resynced \n" );
|
||||
|
||||
static Nes_State* temp = BLARGG_NEW Nes_State [2];
|
||||
static char* temp2 = new char [sizeof *temp];
|
||||
if ( temp && temp2 )
|
||||
{
|
||||
save_state( temp );
|
||||
memcpy( temp2, temp, sizeof *temp );
|
||||
long a = temp->apu.noise.shift_reg;
|
||||
long b = temp->apu.apu.w40xx [0x11];
|
||||
long c = temp->apu.dmc.bits;
|
||||
base::load_state( ss );
|
||||
save_state( temp );
|
||||
save_state( &temp [1] );
|
||||
|
||||
// shift register and dac are not maintained
|
||||
temp->apu.noise.shift_reg = a;
|
||||
temp->apu.apu.w40xx [0x11] = b;
|
||||
temp->apu.dmc.bits = c;
|
||||
|
||||
if ( memcmp( temp2, temp, sizeof *temp ) )
|
||||
{
|
||||
check( !"Film sync corrected error" );
|
||||
Std_File_Writer out;
|
||||
(void) !out.open( "state0" );
|
||||
(void) !temp [0].write( out );
|
||||
(void) !out.open( "state1" );
|
||||
(void) !temp [1].write( out );
|
||||
//(void) !out.open( "state2" );
|
||||
//(void) !ss.write( out );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 )
|
||||
#endif
|
||||
base::load_state( ss );
|
||||
}
|
||||
}
|
||||
|
||||
int count = replay_frame();
|
||||
tell_ = base::timestamp();
|
||||
|
||||
// examination of count prevents endless resync if frame is getting doubled
|
||||
ready_to_resync = false;
|
||||
if ( count && resync_enabled && film_->read_snapshot( tell_ ).timestamp() == tell_ )
|
||||
{
|
||||
fade_sound_out = true;
|
||||
ready_to_resync = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Extra features
|
||||
|
||||
void Nes_Recorder::record_keyframe()
|
||||
{
|
||||
if ( !film_->blank() )
|
||||
{
|
||||
Nes_State_* ss = film_->modify_snapshot( base::timestamp() );
|
||||
if ( !ss )
|
||||
{
|
||||
check( false ); // out of memory simply causes lack of key frame adjustment
|
||||
}
|
||||
// first snapshot can only be replaced if key frame is at beginning of film
|
||||
else if ( ss->timestamp() > film_->begin() || base::timestamp() == film_->begin() )
|
||||
{
|
||||
if ( ss->timestamp() != base::timestamp() )
|
||||
save_state( ss );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame_count_t Nes_Recorder::nearby_keyframe( frame_count_t time ) const
|
||||
{
|
||||
// TODO: reimplement using direct snapshot and cache access
|
||||
check( film_->contains( time ) );
|
||||
|
||||
// don't adjust time if seeking to beginning or end
|
||||
if ( film_->begin() < time && time < film_->end() )
|
||||
{
|
||||
// rounded time must be within about a minute of requested time
|
||||
int const half_threshold = 45 * frame_rate;
|
||||
|
||||
// find nearest snapshots before and after requested time
|
||||
frame_count_t after = invalid_frame_count;
|
||||
frame_count_t before = time + half_threshold;
|
||||
do
|
||||
{
|
||||
after = before;
|
||||
Nes_State_ const* ss = nearest_snapshot( film_->constrain( before - 1 ) );
|
||||
if ( !ss )
|
||||
{
|
||||
require( false ); // tried to seek outside recording
|
||||
return time;
|
||||
}
|
||||
before = ss->timestamp();
|
||||
}
|
||||
while ( time < before );
|
||||
|
||||
// determine closest and use if within threshold
|
||||
frame_count_t closest = after;
|
||||
if ( time - before < after - time )
|
||||
closest = before;
|
||||
int delta = time - closest;
|
||||
if ( max( delta, -delta ) < half_threshold )
|
||||
time = closest;
|
||||
if ( time < film_->begin() )
|
||||
time = film_->begin();
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
void Nes_Recorder::skip( int delta )
|
||||
{
|
||||
if ( delta ) // rounding code can't handle zero
|
||||
{
|
||||
// round to nearest cache timestamp (even if not in cache)
|
||||
frame_count_t current = tell();
|
||||
frame_count_t time = current + delta + cache_period_ / 2;
|
||||
time -= time % cache_period_;
|
||||
if ( delta < 0 )
|
||||
{
|
||||
if ( time >= current )
|
||||
time -= cache_period_;
|
||||
}
|
||||
else if ( time <= current )
|
||||
{
|
||||
time += cache_period_;
|
||||
}
|
||||
seek( film_->constrain( time ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse handling
|
||||
|
||||
// Get index of frame at given timestamp in reverse frames
|
||||
inline int Nes_Recorder::reverse_index( frame_count_t time ) const
|
||||
{
|
||||
int index = time % (frames_size * 2);
|
||||
if ( index >= frames_size ) // opposite direction on odd runs
|
||||
index = frames_size * 2 - 1 - index;
|
||||
return index;
|
||||
}
|
||||
|
||||
// Generate frame at given timestamp into proper position in reverse frames
|
||||
void Nes_Recorder::reverse_fill( frame_count_t time )
|
||||
{
|
||||
if ( time >= film_->begin() )
|
||||
{
|
||||
// todo: can cause excessive seeking when joypad data loses sync
|
||||
if ( base::timestamp() != time )
|
||||
seek_( time );
|
||||
|
||||
saved_frame_t* frame = &frames [reverse_index( time )];
|
||||
frame_ = frame;
|
||||
if ( time % frames_size == frames_size - 1 )
|
||||
fade_sound_out = true;
|
||||
replay_frame();
|
||||
frame->sample_count = base::read_samples( frame->samples, frame->max_samples );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Recorder::prev_frame()
|
||||
{
|
||||
if ( tell() <= film_->begin() || !reverse_allowed )
|
||||
{
|
||||
check( false ); // tried to go before beginning
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = tell_ % frames_size;
|
||||
frame_count_t aligned = tell_ - offset;
|
||||
if ( reverse_enabled )
|
||||
{
|
||||
reverse_fill( aligned - 1 - offset );
|
||||
}
|
||||
else
|
||||
{
|
||||
reverse_enabled = true;
|
||||
for ( int i = 0; i < frames_size; i++ )
|
||||
{
|
||||
frame_count_t time = aligned + i;
|
||||
if ( i >= offset )
|
||||
time -= offset + frames_size; // restore some of previous second
|
||||
reverse_fill( time );
|
||||
}
|
||||
}
|
||||
|
||||
tell_--;
|
||||
frame_ = &frames [reverse_index( tell_ )];
|
||||
}
|
||||
|
||||
long Nes_Recorder::read_samples( short* out, long count )
|
||||
{
|
||||
require( count >= frame().sample_count );
|
||||
if ( !reverse_enabled )
|
||||
return base::read_samples( out, count );
|
||||
|
||||
// copy samples in reverse without reversing left and right channels
|
||||
// to do: optimize?
|
||||
count = frame().sample_count;
|
||||
blip_sample_t const* in = STATIC_CAST(saved_frame_t const&,frame()).samples;
|
||||
int step = frame().chan_count - 1;
|
||||
for ( int i = 0; i < count; i++ )
|
||||
out [count - 1 - (i ^ step)] = in [i];
|
||||
|
||||
return count;
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
// NES emulator with movie recording and playback in both directions
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
#ifndef NES_RECORDER_H
|
||||
#define NES_RECORDER_H
|
||||
|
||||
#include "Nes_Emu.h"
|
||||
#include "Nes_Film.h"
|
||||
|
||||
class Nes_Recorder : public Nes_Emu {
|
||||
public:
|
||||
// Set new film to use for recording and playback. If film isn't empty, seeks to
|
||||
// its beginning (or to optional specified fime). Film *must* be loaded before
|
||||
// using the recorder for emulation.
|
||||
void set_film( Nes_Film*, frame_count_t );
|
||||
void set_film( Nes_Film* f ) { set_film( f, f->begin() ); }
|
||||
|
||||
// Currently loaded film
|
||||
Nes_Film& film() const { return *film_; }
|
||||
|
||||
// Time references are in terms of timestamps at a single moment, rather than
|
||||
// the frames that occur between two timestamps. All timestamps must be within
|
||||
// the current recording. The emulator is always at a particular timestamp in the
|
||||
// recording; a frame exists only in the graphics buffer and sample buffer. Shown
|
||||
// below are timestamps and the frame that occurs between 1 and 2.
|
||||
// |---|///|---|---
|
||||
// 0 1 2 3 timestamps of snapshots between frames
|
||||
|
||||
// Current timestamp
|
||||
frame_count_t tell() const;
|
||||
|
||||
// Seek to new timestamp. Time is constrained to recording if it falls outside.
|
||||
void seek( frame_count_t );
|
||||
|
||||
// Record new frame at current timestamp and remove anything previously
|
||||
// recorded after it.
|
||||
blargg_err_t emulate_frame( int joypad, int joypad2 = 0 );
|
||||
|
||||
// Increment timestamp and generate frame for that duration. Does nothing
|
||||
// if already at end of recording.
|
||||
void next_frame();
|
||||
|
||||
// Decrement timestamp and generate frame for that duration. Does nothing
|
||||
// if already at beginning of recording. Performance is close to that of
|
||||
// next_frame().
|
||||
void prev_frame();
|
||||
|
||||
// Additional features
|
||||
|
||||
// Disable reverse support and optionally use less-frequent cache snapshots, in order
|
||||
// to drastically reduce the required height of the graphics buffer and memory usage.
|
||||
// If used, must be called one time *before* any use of emulator.
|
||||
void disable_reverse( int cache_period_secs = 5 );
|
||||
|
||||
// Call when current film has been significantly changed (loaded from file).
|
||||
// Doesn't need to be called if film was merely trimmed.
|
||||
void film_changed();
|
||||
|
||||
// Attempt to add keyframe to film at current timestamp, which will make future
|
||||
// seeking to this point faster after film is later loaded.
|
||||
void record_keyframe();
|
||||
|
||||
// Get time of nearby key frame within +/- 45 seconds, otherwise return time unchanged.
|
||||
// Seeking to times of key frames is much faster than an arbitrary time. Time
|
||||
// is constrained to film if it falls outside.
|
||||
frame_count_t nearby_keyframe( frame_count_t ) const;
|
||||
|
||||
// Quickly skip forwards or backwards, staying within recording. May skip slightly
|
||||
// more or less than delta, but will always skip at least some if delta is non-zero
|
||||
// (i.e. repeated skip( 1 ) calls will eventually reach the end of the recording).
|
||||
void skip( int delta );
|
||||
|
||||
// When enabled, emulator is synchronized at each snapshot in film. This
|
||||
// corrects any emulation differences compared to when the film was made. Might
|
||||
// be necessary when playing movies made in an older version of this emulator
|
||||
// or from other emulators.
|
||||
void enable_resync( bool b = true ) { resync_enabled = b; }
|
||||
|
||||
// Height of graphics buffer needed when running forward (next_frame() or
|
||||
// emulate_frame(), but not prev_frame())
|
||||
enum { forward_buffer_height = Nes_Ppu::buffer_height };
|
||||
|
||||
public:
|
||||
Nes_Recorder();
|
||||
virtual ~Nes_Recorder();
|
||||
virtual blargg_err_t init_();
|
||||
virtual void reset( bool = true, bool = false );
|
||||
void load_state( Nes_State const& s ) { Nes_Emu::load_state( s ); }
|
||||
blargg_err_t load_state( Auto_File_Reader r ) { return Nes_Emu::load_state( r ); }
|
||||
virtual long read_samples( short* out, long count );
|
||||
private:
|
||||
typedef Nes_Emu base;
|
||||
|
||||
// snapshots
|
||||
Nes_State* cache;
|
||||
int cache_size;
|
||||
int cache_period_;
|
||||
Nes_Film* film_;
|
||||
void clear_cache();
|
||||
int cache_index( frame_count_t ) const;
|
||||
Nes_State_ const* nearest_snapshot( frame_count_t ) const;
|
||||
|
||||
// film
|
||||
frame_count_t tell_;
|
||||
bool resync_enabled;
|
||||
bool ready_to_resync;
|
||||
void emulate_frame_( Nes_Film::joypad_t );
|
||||
void replay_frame_( Nes_Film::joypad_t );
|
||||
int replay_frame();
|
||||
void seek_( frame_count_t );
|
||||
frame_count_t advancing_frame();
|
||||
void loading_state( Nes_State const& );
|
||||
|
||||
// reverse handling
|
||||
enum { frames_size = frame_rate };
|
||||
struct saved_frame_t : Nes_Emu::frame_t {
|
||||
enum { max_samples = 2048 };
|
||||
blip_sample_t samples [max_samples];
|
||||
};
|
||||
saved_frame_t* frames;
|
||||
bool reverse_enabled;
|
||||
bool reverse_allowed;
|
||||
void reverse_fill( frame_count_t );
|
||||
int reverse_index( frame_count_t ) const; // index for given frame
|
||||
};
|
||||
|
||||
inline frame_count_t Nes_Recorder::tell() const { return film_->constrain( tell_ ); }
|
||||
|
||||
inline void Nes_Recorder::film_changed() { set_film( &film(), film().constrain( tell() ) ); }
|
||||
|
||||
inline void Nes_Recorder::disable_reverse( int new_cache_period )
|
||||
{
|
||||
reverse_allowed = false;
|
||||
buffer_height_ = Nes_Ppu::buffer_height;
|
||||
cache_period_ = new_cache_period * frame_rate;
|
||||
cache_size = 2 * 60 * frame_rate / cache_period_ + 7; // +7 reduces contention
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
|
||||
// Nes_Emu 0.5.6. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Rewinder.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2005 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for
|
||||
more details. You should have received a copy of the GNU Lesser General
|
||||
Public License along with this module; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||
|
||||
// to do: fade out at transitions between forward and reverse
|
||||
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
|
||||
// If true, always keep recent frame images in graphics buffer. Reduces overall
|
||||
// performance by about 33% on my machine, due to the frame buffer not staying in the cache.
|
||||
bool const quick_reverse = false;
|
||||
|
||||
Nes_Rewinder::Nes_Rewinder( frame_count_t snapshot_period ) : recorder( snapshot_period )
|
||||
{
|
||||
pixels = NULL;
|
||||
frames = NULL;
|
||||
reverse_enabled = false;
|
||||
}
|
||||
|
||||
Nes_Rewinder::~Nes_Rewinder()
|
||||
{
|
||||
delete [] frames;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Rewinder::init()
|
||||
{
|
||||
if ( !frames )
|
||||
{
|
||||
BLARGG_RETURN_ERR( recorder::init() );
|
||||
|
||||
frames = BLARGG_NEW frame_t [frames_size];
|
||||
BLARGG_CHECK_ALLOC( frames );
|
||||
}
|
||||
|
||||
return blargg_success;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Rewinder::load_ines_rom( Data_Reader& in, Data_Reader* ips )
|
||||
{
|
||||
if ( !frames )
|
||||
BLARGG_RETURN_ERR( init() );
|
||||
|
||||
return recorder::load_ines_rom( in, ips );
|
||||
}
|
||||
|
||||
long Nes_Rewinder::samples_avail() const
|
||||
{
|
||||
return frames [current_frame].sample_count;
|
||||
}
|
||||
|
||||
inline void copy_reverse( const blip_sample_t* in, int count, blip_sample_t* out, int step )
|
||||
{
|
||||
in += count;
|
||||
while ( count > 0 )
|
||||
{
|
||||
count -= step;
|
||||
in -= step;
|
||||
*out = *in;
|
||||
out += step;
|
||||
}
|
||||
}
|
||||
|
||||
long Nes_Rewinder::read_samples( short* out, long out_size )
|
||||
{
|
||||
int count = samples_avail();
|
||||
if ( count )
|
||||
{
|
||||
if ( count > out_size )
|
||||
{
|
||||
count = out_size;
|
||||
assert( false ); // has no provision for reading partial buffer
|
||||
}
|
||||
|
||||
if ( !reverse_enabled )
|
||||
{
|
||||
memcpy( out, frames [current_frame].samples, count * sizeof *out );
|
||||
}
|
||||
else
|
||||
{
|
||||
int step = samples_per_frame();
|
||||
for ( int i = step; i-- > 0; )
|
||||
copy_reverse( frames [current_frame].samples + i, count, out + i, step );
|
||||
}
|
||||
|
||||
if ( fade_sound_in )
|
||||
{
|
||||
fade_sound_in = false;
|
||||
fade_samples_( out, count, 1 );
|
||||
}
|
||||
|
||||
if ( frames [current_frame].fade_out )
|
||||
{
|
||||
fade_sound_in = true;
|
||||
fade_samples_( out, count, -1 );
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Nes_Rewinder::seek( frame_count_t time )
|
||||
{
|
||||
if ( time != tell() )
|
||||
{
|
||||
clear_reverse();
|
||||
recorder::seek_( time );
|
||||
}
|
||||
}
|
||||
|
||||
inline void Nes_Rewinder::set_output( int index )
|
||||
{
|
||||
recorder::set_pixels( pixels + index * frame_height * row_bytes, row_bytes );
|
||||
}
|
||||
|
||||
void Nes_Rewinder::frame_rendered( int index, bool using_buffer )
|
||||
{
|
||||
frame_t& frame = frames [index];
|
||||
if ( recorder::frames_emulated() > 0 )
|
||||
{
|
||||
frame.palette_size = recorder::palette_size();
|
||||
for ( int i = frame.palette_size; i--; )
|
||||
frame.palette [i] = recorder::palette_entry( i );
|
||||
frame.sample_count = recorder::read_samples( frame.samples, frame.max_samples );
|
||||
}
|
||||
else if ( pixels && using_buffer )
|
||||
{
|
||||
int old_index = (index + frames_size - 1) % frames_size;
|
||||
|
||||
memcpy( &frame, &frames [old_index], sizeof frame );
|
||||
|
||||
// to do: handle case where row_bytes is a lot greater than buffer_width
|
||||
memcpy( pixels + index * frame_height * row_bytes,
|
||||
pixels + old_index * frame_height * row_bytes,
|
||||
row_bytes * frame_height );
|
||||
}
|
||||
frame.fade_out = false;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Rewinder::next_frame( int joypad, int joypad2 )
|
||||
{
|
||||
if ( reverse_enabled )
|
||||
{
|
||||
if ( !get_film().empty() ) // if empty then we can't seek
|
||||
recorder::seek_( reversed_time );
|
||||
clear_reverse();
|
||||
}
|
||||
|
||||
current_frame = 0;
|
||||
if ( quick_reverse )
|
||||
{
|
||||
current_frame = recorder::tell() % frames_size;
|
||||
if ( buffer_scrambled )
|
||||
buffer_scrambled--;
|
||||
}
|
||||
set_output( current_frame );
|
||||
|
||||
BLARGG_RETURN_ERR( recorder::next_frame( joypad, joypad2 ) );
|
||||
frame_rendered( current_frame, quick_reverse );
|
||||
|
||||
return blargg_success;
|
||||
}
|
||||
|
||||
frame_count_t Nes_Rewinder::tell() const
|
||||
{
|
||||
return reverse_enabled ? reversed_time : recorder::tell();
|
||||
}
|
||||
|
||||
void Nes_Rewinder::clear_cache()
|
||||
{
|
||||
recorder::clear_cache();
|
||||
clear_reverse();
|
||||
}
|
||||
|
||||
void Nes_Rewinder::clear_reverse()
|
||||
{
|
||||
buffer_scrambled = frames_size;
|
||||
reverse_enabled = false;
|
||||
reverse_unmirrored = 0;
|
||||
reverse_pivot = 0;
|
||||
reversed_time = 0;
|
||||
}
|
||||
|
||||
void Nes_Rewinder::play_frame_( int index )
|
||||
{
|
||||
if ( negative_seek > 0 )
|
||||
{
|
||||
negative_seek--;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_output( index );
|
||||
recorder::play_frame_();
|
||||
frame_rendered( index, true );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Rewinder::seek_clamped( frame_count_t time )
|
||||
{
|
||||
negative_seek = movie_begin() - time;
|
||||
if ( negative_seek > 0 )
|
||||
time = movie_begin();
|
||||
recorder::seek_( time );
|
||||
}
|
||||
|
||||
void Nes_Rewinder::enter_reverse()
|
||||
{
|
||||
reversed_time = recorder::tell() - 1;
|
||||
|
||||
reverse_pivot = reversed_time % frames_size;
|
||||
frame_count_t first_frame = reversed_time - reverse_pivot;
|
||||
if ( buffer_scrambled )
|
||||
{
|
||||
// buffer hasn't been filled with a clean second of frames since last seek
|
||||
|
||||
dprintf( "Refilling reverse buffer, pivot: %d\n", (int) reverse_pivot );
|
||||
|
||||
// fill beginning
|
||||
seek_clamped( first_frame );
|
||||
for ( int i = 0; i <= reverse_pivot; i++ )
|
||||
play_frame_( i );
|
||||
frames [0].fade_out = true;
|
||||
|
||||
// fill end
|
||||
seek_clamped( first_frame - frames_size + reverse_pivot + 1 );
|
||||
for ( int i = reverse_pivot + 1; i < frames_size; i++ )
|
||||
play_frame_( i );
|
||||
}
|
||||
|
||||
if ( reverse_pivot + 1 < frames_size )
|
||||
frames [reverse_pivot + 1].fade_out = true;
|
||||
|
||||
reverse_unmirrored = 2; // unmirrored for first two passes, then alternating
|
||||
reverse_pivot = -reverse_pivot; // don't pivot yet
|
||||
|
||||
seek_clamped( first_frame - frames_size );
|
||||
|
||||
// Buffer is now filled. Current second is at beginning and previous at end,
|
||||
// and in this example reversed_time is 24 and reverse_pivot is 4:
|
||||
// 20 21 22 23 24 25 16 17 18 19
|
||||
|
||||
// As fragment of current second is played backwards, it will be replaced with
|
||||
// beginning of previous second:
|
||||
|
||||
// <---------------
|
||||
// 20 21 22 23 24 25 16 17 18 19 frame 25
|
||||
// 20 21 22 23 24 10 16 17 18 19 frame 24
|
||||
// 20 21 22 23 11 10 16 17 18 19 frame 23
|
||||
// 20 21 22 12 11 10 16 17 18 19 frame 22
|
||||
// 20 21 13 12 11 10 16 17 18 19 frame 21
|
||||
// 20 14 13 12 11 10 16 17 18 19 frame 20
|
||||
// 15 14 13 12 11 10 16 17 18 19 frame 19
|
||||
// Then filling will keep replacing buffer contents in a converging fashion:
|
||||
// <---------
|
||||
// 15 14 13 12 11 10 16 17 18 0 frame 19
|
||||
// 15 14 13 12 11 10 16 17 1 0 frame 18
|
||||
// 15 14 13 12 11 10 16 2 1 0 frame 17
|
||||
// 15 14 13 12 11 10 3 2 1 0 frame 16
|
||||
// -------------->
|
||||
// 4 14 13 12 11 10 3 2 1 0 frame 15
|
||||
// 4 5 13 12 11 10 3 2 1 0 frame 14
|
||||
// 4 5 6 12 11 10 3 2 1 0 frame 13
|
||||
// 4 5 6 7 11 10 3 2 1 0 frame 12
|
||||
// 4 5 6 7 8 10 3 2 1 0 frame 11
|
||||
// 4 5 6 7 8 9 3 2 1 0 frame 10
|
||||
// <---------------
|
||||
// etc.
|
||||
}
|
||||
|
||||
void Nes_Rewinder::prev_frame()
|
||||
{
|
||||
if ( tell() <= movie_begin() )
|
||||
{
|
||||
require( false ); // tried to go before beginning of movie
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !reverse_enabled )
|
||||
{
|
||||
reverse_enabled = true;
|
||||
enter_reverse();
|
||||
}
|
||||
else
|
||||
{
|
||||
play_frame_( current_frame );
|
||||
|
||||
reversed_time--;
|
||||
if ( reversed_time % frames_size == frames_size - 1 )
|
||||
{
|
||||
if ( reverse_pivot < 0 )
|
||||
reverse_pivot = -reverse_pivot;
|
||||
|
||||
if ( --reverse_unmirrored < 0 )
|
||||
reverse_unmirrored = 1;
|
||||
|
||||
seek_clamped( reversed_time - frames_size * 2 + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// determine index of frame in buffer
|
||||
int raw_index = reversed_time % frames_size;
|
||||
int index = raw_index;
|
||||
if ( !reverse_unmirrored )
|
||||
index = frames_size - 1 - index;
|
||||
if ( index <= reverse_pivot )
|
||||
index = reverse_pivot - index;
|
||||
current_frame = index;
|
||||
|
||||
if ( raw_index == 0 ) // previous frame will be from previous restoration
|
||||
frames [index].fade_out = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
// NES recorder with smooth rewind (backwards playback)
|
||||
|
||||
// Nes_Emu 0.5.6. Copyright (C) 2004-2005 Shay Green. GNU LGPL license.
|
||||
|
||||
#ifndef NES_REWINDER_H
|
||||
#define NES_REWINDER_H
|
||||
|
||||
#include "Nes_Recorder.h"
|
||||
|
||||
class Nes_Rewinder : public Nes_Recorder {
|
||||
typedef Nes_Recorder recorder;
|
||||
enum { frames_size = frames_per_second };
|
||||
public:
|
||||
explicit Nes_Rewinder( frame_count_t snapshot_period = 0 );
|
||||
~Nes_Rewinder();
|
||||
|
||||
// Nes_Rewinder adds a single feature to Nes_Recorder: the ability to generate
|
||||
// consecutive previous frames with similar performance to normal forward
|
||||
// generation.
|
||||
|
||||
// Emulate frame *ending* at current timestamp. On exit, timestamp = timestamp - 1.
|
||||
void prev_frame();
|
||||
|
||||
// Recently-generated frames are stored in a caller-supplied graphics buffer,
|
||||
// which is many times taller than a single frame image.
|
||||
enum { frame_height = Nes_Recorder::buffer_height };
|
||||
enum { buffer_height = frame_height * frames_size };
|
||||
|
||||
// Y coordinate of image for current frame in graphics buffer
|
||||
int get_buffer_y();
|
||||
|
||||
// Documented in Nes_Emu.h and Nes_Recorder.h
|
||||
blargg_err_t load_ines_rom( Data_Reader&, Data_Reader* = NULL );
|
||||
void set_pixels( void*, long bytes_per_row );
|
||||
blargg_err_t next_frame( int joypad, int joypad2 = 0 );
|
||||
frame_count_t tell() const;
|
||||
void seek( frame_count_t );
|
||||
long samples_avail() const;
|
||||
long read_samples( short* out, long max_samples );
|
||||
int palette_size() const { return frames [current_frame].palette_size; }
|
||||
int palette_entry( int i ) const { return frames [current_frame].palette [i]; }
|
||||
blargg_err_t init();
|
||||
|
||||
// End of public interface
|
||||
private:
|
||||
|
||||
BOOST::uint8_t* pixels;
|
||||
long row_bytes;
|
||||
|
||||
// frame cache
|
||||
struct frame_t
|
||||
{
|
||||
int sample_count;
|
||||
bool fade_out;
|
||||
int palette_size;
|
||||
byte palette [max_palette_size];
|
||||
enum { max_samples = 2048 };
|
||||
blip_sample_t samples [max_samples];
|
||||
};
|
||||
frame_t* frames;
|
||||
int current_frame;
|
||||
bool fade_sound_in;
|
||||
void set_output( int index );
|
||||
void frame_rendered( int index, bool using_buffer );
|
||||
void clear_cache(); // Nes_Recorder override
|
||||
|
||||
// clamped seek
|
||||
int negative_seek; // if positive, number of frames to ignore before playing
|
||||
void play_frame_( int index );
|
||||
void seek_clamped( frame_count_t ); // allows timestamp to be before beginning
|
||||
|
||||
// reversed frame mapping
|
||||
frame_count_t reversed_time;// current time is different than emulator time in reverse
|
||||
int buffer_scrambled; // number of frames remaining before buffer isn't scrambled
|
||||
bool reverse_enabled;
|
||||
int reverse_unmirrored; // number of buffers until buffer order is mirrored
|
||||
int reverse_pivot; // pivot point in buffer
|
||||
void clear_reverse();
|
||||
void enter_reverse();
|
||||
};
|
||||
|
||||
inline int Nes_Rewinder::get_buffer_y()
|
||||
{
|
||||
return current_frame * frame_height + image_top;
|
||||
}
|
||||
|
||||
inline void Nes_Rewinder::set_pixels( void* p, long rb )
|
||||
{
|
||||
pixels = (BOOST::uint8_t*) p;
|
||||
row_bytes = rb;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue