2009-04-19 18:07:22 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.Collections.Generic ;
using System.IO ;
using CodeImp.DoomBuilder.IO ;
2013-04-11 11:04:16 +00:00
using SharpCompress.Archive ; //mxd
using SharpCompress.Common ; //mxd
using SharpCompress.Reader ; //mxd
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.Data
{
internal sealed class PK3Reader : PK3StructuredReader
{
#region = = = = = = = = = = = = = = = = = = Variables
2014-10-15 08:36:17 +00:00
private readonly DirectoryFilesList files ;
private IArchive archive ; //mxd
private readonly ArchiveType archivetype ; //mxd
private readonly Dictionary < string , byte [ ] > sevenzipentries ; //mxd
private bool bathmode = true ; //mxd
#endregion
#region = = = = = = = = = = = = = = = = = = Properties ( mxd )
public bool BathMode { get { return bathmode ; } set { bathmode = value ; UpdateArchive ( bathmode ) ; } }
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
public PK3Reader ( DataLocation dl ) : base ( dl )
{
2013-09-11 09:47:53 +00:00
General . WriteLogLine ( "Opening " + Path . GetExtension ( location . location ) . ToUpper ( ) . Replace ( "." , "" ) + " resource '" + location . location + "'" ) ;
2010-10-03 16:54:38 +00:00
if ( ! File . Exists ( location . location ) )
throw new FileNotFoundException ( "Could not find the file \"" + location . location + "\"" , location . location ) ;
2009-04-19 18:07:22 +00:00
2013-09-11 09:47:53 +00:00
// Make list of all files
List < DirectoryFileEntry > fileentries = new List < DirectoryFileEntry > ( ) ;
2014-10-15 08:36:17 +00:00
// Create archive
archive = ArchiveFactory . Open ( location . location , Options . KeepStreamsOpen ) ;
archivetype = archive . Type ;
2013-09-11 09:47:53 +00:00
2014-10-15 08:36:17 +00:00
// Random access of 7z archives works TERRIBLY slow in SharpCompress
if ( archivetype = = ArchiveType . SevenZip )
{
sevenzipentries = new Dictionary < string , byte [ ] > ( StringComparer . Ordinal ) ;
2013-09-11 09:47:53 +00:00
IReader reader = archive . ExtractAllEntries ( ) ;
2014-10-15 08:36:17 +00:00
while ( reader . MoveToNextEntry ( ) )
{
2015-12-17 10:07:28 +00:00
if ( reader . Entry . IsDirectory | | ! CheckInvalidPathChars ( reader . Entry . Key ) ) continue ;
2014-10-15 08:36:17 +00:00
MemoryStream s = new MemoryStream ( ) ;
reader . WriteEntryTo ( s ) ;
2014-12-29 12:28:40 +00:00
sevenzipentries . Add ( reader . Entry . Key . ToLowerInvariant ( ) , s . ToArray ( ) ) ;
fileentries . Add ( new DirectoryFileEntry ( reader . Entry . Key ) ) ;
2013-09-11 09:47:53 +00:00
}
2014-10-15 08:36:17 +00:00
}
else
{
foreach ( IArchiveEntry entry in archive . Entries )
{
2015-12-17 10:07:28 +00:00
if ( ! entry . IsDirectory & & CheckInvalidPathChars ( entry . Key ) )
fileentries . Add ( new DirectoryFileEntry ( entry . Key ) ) ;
2013-09-11 09:47:53 +00:00
}
}
2012-07-23 21:28:23 +00:00
2015-01-20 12:20:35 +00:00
// Get rid of archive
archive . Dispose ( ) ;
archive = null ;
2013-09-11 09:47:53 +00:00
// Make files list
files = new DirectoryFilesList ( fileentries ) ;
2009-04-19 18:07:22 +00:00
// Initialize without path (because we use paths relative to the PK3 file)
Initialize ( ) ;
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
// Disposer
public override void Dispose ( )
{
// Not already disposed?
if ( ! isdisposed )
{
2013-09-11 09:47:53 +00:00
General . WriteLogLine ( "Closing " + Path . GetExtension ( location . location ) . ToUpper ( ) . Replace ( "." , "" ) + " resource '" + location . location + "'" ) ;
2014-10-15 08:36:17 +00:00
//mxd
if ( archive ! = null )
{
archive . Dispose ( ) ;
archive = null ;
}
2012-07-23 21:28:23 +00:00
2009-04-19 18:07:22 +00:00
// Done
base . Dispose ( ) ;
}
}
2014-10-15 08:36:17 +00:00
//mxd
private void UpdateArchive ( bool enable )
{
if ( archivetype = = ArchiveType . SevenZip ) return ;
2015-12-28 15:01:53 +00:00
if ( enable & & archive = = null )
2014-10-15 08:36:17 +00:00
{
2015-01-20 12:20:35 +00:00
archive = ArchiveFactory . Open ( location . location ) ;
2014-10-15 08:36:17 +00:00
}
else if ( ! enable & & ! bathmode & & archive ! = null )
{
archive . Dispose ( ) ;
archive = null ;
}
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Textures
// This finds and returns a patch stream
2014-11-25 11:52:01 +00:00
public override Stream GetPatchData ( string pname , bool longname )
2009-04-19 18:07:22 +00:00
{
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
// Find in any of the wad files
// Note the backward order, because the last wad's images have priority
2015-12-28 15:01:53 +00:00
if ( ! longname ) //mxd. Patches with long names can't be in wads
2009-04-19 18:07:22 +00:00
{
2015-12-28 15:01:53 +00:00
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
2014-11-25 11:52:01 +00:00
{
Stream data = wads [ i ] . GetPatchData ( pname , false ) ;
2015-12-28 15:01:53 +00:00
if ( data ! = null ) return data ;
2014-11-25 11:52:01 +00:00
}
}
else
{
//mxd. Long names are absolute
return ( FileExists ( pname ) ? LoadFile ( pname ) : null ) ;
2009-04-19 18:07:22 +00:00
}
2015-12-28 15:01:53 +00:00
if ( General . Map . Config . MixTexturesFlats )
Textures Browser form: empty texture sets are no longer shown when mixed textures & flats is disabled in the current game configuration.
Textures Browser form: PK3/Directory TEXTURES images are now shown in a separate folder in the resources tree.
Fixed, Textures Browser form: fixed a logic error when trying to select initial flat when mix textures & flats was disabled in the current game configuration (this resulted in the blank textures list after opening the form).
Fixed, Textures Browser form: resources tree showed textures count even when browsing flats.
Fixed, Textures Browser form: PK3/Directory textures took precedence even when browsing flats (this means when there were a flat and a texture with the same name, a texture was displayed when browsing flats).
Fixed, Classic modes: actor's scale set in DECORATE was ignored when rendering models.
Fixed, MODELDEF parser: in some cases, several model definitions were skipped when trying to skip the current one.
Fixed, resource management: flat and sprite TEXTURES definitions were loaded only from TEXTURES files named "TEXTURES".
Fixed/added, PK3/folder resource management: patch locations for sprites defined in TEXTURES are now checked the same way as in ZDoom (previously only the "sprites" folder was checked).
Fixed/added, PK3/folder resource management: patch locations for textures defined in TEXTURES are now checked the same way as in ZDoom (previously only the "textures" folder was checked).
Fixed, PK3/folder resource management: flats defined in TEXTURES were not added to the global Flats image list.
Fixed, PK3/folder resource management: in some cases, the image search algorithm could find flats, textures, patches or sprites in incorrect folders (for example, it could find a flat in "flats_backup" folder).
2014-10-07 00:23:02 +00:00
{
2014-10-07 08:56:21 +00:00
//mxd. Find in directories ZDoom expects them to be
2015-12-28 15:01:53 +00:00
foreach ( string loc in PatchLocations )
2014-10-07 08:56:21 +00:00
{
string filename = FindFirstFile ( loc , pname , true ) ;
2015-12-28 15:01:53 +00:00
if ( ( filename ! = null ) & & FileExists ( filename ) )
2014-10-07 08:56:21 +00:00
return LoadFile ( filename ) ;
}
}
else
{
// Find in patches directory
string filename = FindFirstFile ( PATCHES_DIR , pname , true ) ;
2013-07-29 08:50:50 +00:00
if ( ( filename ! = null ) & & FileExists ( filename ) )
return LoadFile ( filename ) ;
2009-04-19 18:07:22 +00:00
}
// Nothing found
return null ;
}
// This finds and returns a textue stream
2014-11-25 11:52:01 +00:00
public override Stream GetTextureData ( string pname , bool longname )
2009-04-19 18:07:22 +00:00
{
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
// Find in any of the wad files
// Note the backward order, because the last wad's images have priority
2015-12-28 15:01:53 +00:00
if ( ! longname ) //mxd. Textures with long names can't be in wads
2009-04-19 18:07:22 +00:00
{
2015-12-28 15:01:53 +00:00
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
2014-11-25 11:52:01 +00:00
{
Stream data = wads [ i ] . GetTextureData ( pname , false ) ;
2015-12-28 15:01:53 +00:00
if ( data ! = null ) return data ;
2014-11-25 11:52:01 +00:00
}
}
else
{
//mxd. Long names are absolute
return ( FileExists ( pname ) ? LoadFile ( pname ) : null ) ;
2009-04-19 18:07:22 +00:00
}
2014-10-13 09:32:55 +00:00
// Find in textures directory
string filename = FindFirstFile ( TEXTURES_DIR , pname , true ) ;
if ( ! string . IsNullOrEmpty ( filename ) & & FileExists ( filename ) )
return LoadFile ( filename ) ;
2009-04-19 18:07:22 +00:00
// Nothing found
return null ;
}
2009-05-12 09:50:08 +00:00
// This finds and returns a colormap stream
public override Stream GetColormapData ( string pname )
{
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
// Find in any of the wad files
// Note the backward order, because the last wad's images have priority
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
{
Stream data = wads [ i ] . GetColormapData ( pname ) ;
if ( data ! = null ) return data ;
}
// Find in patches directory
string filename = FindFirstFile ( COLORMAPS_DIR , pname , true ) ;
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
return LoadFile ( filename ) ;
}
// Nothing found
return null ;
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Sprites
// This finds and returns a sprite stream
public override Stream GetSpriteData ( string pname )
{
string pfilename = pname . Replace ( '\\' , '^' ) ;
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
// Find in any of the wad files
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
{
Stream sprite = wads [ i ] . GetSpriteData ( pname ) ;
if ( sprite ! = null ) return sprite ;
}
// Find in sprites directory
string filename = FindFirstFile ( SPRITES_DIR , pfilename , true ) ;
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
return LoadFile ( filename ) ;
}
// Nothing found
return null ;
}
// This checks if the given sprite exists
public override bool GetSpriteExists ( string pname )
{
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
2014-01-08 09:46:57 +00:00
string pfilename = pname . Replace ( '\\' , '^' ) ;
2009-04-19 18:07:22 +00:00
// Find in any of the wad files
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
{
if ( wads [ i ] . GetSpriteExists ( pname ) ) return true ;
}
// Find in sprites directory
string filename = FindFirstFile ( SPRITES_DIR , pfilename , true ) ;
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
return true ;
}
// Nothing found
return false ;
}
2014-01-03 10:33:45 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Voxels ( mxd )
2014-01-08 09:46:57 +00:00
//mxd. This finds and returns a voxel stream or null if no voxel was found
2014-11-25 11:52:01 +00:00
public override Stream GetVoxelData ( string name )
{
2014-01-03 10:33:45 +00:00
// Error when suspended
if ( issuspended ) throw new Exception ( "Data reader is suspended" ) ;
2014-01-08 09:46:57 +00:00
// Find in any of the wad files
2014-11-25 11:52:01 +00:00
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
{
2014-01-08 09:46:57 +00:00
Stream voxel = wads [ i ] . GetVoxelData ( name ) ;
if ( voxel ! = null ) return voxel ;
2014-01-03 10:33:45 +00:00
}
string pfilename = name . Replace ( '\\' , '^' ) ;
// Find in sprites directory
string filename = FindFirstFile ( VOXELS_DIR , pfilename , true ) ;
2014-11-25 11:52:01 +00:00
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
2014-01-08 09:46:57 +00:00
return LoadFile ( filename ) ;
2014-01-03 10:33:45 +00:00
}
// Nothing found
2014-01-08 09:46:57 +00:00
return null ;
2014-01-03 10:33:45 +00:00
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Methods
// Return a short name for this data location
public override string GetTitle ( )
{
return Path . GetFileName ( location . location ) ;
}
// This creates an image
2014-11-25 11:52:01 +00:00
protected override ImageData CreateImage ( string filename , int imagetype )
2009-04-19 18:07:22 +00:00
{
2009-05-12 09:50:08 +00:00
switch ( imagetype )
{
case ImageDataFormat . DOOMFLAT :
2014-11-25 11:52:01 +00:00
return new PK3FileImage ( this , filename , true ) ;
2009-05-12 09:50:08 +00:00
case ImageDataFormat . DOOMPICTURE :
2014-11-25 11:52:01 +00:00
return new PK3FileImage ( this , filename , false ) ;
2009-05-12 09:50:08 +00:00
case ImageDataFormat . DOOMCOLORMAP :
2014-11-25 11:52:01 +00:00
return new ColormapImage ( Path . GetFileNameWithoutExtension ( filename ) ) ;
2009-05-12 09:50:08 +00:00
default :
throw new ArgumentException ( "Invalid image format specified!" ) ;
}
2009-04-19 18:07:22 +00:00
}
// This returns true if the specified file exists
2012-07-23 21:28:23 +00:00
internal override bool FileExists ( string filename )
2009-04-19 18:07:22 +00:00
{
return files . FileExists ( filename ) ;
}
// This returns all files in a given directory
protected override string [ ] GetAllFiles ( string path , bool subfolders )
{
return files . GetAllFiles ( path , subfolders ) . ToArray ( ) ;
}
2010-08-13 15:19:51 +00:00
// This returns all files in a given directory that have the given title
protected override string [ ] GetAllFilesWithTitle ( string path , string title , bool subfolders )
{
return files . GetAllFilesWithTitle ( path , title , subfolders ) . ToArray ( ) ;
}
2012-08-05 19:18:05 +00:00
2013-09-11 09:47:53 +00:00
//mxd. This returns all files in a given directory which title starts with given title
2014-11-25 11:52:01 +00:00
protected override string [ ] GetAllFilesWhichTitleStartsWith ( string path , string title )
{
2013-09-11 09:47:53 +00:00
return files . GetAllFilesWhichTitleStartsWith ( path , title ) . ToArray ( ) ;
}
2010-08-13 15:19:51 +00:00
2009-04-19 18:07:22 +00:00
// This returns all files in a given directory that match the given extension
protected override string [ ] GetFilesWithExt ( string path , string extension , bool subfolders )
{
return files . GetAllFiles ( path , extension , subfolders ) . ToArray ( ) ;
}
// This finds the first file that has the specific name, regardless of file extension
protected override string FindFirstFile ( string beginswith , bool subfolders )
{
return files . GetFirstFile ( beginswith , subfolders ) ;
}
// This finds the first file that has the specific name, regardless of file extension
protected override string FindFirstFile ( string path , string beginswith , bool subfolders )
{
return files . GetFirstFile ( path , beginswith , subfolders ) ;
}
// This finds the first file that has the specific name
protected override string FindFirstFileWithExt ( string path , string beginswith , bool subfolders )
{
string title = Path . GetFileNameWithoutExtension ( beginswith ) ;
string ext = Path . GetExtension ( beginswith ) ;
2014-10-15 08:36:17 +00:00
ext = ( ! string . IsNullOrEmpty ( ext ) & & ext . Length > 1 ? ext . Substring ( 1 ) : "" ) ;
2009-04-19 18:07:22 +00:00
return files . GetFirstFile ( path , title , subfolders , ext ) ;
}
// This loads an entire file in memory and returns the stream
// NOTE: Callers are responsible for disposing the stream!
2013-09-11 09:47:53 +00:00
internal override MemoryStream LoadFile ( string filename )
2009-04-19 18:07:22 +00:00
{
2013-09-11 09:47:53 +00:00
MemoryStream filedata = null ;
2014-11-25 11:52:01 +00:00
string fn = filename . Replace ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ; //mxd
2014-10-15 08:36:17 +00:00
//mxd. This works waaaaaay faster with 7z archive
2015-12-17 10:07:28 +00:00
if ( archivetype = = ArchiveType . SevenZip )
2014-10-15 08:36:17 +00:00
{
2014-11-25 11:52:01 +00:00
fn = fn . ToLowerInvariant ( ) ;
2015-12-28 15:01:53 +00:00
if ( sevenzipentries . ContainsKey ( fn ) ) filedata = new MemoryStream ( sevenzipentries [ fn ] ) ;
2014-10-15 08:36:17 +00:00
}
else
{
2015-12-17 10:07:28 +00:00
lock ( this )
2014-10-15 08:36:17 +00:00
{
2015-05-28 10:04:42 +00:00
UpdateArchive ( true ) ;
2015-12-17 10:07:28 +00:00
foreach ( var entry in archive . Entries )
2014-10-15 08:36:17 +00:00
{
2015-05-28 10:04:42 +00:00
if ( entry . IsDirectory ) continue ;
// Is this the entry we are looking for?
if ( string . Compare ( entry . Key , fn , true ) = = 0 )
{
filedata = new MemoryStream ( ) ;
entry . WriteTo ( filedata ) ;
break ;
}
2013-09-11 09:47:53 +00:00
}
2014-10-15 08:36:17 +00:00
2015-05-28 10:04:42 +00:00
UpdateArchive ( false ) ;
}
2013-09-11 09:47:53 +00:00
}
2009-04-19 18:07:22 +00:00
// Nothing found?
2015-12-17 10:07:28 +00:00
if ( filedata = = null )
2014-10-15 08:36:17 +00:00
{
2013-03-18 13:52:27 +00:00
//mxd
2014-10-15 08:36:17 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Cannot find the file '" + filename + "' in archive '" + location . location + "'." ) ;
return null ;
2009-04-19 18:07:22 +00:00
}
2013-03-18 13:52:27 +00:00
2013-09-11 09:47:53 +00:00
filedata . Position = 0 ; //mxd. rewind before use
return filedata ;
2009-04-19 18:07:22 +00:00
}
// This creates a temp file for the speciied file and return the absolute path to the temp file
// NOTE: Callers are responsible for removing the temp file when done!
protected override string CreateTempFile ( string filename )
{
// Just copy the file
string tempfile = General . MakeTempFilename ( General . Map . TempPath , "wad" ) ;
MemoryStream filedata = LoadFile ( filename ) ;
File . WriteAllBytes ( tempfile , filedata . ToArray ( ) ) ;
filedata . Dispose ( ) ;
return tempfile ;
}
2015-12-17 10:07:28 +00:00
//mxd. This replicates System.IO.Path.CheckInvalidPathChars() internal function
private bool CheckInvalidPathChars ( string path )
{
foreach ( char c in path )
{
int num = c ;
switch ( num )
{
case 34 :
case 60 :
case 62 :
case 124 :
General . ErrorLogger . Add ( ErrorType . Error , "Error in \"" + location . location + "\": unsupported character \"" + c + "\" in path \"" + path + "\". File loading was skipped." ) ;
return false ;
default :
if ( num > = 32 ) continue ;
else goto case 34 ;
}
}
return true ;
}
2009-04-19 18:07:22 +00:00
#endregion
}
}