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 ;
2016-04-29 13:42:52 +00:00
using CodeImp.DoomBuilder.Compilers ;
2016-05-12 13:56:25 +00:00
using CodeImp.DoomBuilder.Config ;
2009-04-19 18:07:22 +00:00
using CodeImp.DoomBuilder.IO ;
2018-04-14 13:55:21 +00:00
using SharpCompress.Archives ;
using SharpCompress.Archives.Zip ;
2016-11-24 11:55:11 +00:00
using SharpCompress.Common ;
2018-04-14 13:55:21 +00:00
using SharpCompress.Readers ;
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.Data
{
internal sealed class PK3Reader : PK3StructuredReader
{
#region = = = = = = = = = = = = = = = = = = Variables
2017-01-21 01:13:36 +00:00
private /*readonly*/ DirectoryFilesList files ;
2014-10-15 08:36:17 +00:00
private IArchive archive ; //mxd
2017-01-21 01:13:36 +00:00
private /*readonly*/ ArchiveType archivetype ; //mxd
private /*readonly*/ Dictionary < string , byte [ ] > sevenzipentries ; //mxd
2019-12-29 14:38:53 +00:00
private bool batchmode = true ; //mxd
2020-09-27 13:05:27 +00:00
private FileStream filestream ;
2014-10-15 08:36:17 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Properties ( mxd )
2019-12-29 14:38:53 +00:00
public bool BatchMode
{
get
{
return batchmode ;
}
set
{
if ( batchmode ! = value )
{
batchmode = value ;
UpdateArchive ( batchmode ) ;
}
}
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
2022-09-18 17:54:53 +00:00
public PK3Reader ( DataLocation dl , GameConfiguration config , bool asreadonly ) : base ( dl , asreadonly )
2009-04-19 18:07:22 +00:00
{
2022-09-18 17:54:53 +00:00
LoadFrom ( dl , config , asreadonly ) ;
2009-04-19 18:07:22 +00:00
}
2022-09-18 17:54:53 +00:00
private void LoadFrom ( DataLocation dl , GameConfiguration config , bool asreadonly )
2017-01-21 01:13:36 +00:00
{
2020-09-27 13:05:27 +00:00
FileAccess access ;
FileShare share ;
isreadonly = asreadonly ;
2022-09-18 17:54:53 +00:00
config = config ;
2020-09-27 13:05:27 +00:00
// Determine if opening for read only
if ( isreadonly )
{
// Read only
access = FileAccess . Read ;
share = FileShare . ReadWrite ;
}
else
{
// Private access
access = FileAccess . ReadWrite ;
share = FileShare . Read ;
}
General . WriteLogLine ( "Opening " + Path . GetExtension ( location . location ) . ToUpper ( ) . Replace ( "." , "" ) + " resource \"" + location . location + "\"" ) ;
2017-01-21 01:13:36 +00:00
if ( ! File . Exists ( location . location ) )
throw new FileNotFoundException ( "Could not find the file \"" + location . location + "\"" , location . location ) ;
// Make list of all files
List < DirectoryFileEntry > fileentries = new List < DirectoryFileEntry > ( ) ;
2020-09-27 13:05:27 +00:00
// Take the detour with a FileStream because SharpCompress doesn't directly support opening files as read-only
filestream = File . Open ( location . location , FileMode . OpenOrCreate , access , share ) ;
// Create archive
archive = ArchiveFactory . Open ( filestream ) ;
2017-01-21 01:13:36 +00:00
archivetype = archive . Type ;
// Random access of 7z archives works TERRIBLY slow in SharpCompress
if ( archivetype = = ArchiveType . SevenZip )
{
isreadonly = true ; // Unsaveable...
sevenzipentries = new Dictionary < string , byte [ ] > ( StringComparer . Ordinal ) ;
IReader reader = archive . ExtractAllEntries ( ) ;
while ( reader . MoveToNextEntry ( ) )
{
if ( reader . Entry . IsDirectory | | ! CheckInvalidPathChars ( reader . Entry . Key ) ) continue ;
MemoryStream s = new MemoryStream ( ) ;
reader . WriteEntryTo ( s ) ;
sevenzipentries . Add ( reader . Entry . Key . ToLowerInvariant ( ) , s . ToArray ( ) ) ;
fileentries . Add ( new DirectoryFileEntry ( reader . Entry . Key ) ) ;
}
2019-12-31 11:35:43 +00:00
}
2017-01-21 01:13:36 +00:00
else
{
foreach ( IArchiveEntry entry in archive . Entries )
{
if ( ! entry . IsDirectory & & CheckInvalidPathChars ( entry . Key ) )
fileentries . Add ( new DirectoryFileEntry ( entry . Key ) ) ;
}
}
// Get rid of archive
archive . Dispose ( ) ;
archive = null ;
2020-09-27 13:05:27 +00:00
filestream . Dispose ( ) ;
filestream = null ;
2017-01-21 01:13:36 +00:00
// Make files list
2022-09-18 17:54:53 +00:00
files = new DirectoryFilesList ( dl . GetDisplayName ( ) , config , Silent , fileentries ) ;
2017-01-21 01:13:36 +00:00
// Initialize without path (because we use paths relative to the PK3 file)
2022-09-18 17:54:53 +00:00
Initialize ( config ) ;
2017-01-21 01:13:36 +00:00
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
2009-04-19 18:07:22 +00:00
// Disposer
public override void Dispose ( )
{
// Not already disposed?
if ( ! isdisposed )
{
Added, Sector Edit window, UDMF: added UI for sector damage-realted properties.
Added, DECORATE parser: damage types are now parsed.
Added: the editor now reports duplicate textures/flats/patches/sprites/colormaps/voxels in the loaded wads.
Added, all text parsers: added #region/#endregion support.
Added TERRAIN parser.
Added, Script Editor: added special handling for DECORATE special comments.
Added, Sector Edit window, UDMF: Soundsequence value was setup incorrectly when showing the window for multiple sectors with mixed Soundsequence value.
Fixed, Map Options window: "Strictly load patches between P_START and P_END" was not applied when applying the changes.
Fixed, MAPINFO parser: MapInfo should be treated as defined when a map MAPINFO block corresponding to current map is encountered even if it doesn't define any properties recognized by the editor.
Fixed, all text parsers: in some cases error line was calculated incorrectly when reporting an error detected by a text parser.
Cosmetic: changed ' to " in the rest of Error and Warning messages.
Internal: added text resource tracking.
Updated ZDoom_DECORATE.cfg.
Updated documentation ("Game Configuration - Basic Settings" page).
2016-02-22 12:33:19 +00:00
General . WriteLogLine ( "Closing " + Path . GetExtension ( location . location ) . ToUpper ( ) . Replace ( "." , "" ) + " resource \"" + location . location + "\"" ) ;
//mxd. Remove temp files
foreach ( WADReader wr in wads )
{
try { File . Delete ( wr . Location . location ) ; }
catch ( Exception ) { }
}
2014-10-15 08:36:17 +00:00
//mxd
if ( archive ! = null )
{
archive . Dispose ( ) ;
archive = null ;
}
2012-07-23 21:28:23 +00:00
2020-09-27 13:05:27 +00:00
if ( filestream ! = null )
{
filestream . Dispose ( ) ;
filestream = null ;
}
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 )
{
2019-12-31 11:46:30 +00:00
lock ( this )
2014-10-15 08:36:17 +00:00
{
2019-12-31 11:46:30 +00:00
if ( archivetype = = ArchiveType . SevenZip ) return ;
if ( enable & & archive = = null )
{
2020-09-27 13:05:27 +00:00
FileAccess access ;
FileShare share ;
// Determine if opening for read only
if ( isreadonly )
{
// Read only
access = FileAccess . Read ;
share = FileShare . ReadWrite ;
}
else
{
// Private access
access = FileAccess . ReadWrite ;
share = FileShare . Read ;
}
// The file might have vanished in the meantime
if ( ! File . Exists ( location . location ) )
throw new FileNotFoundException ( "Could not find the file \"" + location . location + "\"" , location . location ) ;
// Take the detour with a FileStream because SharpCompress doesn't directly support opening files as read-only
filestream = File . Open ( location . location , FileMode . OpenOrCreate , access , share ) ;
archive = ArchiveFactory . Open ( filestream ) ;
2019-12-31 11:46:30 +00:00
}
else if ( ! enable & & ! batchmode & & archive ! = null )
2019-12-31 11:35:43 +00:00
{
archive . Dispose ( ) ;
archive = null ;
2020-09-27 13:05:27 +00:00
filestream . Dispose ( ) ;
filestream = null ;
2019-12-31 11:35:43 +00:00
}
2014-10-15 08:36:17 +00:00
}
}
2017-01-21 01:13:36 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Textures
2009-04-19 18:07:22 +00:00
2017-01-21 01:13:36 +00:00
// This finds and returns a patch stream
public override Stream GetPatchData ( string pname , bool longname , ref string patchlocation )
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
{
2016-03-14 21:53:53 +00:00
Stream data = wads [ i ] . GetPatchData ( pname , false , ref patchlocation ) ;
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
2016-03-14 21:53:53 +00:00
if ( FileExists ( pname ) )
{
patchlocation = location . GetDisplayName ( ) ;
return LoadFile ( pname ) ;
}
return 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 ) ;
2016-03-14 21:53:53 +00:00
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
patchlocation = location . GetDisplayName ( ) ;
2014-10-07 08:56:21 +00:00
return LoadFile ( filename ) ;
2016-03-14 21:53:53 +00:00
}
2014-10-07 08:56:21 +00:00
}
}
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 ) )
2016-03-14 21:53:53 +00:00
{
patchlocation = location . GetDisplayName ( ) ;
2013-07-29 08:50:50 +00:00
return LoadFile ( filename ) ;
2016-03-14 21:53:53 +00:00
}
2009-04-19 18:07:22 +00:00
}
// Nothing found
return null ;
}
// This finds and returns a textue stream
2016-03-14 21:53:53 +00:00
public override Stream GetTextureData ( string pname , bool longname , ref string texturelocation )
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
{
2016-03-14 21:53:53 +00:00
Stream data = wads [ i ] . GetTextureData ( pname , false , ref texturelocation ) ;
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
2016-03-14 21:53:53 +00:00
if ( FileExists ( pname ) )
{
texturelocation = location . GetDisplayName ( ) ;
return LoadFile ( pname ) ;
}
return 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 ) )
2016-03-14 21:53:53 +00:00
{
texturelocation = location . GetDisplayName ( ) ;
2016-03-04 08:10:56 +00:00
return LoadFile ( filename ) ;
2016-03-14 21:53:53 +00:00
}
2016-03-04 08:10:56 +00:00
// Nothing found
return null ;
}
//mxd. This finds and returns a HiRes textue stream
2016-03-22 22:24:33 +00:00
public override Stream GetHiResTextureData ( string name , ref string hireslocation )
2016-03-04 08:10:56 +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
for ( int i = wads . Count - 1 ; i > = 0 ; i - - )
{
2016-03-22 22:24:33 +00:00
Stream data = wads [ i ] . GetTextureData ( name , false , ref hireslocation ) ;
2016-03-04 08:10:56 +00:00
if ( data ! = null ) return data ;
}
// Find in HiRes directory
2016-03-22 22:24:33 +00:00
string filename = FindFirstFile ( HIRES_DIR , name , true ) ;
2016-03-04 08:10:56 +00:00
if ( ! string . IsNullOrEmpty ( filename ) & & FileExists ( filename ) )
2016-03-14 21:53:53 +00:00
{
hireslocation = location . GetDisplayName ( ) ;
2014-10-13 09:32:55 +00:00
return LoadFile ( filename ) ;
2016-03-14 21:53:53 +00:00
}
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
2016-03-14 21:53:53 +00:00
public override Stream GetSpriteData ( string pname , ref string spritelocation )
2009-04-19 18:07:22 +00:00
{
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 - - )
{
2016-03-14 21:53:53 +00:00
Stream sprite = wads [ i ] . GetSpriteData ( pname , ref spritelocation ) ;
2009-04-19 18:07:22 +00:00
if ( sprite ! = null ) return sprite ;
}
// Find in sprites directory
string filename = FindFirstFile ( SPRITES_DIR , pfilename , true ) ;
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
2016-03-14 21:53:53 +00:00
spritelocation = location . GetDisplayName ( ) ; //mxd
2009-04-19 18:07:22 +00:00
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
2016-07-11 22:13:43 +00:00
public override Stream GetVoxelData ( string name , ref string voxellocation )
2014-11-25 11:52:01 +00:00
{
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 - - )
{
2016-07-11 22:13:43 +00:00
Stream voxel = wads [ i ] . GetVoxelData ( name , ref voxellocation ) ;
2014-01-08 09:46:57 +00:00
if ( voxel ! = null ) return voxel ;
2014-01-03 10:33:45 +00:00
}
2024-03-03 16:43:22 +00:00
name = name . Replace ( '\\' , '^' ) ;
2014-01-03 10:33:45 +00:00
2024-03-03 16:43:22 +00:00
try
2014-11-25 11:52:01 +00:00
{
2024-03-03 16:43:22 +00:00
// Find in voxels directory
string filename = null ;
// There are different places we have to look for the file, depending on how it was defined. Right now
// there's no way to know if the definition comes from VOXELDEF or spreite replacement, so we're assuming
// it's from VOXELDEF if there's an extension given.
// - Auto-detect for sprite name: must be in the "voxels" directory
// - Just given as file name without path in VOXELDEF: must be in the root directory
// - Given as full path and file name in VOXELDEF: must be in exactly that spot
string path = Path . GetDirectoryName ( name ) ;
if ( string . IsNullOrWhiteSpace ( path ) )
{
if ( string . IsNullOrWhiteSpace ( Path . GetExtension ( name ) ) )
filename = FindFirstFile ( VOXELS_DIR , Path . GetFileName ( name ) , true ) ;
else
filename = FindFirstFileWithExt ( "" , Path . GetFileName ( name ) , true ) ;
}
else
{
filename = FindFirstFileWithExt ( path , Path . GetFileName ( name ) , false ) ;
}
if ( ( filename ! = null ) & & FileExists ( filename ) )
{
voxellocation = location . GetDisplayName ( ) ;
return LoadFile ( filename ) ;
}
}
catch ( Exception e )
{
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , e . GetType ( ) . Name + " while loading voxel \"" + name + "\" from PK3: " + e . Message ) ;
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
2016-04-29 13:42:52 +00:00
internal override bool FileExists ( string filename , int unused ) { return files . FileExists ( filename ) ; }
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 ( ) ;
}
2016-04-23 23:15:43 +00:00
//mxd. This returns wad files in the root directory
protected override string [ ] GetWadFiles ( )
{
return files . GetWadFiles ( ) . 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
Added, Sector Edit window, UDMF: added UI for sector damage-realted properties.
Added, DECORATE parser: damage types are now parsed.
Added: the editor now reports duplicate textures/flats/patches/sprites/colormaps/voxels in the loaded wads.
Added, all text parsers: added #region/#endregion support.
Added TERRAIN parser.
Added, Script Editor: added special handling for DECORATE special comments.
Added, Sector Edit window, UDMF: Soundsequence value was setup incorrectly when showing the window for multiple sectors with mixed Soundsequence value.
Fixed, Map Options window: "Strictly load patches between P_START and P_END" was not applied when applying the changes.
Fixed, MAPINFO parser: MapInfo should be treated as defined when a map MAPINFO block corresponding to current map is encountered even if it doesn't define any properties recognized by the editor.
Fixed, all text parsers: in some cases error line was calculated incorrectly when reporting an error detected by a text parser.
Cosmetic: changed ' to " in the rest of Error and Warning messages.
Internal: added text resource tracking.
Updated ZDoom_DECORATE.cfg.
Updated documentation ("Game Configuration - Basic Settings" page).
2016-02-22 12:33:19 +00:00
protected override string [ ] GetAllFilesWhichTitleStartsWith ( string path , string title , bool subfolders )
2014-11-25 11:52:01 +00:00
{
Added, Sector Edit window, UDMF: added UI for sector damage-realted properties.
Added, DECORATE parser: damage types are now parsed.
Added: the editor now reports duplicate textures/flats/patches/sprites/colormaps/voxels in the loaded wads.
Added, all text parsers: added #region/#endregion support.
Added TERRAIN parser.
Added, Script Editor: added special handling for DECORATE special comments.
Added, Sector Edit window, UDMF: Soundsequence value was setup incorrectly when showing the window for multiple sectors with mixed Soundsequence value.
Fixed, Map Options window: "Strictly load patches between P_START and P_END" was not applied when applying the changes.
Fixed, MAPINFO parser: MapInfo should be treated as defined when a map MAPINFO block corresponding to current map is encountered even if it doesn't define any properties recognized by the editor.
Fixed, all text parsers: in some cases error line was calculated incorrectly when reporting an error detected by a text parser.
Cosmetic: changed ' to " in the rest of Error and Warning messages.
Internal: added text resource tracking.
Updated ZDoom_DECORATE.cfg.
Updated documentation ("Game Configuration - Basic Settings" page).
2016-02-22 12:33:19 +00:00
return files . GetAllFilesWhichTitleStartsWith ( path , title , subfolders ) . ToArray ( ) ;
2013-09-11 09:47:53 +00:00
}
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
2016-11-24 21:09:24 +00:00
internal override string [ ] GetFilesWithExt ( string path , string extension , bool subfolders )
2009-04-19 18:07:22 +00:00
{
return files . GetAllFiles ( path , extension , subfolders ) . ToArray ( ) ;
}
// This finds the first file that has the specific name, regardless of file extension
2016-01-13 09:34:32 +00:00
internal override string FindFirstFile ( string beginswith , bool subfolders )
2009-04-19 18:07:22 +00:00
{
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
{
2019-12-31 11:46:30 +00:00
lock ( this )
2014-10-15 08:36:17 +00:00
{
2015-05-28 10:04:42 +00:00
UpdateArchive ( true ) ;
2019-12-31 11:46:30 +00:00
foreach ( var entry in archive . Entries )
2014-10-15 08:36:17 +00:00
{
2019-12-31 11:46:30 +00:00
if ( entry . IsDirectory ) continue ;
2015-05-28 10:04:42 +00:00
2019-12-31 11:46:30 +00:00
// Is this the entry we are looking for?
if ( string . Compare ( entry . Key , fn , true ) = = 0 )
2015-05-28 10:04:42 +00:00
{
2019-12-31 11:46:30 +00:00
filedata = new MemoryStream ( ) ;
2020-09-27 13:05:27 +00:00
try
{
entry . WriteTo ( filedata ) ;
}
2021-09-06 21:43:30 +00:00
catch ( SharpCompress . Compressors . Deflate . ZlibException )
2020-09-27 13:05:27 +00:00
{
// This happens when the PK3 was modified externally and the resources were not reloaded
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , "Cannot load the file \"" + filename + "\" from archive \"" + location . GetDisplayName ( ) + "\". Did you modify the archive without reloading the resouces?" ) ;
2020-09-27 13:05:27 +00:00
filedata . Dispose ( ) ;
filedata = null ;
return null ;
}
2021-08-01 09:10:51 +00:00
catch ( NotSupportedException e )
{
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , "Cannot load the file \"" + filename + "\" from archive \"" + location . GetDisplayName ( ) + "\". " + e . Message ) ;
2021-08-01 09:10:51 +00:00
filedata . Dispose ( ) ;
filedata = null ;
return null ;
}
2020-09-27 13:05:27 +00:00
2019-12-31 11:46:30 +00:00
break ;
2015-05-28 10:04:42 +00:00
}
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
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , "Cannot find the file \"" + filename + "\" in archive \"" + location . GetDisplayName ( ) + "\"." ) ;
2014-10-15 08:36:17 +00:00
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
}
2016-11-24 11:55:11 +00:00
//mxd
2016-04-29 13:42:52 +00:00
internal override bool SaveFile ( MemoryStream stream , string filename , int unused ) { return SaveFile ( stream , filename ) ; }
internal override bool SaveFile ( MemoryStream stream , string filename )
{
// Not implemented in SevenZipArchive...
if ( isreadonly | | archivetype = = ArchiveType . SevenZip ) return false ;
2016-11-24 11:55:11 +00:00
// Convert slashes...
filename = filename . Replace ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
// Check if target file is locked...
var checkresult = FileLockChecker . CheckFile ( location . location ) ;
if ( ! string . IsNullOrEmpty ( checkresult . Error ) )
2016-04-29 13:42:52 +00:00
{
2016-11-24 11:55:11 +00:00
string errmsg = "Unable to save file \"" + filename + "\" into archive \"" + location . GetDisplayName ( ) + "\"." ;
if ( checkresult . Processes . Count > 0 )
{
string processpath = string . Empty ;
try
{
// All manner of exceptions are possible here...
processpath = checkresult . Processes [ 0 ] . MainModule . FileName ;
}
catch { }
errmsg + = " Archive is locked by " + checkresult . Processes [ 0 ] . ProcessName
+ " (" + ( ! string . IsNullOrEmpty ( processpath ) ? "\"" + processpath + "\"" : "" )
+ ", started at " + checkresult . Processes [ 0 ] . StartTime + ")." ;
}
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , errmsg ) ;
2016-11-24 11:55:11 +00:00
return false ;
}
using ( MemoryStream savestream = new MemoryStream ( ) )
{
using ( ZipArchive za = ( ZipArchive ) ArchiveFactory . Open ( location . location ) )
{
if ( za = = null )
{
string errmsg = "Unable to save file \"" + filename + "\" into archive \"" + location . GetDisplayName ( ) + "\". Unable to open target file as a zip archive." ;
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , errmsg ) ;
2016-11-24 11:55:11 +00:00
return false ;
}
// Find and remove original entry...
foreach ( ZipArchiveEntry entry in za . Entries )
{
if ( ! entry . IsDirectory & & entry . Key = = filename )
{
za . RemoveEntry ( entry ) ;
break ;
}
}
// Add new entry and save the archive to stream...
za . AddEntry ( filename , stream , 0L , DateTime . Now ) ;
za . SaveTo ( savestream , CompressionType . Deflate ) ;
}
2016-04-29 13:42:52 +00:00
2016-11-24 11:55:11 +00:00
// Replace archive file...
File . WriteAllBytes ( location . location , savestream . ToArray ( ) ) ;
2016-04-29 13:42:52 +00:00
}
2016-12-22 00:03:58 +00:00
// Rewind the stream (because DirectoryReader/WADReader don't modify stream Position in SaveFile)
stream . Position = 0 ;
2016-04-29 13:42:52 +00:00
return true ;
}
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 :
2022-09-18 17:54:53 +00:00
if ( ! Silent ) General . ErrorLogger . Add ( ErrorType . Error , "Error in \"" + location . GetDisplayName ( ) + "\": unsupported character \"" + c + "\" in path \"" + path + "\". File loading was skipped." ) ;
2015-12-17 10:07:28 +00:00
return false ;
default :
if ( num > = 32 ) continue ;
else goto case 34 ;
}
}
return true ;
}
2009-04-19 18:07:22 +00:00
#endregion
2016-04-29 13:42:52 +00:00
#region = = = = = = = = = = = = = = = = = = Compiling ( mxd )
// This compiles a script lump and returns any errors that may have occurred
// Returns true when our code worked properly (even when the compiler returned errors)
2016-05-12 13:56:25 +00:00
internal override bool CompileLump ( string filename , int unused , ScriptConfiguration scriptconfig , List < CompilerError > errors ) { return CompileLump ( filename , scriptconfig , errors ) ; }
internal override bool CompileLump ( string filename , ScriptConfiguration scriptconfig , List < CompilerError > errors )
2016-04-29 13:42:52 +00:00
{
2016-05-12 13:56:25 +00:00
// No compiling required
if ( scriptconfig . Compiler = = null ) return true ;
// Initialize compiler
Compiler compiler ;
try
{
compiler = scriptconfig . Compiler . Create ( ) ;
}
catch ( Exception e )
{
// Fail
errors . Add ( new CompilerError ( "Unable to initialize compiler. " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
return false ;
}
// Extract the source file into the temporary directory
string inputfile = Path . Combine ( compiler . Location , Path . GetFileName ( filename ) ) ;
using ( MemoryStream stream = LoadFile ( filename ) )
{
File . WriteAllBytes ( inputfile , stream . ToArray ( ) ) ;
}
// Make random output filename
string outputfile = General . MakeTempFilename ( compiler . Location , "tmp" ) ;
// Run compiler
compiler . Parameters = scriptconfig . Parameters ;
compiler . InputFile = inputfile ;
compiler . OutputFile = Path . GetFileName ( outputfile ) ;
compiler . SourceFile = inputfile ;
compiler . WorkingDirectory = Path . GetDirectoryName ( inputfile ) ;
if ( compiler . Run ( ) )
{
// Fetch errors
foreach ( CompilerError e in compiler . Errors )
{
CompilerError newerr = e ;
// If the error's filename equals our temporary file, // replace it with the original source filename
if ( String . Compare ( e . filename , inputfile , true ) = = 0 ) newerr . filename = filename ;
errors . Add ( newerr ) ;
}
// No errors and output file exists?
if ( compiler . Errors . Length = = 0 )
{
// Output file exists?
if ( ! File . Exists ( outputfile ) )
{
// Fail
compiler . Dispose ( ) ;
errors . Add ( new CompilerError ( "Output file \"" + outputfile + "\" doesn't exist." ) ) ;
return false ;
}
//mxd. Move and rename the result file
string targetfilename ;
if ( compiler is AccCompiler )
{
AccCompiler acccompiler = ( AccCompiler ) compiler ;
targetfilename = Path . Combine ( Path . GetDirectoryName ( filename ) , acccompiler . Parser . LibraryName + ".o" ) ;
}
else
{
//mxd. No can't do...
if ( String . IsNullOrEmpty ( scriptconfig . ResultLump ) )
{
// Fail
compiler . Dispose ( ) ;
errors . Add ( new CompilerError ( "Unable to create target file: unable to determine target filename. Make sure \"ResultLump\" property is set in the \"" + scriptconfig + "\" script configuration." ) ) ;
return false ;
}
targetfilename = Path . Combine ( Path . GetDirectoryName ( filename ) , scriptconfig . ResultLump ) ;
}
// Rename and add to source archive
try
{
byte [ ] buffer = File . ReadAllBytes ( outputfile ) ;
using ( MemoryStream stream = new MemoryStream ( buffer . Length ) )
{
stream . Write ( buffer , 0 , buffer . Length ) ;
SaveFile ( stream , targetfilename ) ;
}
}
catch ( Exception e )
{
// Fail
compiler . Dispose ( ) ;
errors . Add ( new CompilerError ( "Unable to create library file \"" + targetfilename + "\". " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
return false ;
}
}
// Done
compiler . Dispose ( ) ;
return true ;
}
// Fail
compiler . Dispose ( ) ;
return false ;
2016-04-29 13:42:52 +00:00
}
#endregion
2009-04-19 18:07:22 +00:00
}
}