Add .bz2 file support

Add .bz2 file support
This commit is contained in:
Andy Vandijck 2025-07-11 16:38:46 +02:00
parent c5d1862e4e
commit 0c91d45627
11 changed files with 642 additions and 3 deletions

View File

@ -57,6 +57,10 @@ target_sources(vbam-fex
fex/Gzip_Extractor.h fex/Gzip_Extractor.h
fex/Gzip_Reader.cpp fex/Gzip_Reader.cpp
fex/Gzip_Reader.h fex/Gzip_Reader.h
fex/BZ2_Extractor.cpp
fex/BZ2_Extractor.h
fex/BZ2_Reader.cpp
fex/BZ2_Reader.h
fex/Rar_Extractor.cpp fex/Rar_Extractor.cpp
fex/Rar_Extractor.h fex/Rar_Extractor.h
fex/Zip7_Extractor.cpp fex/Zip7_Extractor.cpp
@ -65,6 +69,8 @@ target_sources(vbam-fex
fex/Zip_Extractor.h fex/Zip_Extractor.h
fex/Zlib_Inflater.cpp fex/Zlib_Inflater.cpp
fex/Zlib_Inflater.h fex/Zlib_Inflater.h
fex/BZ2_Inflater.cpp
fex/BZ2_Inflater.h
unrar/archive.cpp unrar/archive.cpp
unrar/arcread.cpp unrar/arcread.cpp
unrar/blake2s_sse.cpp unrar/blake2s_sse.cpp
@ -105,6 +111,12 @@ target_include_directories(vbam-fex
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${ZLIB_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${ZLIB_INCLUDE_DIRS}
) )
find_library(BZ2_LIBRARY bz2)
if (BZ2_LIBRARY)
target_compile_definitions(vbam-fex PRIVATE FEX_ENABLE_BZ2=1)
endif()
target_link_libraries(vbam-fex target_link_libraries(vbam-fex
PRIVATE ${ZLIB_LIBRARY} PRIVATE ${ZLIB_LIBRARY} ${BZ2_LIBRARY}
) )

View File

@ -0,0 +1,103 @@
// File_Extractor 1.0.0. http://www.slack.net/~ant/
#if FEX_ENABLE_BZ2
#include "BZ2_Extractor.h"
#include <zlib.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_bz2_file()
{
get_crc_table(); // initialize zlib's CRC-32 tables
return blargg_ok;
}
static File_Extractor* new_bzip2()
{
return BLARGG_NEW BZ2_Extractor;
}
fex_type_t_ const fex_bz2_type [1] = {{
".bz2",
&new_bzip2,
"bzip2 file",
&init_bz2_file
}};
BZ2_Extractor::BZ2_Extractor() :
File_Extractor( fex_bz2_type )
{ }
BZ2_Extractor::~BZ2_Extractor()
{
close();
}
blargg_err_t BZ2_Extractor::open_path_v()
{
// skip opening file
return open_v();
}
blargg_err_t BZ2_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 BZ2_Extractor::open_v()
{
// Remove .gz suffix
size_t len = strlen( arc_path() );
if ( fex_has_extension( arc_path(), ".bz2" ) )
len -= 4;
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 BZ2_Extractor::close_v()
{
name.clear();
gr.close();
}
blargg_err_t BZ2_Extractor::next_v()
{
return blargg_ok;
}
blargg_err_t BZ2_Extractor::rewind_v()
{
set_name( name.begin() );
return blargg_ok;
}
blargg_err_t BZ2_Extractor::extract_v( void* p, int n )
{
return gr.read( p, n );
}
#endif

View File

@ -0,0 +1,35 @@
// Presents a gzipped file as an "archive" of just that file.
// Also handles non-gzipped files.
// File_Extractor 1.0.0
#ifndef BZ2_EXTRACTOR_H
#define BZ2_EXTRACTOR_H
#include "File_Extractor.h"
#include "BZ2_Reader.h"
class BZ2_Extractor : public File_Extractor
{
public:
BZ2_Extractor();
virtual ~BZ2_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:
BZ2_Reader gr;
blargg_vector<char> name;
void set_info_();
};
#endif

View File

@ -0,0 +1,255 @@
// File_Extractor 1.0.0. http://www.slack.net/~ant/
#if FEX_ENABLE_BZ2
#include "BZ2_Inflater.h"
/* Copyright (C) 2025 Andy Vandijck. 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_bz2_err( int code )
{
assert( code != Z_OK );
switch ( code )
{
case BZ_MEM_ERROR: return blargg_err_memory;
case BZ_DATA_ERROR: return blargg_err_file_corrupt;
// TODO: handle more error codes
}
const char* str = BLARGG_ERR( BLARGG_ERR_GENERIC, "problem unzipping data" );
return str;
}
void BZ2_Inflater::end()
{
if ( deflated_ )
{
deflated_ = false;
if ( BZ2_bzDecompressEnd ( &zbuf ) )
check( false );
}
buf.clear();
static bz_stream const empty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
memcpy( &zbuf, &empty, sizeof zbuf );
}
BZ2_Inflater::BZ2_Inflater()
{
deflated_ = false;
end(); // initialize things
}
BZ2_Inflater::~BZ2_Inflater()
{
end();
}
blargg_err_t BZ2_Inflater::fill_buf( int count )
{
byte* out = buf.end() - count;
RETURN_ERR( callback( user_data, out, &count ) );
zbuf.avail_in = count;
zbuf.next_in = (char *)out;
return blargg_ok;
}
blargg_err_t BZ2_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 BZ2_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] == 0x42 && zbuf.next_in [1] == 0x5A )
mode = mode_unbz2;
}
if ( mode != mode_copy )
{
int zerr = BZ2_bzDecompressInit( &zbuf, 0, 0 );
if ( zerr )
{
zbuf.next_in = NULL;
return get_bz2_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 BZ2_Inflater::read_all( void* out, int count )
{
if ( deflated_ )
{
zbuf.next_out = (char*) out;
zbuf.avail_out = count;
int err = BZ2_bzDecompress( &zbuf );
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 BZ2_Inflater::read( void* out, int* count_io )
{
int remain = *count_io;
if ( remain && zbuf.next_in )
{
if ( deflated_ )
{
zbuf.next_out = (char*) out;
zbuf.avail_out = remain;
while ( 1 )
{
unsigned int old_avail_in = (unsigned int)zbuf.avail_in;
int err = BZ2_bzDecompress( &zbuf );
if ( err == BZ_STREAM_END )
{
remain = zbuf.avail_out;
end();
break; // no more data to inflate
}
if ( err && (err != BZ_OUTBUFF_FULL || old_avail_in) )
return get_bz2_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( (int)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_lo32 += count;
out = (char*) out + count;
remain -= count;
zbuf.next_in += count;
zbuf.avail_in -= count;
}
if ( !zbuf.avail_in && zbuf.next_in < (char *)buf.end() )
{
end();
break;
}
// read large request directly
if ( remain + zbuf.total_out_lo32 % block_size >= buf.size() )
{
int count = remain;
RETURN_ERR( callback( user_data, out, &count ) );
zbuf.total_out_lo32 += count;
out = (char*) out + count;
remain -= count;
if ( remain )
{
end();
break;
}
}
if ( !remain )
break;
RETURN_ERR( fill_buf( (int)(buf.size() - zbuf.total_out_lo32 % block_size) ) );
}
}
}
*count_io -= remain;
return blargg_ok;
}
#endif

View File

@ -0,0 +1,88 @@
// Simplifies use of zlib for inflating data
// File_Extractor 1.0.0
#ifndef BZ2_INFLATER_H
#define BZ2_INFLATER_H
#include <bzlib.h>
#include "Data_Reader.h"
#include "blargg_common.h"
class BZ2_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 (const unsigned char *)zbuf.next_in;
}
int filled() const
{
return zbuf.avail_in;
}
int totalOut() const
{
return zbuf.total_out_lo32;
}
// 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_unbz2, 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_lo32;
}
// Ends inflation and frees memory
void end();
private:
// noncopyable
BZ2_Inflater(const BZ2_Inflater &);
BZ2_Inflater &operator=(const BZ2_Inflater &);
// Implementation
public:
BZ2_Inflater();
~BZ2_Inflater();
private:
bz_stream zbuf;
blargg_vector<unsigned char> buf;
bool deflated_;
callback_t callback;
void *user_data;
blargg_err_t fill_buf(int count);
};
#endif

View File

@ -0,0 +1,74 @@
// File_Extractor 1.0.0. http://www.slack.net/~ant/
#if FEX_ENABLE_BZ2
#include "BZ2_Reader.h"
#include "blargg_endian.h"
/* Copyright (C) 2025 Andy Vandijck. 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"
BZ2_Reader::BZ2_Reader()
{
close();
}
BZ2_Reader::~BZ2_Reader()
{ }
static blargg_err_t BZ2_reader_read( void* file, void* out, int* count )
{
return STATIC_CAST(File_Reader*,file)->read_avail( out, count );
}
blargg_err_t BZ2_Reader::calc_size()
{
size_ = in->size() * 4;
crc32_ = 0;
return blargg_ok;
}
blargg_err_t BZ2_Reader::open( File_Reader* new_in )
{
close();
in = new_in;
RETURN_ERR( in->seek( 0 ) );
RETURN_ERR( inflater.begin( BZ2_reader_read, new_in ) );
RETURN_ERR( inflater.set_mode( inflater.mode_auto ) );
RETURN_ERR( calc_size() );
set_remain( size_ );
return blargg_ok;
}
void BZ2_Reader::close()
{
in = NULL;
inflater.end();
}
blargg_err_t BZ2_Reader::read_v( void* out, int count )
{
assert( in );
int actual = count;
RETURN_ERR( inflater.read( out, &actual ) );
if ( actual < count )
set_remain(0);
return blargg_ok;
}
#endif

View File

@ -0,0 +1,59 @@
// Transparently decompresses gzip files, as well as uncompressed
// File_Extractor 1.0.0
#ifndef BZ2_READER_H
#define BZ2_READER_H
#include "Data_Reader.h"
#include "BZ2_Inflater.h"
class BZ2_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
int tell() const
{
return size_ - remain();
}
public:
BZ2_Reader();
virtual ~BZ2_Reader();
protected:
virtual blargg_err_t read_v(void *, int);
private:
File_Reader *in;
unsigned crc32_;
int size_;
BZ2_Inflater inflater;
blargg_err_t calc_size();
};
#endif

View File

@ -213,7 +213,7 @@ struct fex_type_t_ {
blargg_err_t (*init)(); // Called by fex_init(). Can be NULL. 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_lz4_type[1], fex_rar_type[1], fex_zip_type[1], extern const fex_type_t_ fex_7z_type[1], fex_gz_type[1], fex_bz2_type[1], fex_rar_type[1], fex_zip_type[1],
fex_bin_type[1]; fex_bin_type[1];
inline blargg_err_t File_Extractor::open_v() inline blargg_err_t File_Extractor::open_v()

View File

@ -21,7 +21,13 @@
#define FEX_ENABLE_RAR 1 #define FEX_ENABLE_RAR 1
#endif #endif
#define FEX_TYPE_LIST fex_7z_type, fex_gz_type, fex_zip_type, fex_rar_type, #if FEX_ENABLE_BZ2
#define FEX_TYPE_BZ2 fex_bz2_type,
#else
#define FEX_TYPE_BZ2
#endif
#define FEX_TYPE_LIST fex_7z_type, fex_gz_type, fex_zip_type, fex_rar_type, FEX_TYPE_BZ2
// Use standard config.h if present // Use standard config.h if present
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H

View File

@ -33,6 +33,9 @@ BLARGG_EXPORT const fex_type_t* fex_type_list( void )
// Modify blargg_config.h to change type list, NOT this file // Modify blargg_config.h to change type list, NOT this file
fex_7z_type, fex_7z_type,
fex_gz_type, fex_gz_type,
#if FEX_ENABLE_BZ2
fex_bz2_type,
#endif
#if FEX_ENABLE_RAR #if FEX_ENABLE_RAR
fex_rar_type, fex_rar_type,
#endif #endif

View File

@ -132,11 +132,13 @@ EVT_HANDLER(wxID_OPEN, "Open ROM...")
wxString pats = _( wxString pats = _(
"Game Boy Advance Files (*.agb;*.gba;*.bin;*.elf;*.mb;*.zip;*.7z;*.rar)|" "Game Boy Advance Files (*.agb;*.gba;*.bin;*.elf;*.mb;*.zip;*.7z;*.rar)|"
"*.agb;*.gba;*.bin;*.elf;*.mb;" "*.agb;*.gba;*.bin;*.elf;*.mb;"
"*.agb.bz2;*.gba.bz2;*.bin.bz2;*.elf.bz2;*.mb.bz2;"
"*.agb.gz;*.gba.gz;*.bin.gz;*.elf.gz;*.mb.gz;" "*.agb.gz;*.gba.gz;*.bin.gz;*.elf.gz;*.mb.gz;"
"*.agb.z;*.gba.z;*.bin.z;*.elf.z;*.mb.z;" "*.agb.z;*.gba.z;*.bin.z;*.elf.z;*.mb.z;"
"*.zip;*.7z;*.rar|" "*.zip;*.7z;*.rar|"
"Game Boy Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|" "Game Boy Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|"
"*.dmg;*.gb;*.gbc;*.cgb;*.sgb;" "*.dmg;*.gb;*.gbc;*.cgb;*.sgb;"
"*.dmg.bz2;*.gb.bz2;*.gbc.bz2;*.cgb.bz2;*.sgb.bz2;"
"*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;" "*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;"
"*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;" "*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;"
"*.zip;*.7z;*.rar|"); "*.zip;*.7z;*.rar|");
@ -168,6 +170,7 @@ EVT_HANDLER(OpenGB, "Open GB...")
wxString pats = _( wxString pats = _(
"Game Boy Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|" "Game Boy Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|"
"*.dmg;*.gb;*.gbc;*.cgb;*.sgb;" "*.dmg;*.gb;*.gbc;*.cgb;*.sgb;"
"*.dmg.bz2;*.gb.bz2;*.gbc.bz2;*.cgb.bz2;*.sgb.bz2;"
"*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;" "*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;"
"*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;" "*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;"
"*.zip;*.7z;*.rar|"); "*.zip;*.7z;*.rar|");
@ -197,6 +200,7 @@ EVT_HANDLER(OpenGBC, "Open GBC...")
wxString pats = _( wxString pats = _(
"Game Boy Color Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|" "Game Boy Color Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb;*.zip;*.7z;*.rar)|"
"*.dmg;*.gb;*.gbc;*.cgb;*.sgb;" "*.dmg;*.gb;*.gbc;*.cgb;*.sgb;"
"*.dmg.bz2;*.gb.bz2;*.gbc.bz2;*.cgb.bz2;*.sgb.bz2;"
"*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;" "*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz;"
"*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;" "*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z;"
"*.zip;*.7z;*.rar|"); "*.zip;*.7z;*.rar|");