diff --git a/Source/Builder.csproj b/Source/Builder.csproj index 525c58da..4b733c10 100644 --- a/Source/Builder.csproj +++ b/Source/Builder.csproj @@ -650,6 +650,8 @@ + + diff --git a/Source/Data/DataManager.cs b/Source/Data/DataManager.cs index fe554b13..4f9ca0cc 100644 --- a/Source/Data/DataManager.cs +++ b/Source/Data/DataManager.cs @@ -243,9 +243,11 @@ namespace CodeImp.DoomBuilder.Data break; } } - catch(Exception) + catch(Exception e) { // Unable to load resource + General.WriteLogLine("ERROR while creating data reader. " + e.GetType().Name + ": " + e.Message); + General.WriteLogLine(e.StackTrace); General.ShowErrorMessage("Unable to load resources from location \"" + dl.location + "\". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK); continue; } @@ -361,9 +363,11 @@ namespace CodeImp.DoomBuilder.Data General.WriteLogLine("Resumed data resource '" + d.Location.location + "'"); d.Resume(); } - catch(Exception) + catch(Exception e) { // Unable to load resource + General.WriteLogLine("ERROR while resuming data reader. " + e.GetType().Name + ": " + e.Message); + General.WriteLogLine(e.StackTrace); General.ShowErrorMessage("Unable to load resources from location \"" + d.Location.location + "\". Please make sure the location is accessible and not in use by another program.", MessageBoxButtons.OK); } } @@ -891,7 +895,7 @@ namespace CodeImp.DoomBuilder.Data string[] files = Directory.GetFiles(General.SpritesPath, "*.png", SearchOption.TopDirectoryOnly); foreach(string spritefile in files) { - ImageData img = new FileImage(Path.GetFileNameWithoutExtension(spritefile).ToLowerInvariant(), spritefile); + ImageData img = new FileImage(Path.GetFileNameWithoutExtension(spritefile).ToLowerInvariant(), spritefile, false); img.LoadImage(); internalsprites.Add(img.Name, img); } diff --git a/Source/Data/DirectoryReader.cs b/Source/Data/DirectoryReader.cs index 10401b0b..1383ca17 100644 --- a/Source/Data/DirectoryReader.cs +++ b/Source/Data/DirectoryReader.cs @@ -30,66 +30,17 @@ using CodeImp.DoomBuilder.IO; namespace CodeImp.DoomBuilder.Data { - internal sealed class DirectoryReader : DataReader + internal sealed class DirectoryReader : PK3StructuredReader { - #region ================== Constants - - private const string PATCHES_DIR = "patches"; - private const string TEXTURES_DIR = "textures"; - private const string FLATS_DIR = "flats"; - private const string HIRES_DIR = "hires"; - private const string SPRITES_DIR = "sprites"; - - #endregion - - #region ================== Variables - - // Source - private bool roottextures; - private bool rootflats; - - // Paths - private string rootpath; - private string patchespath; - private string texturespath; - private string flatspath; - private string hirespath; - private string spritespath; - - // WAD files that must be loaded as well - private List wads; - - #endregion - - #region ================== Properties - - #endregion - #region ================== Constructor / Disposer // Constructor public DirectoryReader(DataLocation dl) : base(dl) { - // Initialize - this.roottextures = dl.textures; - this.rootflats = dl.flats; - this.rootpath = dl.location; - this.patchespath = Path.Combine(rootpath, PATCHES_DIR); - this.texturespath = Path.Combine(rootpath, TEXTURES_DIR); - this.flatspath = Path.Combine(rootpath, FLATS_DIR); - this.hirespath = Path.Combine(rootpath, HIRES_DIR); - this.spritespath = Path.Combine(rootpath, SPRITES_DIR); - General.WriteLogLine("Opening directory resource '" + location.location + "'"); - // Load all WAD files in the root as WAD resources - string[] wadfiles = Directory.GetFiles(rootpath, "*.wad", SearchOption.TopDirectoryOnly); - wads = new List(wadfiles.Length); - foreach(string w in wadfiles) - { - DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, w, false, false); - wads.Add(new WADReader(wdl)); - } + // Initialize + Initialize(dl.location); // We have no destructor GC.SuppressFinalize(this); @@ -101,9 +52,6 @@ namespace CodeImp.DoomBuilder.Data // Not already disposed? if(!isdisposed) { - // Clean up - foreach(WADReader wr in wads) wr.Dispose(); - General.WriteLogLine("Closing directory resource '" + location.location + "'"); // Done @@ -113,275 +61,62 @@ namespace CodeImp.DoomBuilder.Data #endregion - #region ================== Management - - // This suspends use of this resource - public override void Suspend() - { - foreach(WADReader wr in wads) wr.Suspend(); - base.Suspend(); - } - - // This resumes use of this resource - public override void Resume() - { - foreach(WADReader wr in wads) wr.Resume(); - base.Resume(); - } - - #endregion - - #region ================== Palette - - // This loads the PLAYPAL palette - public override Playpal LoadPalette() - { - // Error when suspended - if(issuspended) throw new Exception("Data reader is suspended"); - - // Palette from wad(s) - Playpal palette = null; - foreach(WADReader wr in wads) - { - Playpal wadpalette = wr.LoadPalette(); - if(wadpalette != null) palette = wadpalette; - } - - // Done - return palette; - } - - #endregion - - #region ================== Textures - - // This loads the textures - public override ICollection LoadTextures(PatchNames pnames) - { - List images = new List(); - ICollection collection; - - // Error when suspended - if(issuspended) throw new Exception("Data reader is suspended"); - - // Load from wad files (NOTE: backward order, because the last wad's images have priority) - for(int i = wads.Count - 1; i >= 0; i--) - { - collection = wads[i].LoadTextures(pnames); - AddImagesToList(images, collection); - } - - // Should we load the images in this directory as textures? - if(roottextures) - { - collection = LoadDirectoryImages(rootpath); - AddImagesToList(images, collection); - } - - // TODO: Add support for hires texture here - - // Add images from texture directory - collection = LoadDirectoryImages(texturespath); - AddImagesToList(images, collection); - - return images; - } - - // This returns the patch names from the PNAMES lump - // A directory resource does not support this lump, but the wads in the directory may contain this lump - public override PatchNames LoadPatchNames() - { - // Error when suspended - if(issuspended) throw new Exception("Data reader is suspended"); - - // Load from wad files (NOTE: backward order, because the last wad's images have priority) - for(int i = wads.Count - 1; i >= 0; i--) - { - PatchNames pnames = wads[i].LoadPatchNames(); - if(pnames != null) return pnames; - } - - return null; - } - - // This finds and returns a patch stream - public override Stream GetPatchData(string pname) - { - string filename; - - // 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 data = wads[i].GetPatchData(pname); - if(data != null) return data; - } - - // Find in patches directory - string datafile = null; - filename = Path.Combine(patchespath, pname + ".bmp"); - if(File.Exists(filename)) datafile = filename; - filename = Path.Combine(patchespath, pname + ".gif"); - if(File.Exists(filename)) datafile = filename; - filename = Path.Combine(patchespath, pname + ".png"); - if(File.Exists(filename)) datafile = filename; - filename = Path.Combine(patchespath, pname); - if(File.Exists(filename)) datafile = filename; - - // Found anything? - if(datafile != null) - { - byte[] filedata = File.ReadAllBytes(datafile); - MemoryStream mem = new MemoryStream(filedata); - return mem; - } - - // Nothing found - return null; - } - - #endregion - - #region ================== Flats - - // This loads the textures - public override ICollection LoadFlats() - { - List images = new List(); - ICollection collection; - - // Error when suspended - if(issuspended) throw new Exception("Data reader is suspended"); - - // Load from wad files (NOTE: backward order, because the last wad's images have priority) - for(int i = wads.Count - 1; i >= 0; i--) - { - collection = wads[i].LoadFlats(); - AddImagesToList(images, collection); - } - - // Should we load the images in this directory as flats? - if(rootflats) - { - collection = LoadDirectoryImages(rootpath); - AddImagesToList(images, collection); - } - - // Add images from flats directory - collection = LoadDirectoryImages(flatspath); - AddImagesToList(images, collection); - - return images; - } - - #endregion - - #region ================== Sprites - - // This finds and returns a sprite stream - public override Stream GetSpriteData(string pname) - { - string pfilename = pname.Replace('\\', '^'); - string filename; - - // 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 spritefoundfile = null; - filename = Path.Combine(spritespath, pfilename + ".bmp"); - if(File.Exists(filename)) spritefoundfile = filename; - filename = Path.Combine(spritespath, pfilename + ".gif"); - if(File.Exists(filename)) spritefoundfile = filename; - filename = Path.Combine(spritespath, pfilename + ".png"); - if(File.Exists(filename)) spritefoundfile = filename; - - // Found anything? - if(spritefoundfile != null) - { - byte[] filedata = File.ReadAllBytes(spritefoundfile); - MemoryStream mem = new MemoryStream(filedata); - return mem; - } - - // Nothing found - return null; - } - - #endregion - #region ================== Methods - // This loads the images in this directory - private ICollection LoadDirectoryImages(string path) + // This creates an image + protected override ImageData CreateImage(string name, string filename, bool flat) { - List images = new List(); - string[] files; - string name; + return new FileImage(name, filename, flat); + } - // Find all BMP files - files = Directory.GetFiles(path, "*.bmp", SearchOption.TopDirectoryOnly); + // This returns true if the specified file exists + protected override bool FileExists(string filename) + { + return File.Exists(filename); + } + + // This returns all files in a given directory + protected override string[] GetAllFiles(string path) + { + return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly); + } - // Find all GIF files and append to files array - AddToArray(ref files, Directory.GetFiles(path, "*.gif", SearchOption.TopDirectoryOnly)); + // This returns all files in a given directory that match the given extension + protected override string[] GetFilesWithExt(string path, string extension) + { + return Directory.GetFiles(path, "*." + extension, SearchOption.TopDirectoryOnly); + } - // Find all PNG files and append to files array - AddToArray(ref files, Directory.GetFiles(path, "*.png", SearchOption.TopDirectoryOnly)); - - // Go for all files + // This finds the first file that has the specific name, regardless of file extension + protected override string FindFirstFile(string path, string beginswith) + { + string[] files = GetAllFiles(path); foreach(string f in files) { - // Make the texture name from filename without extension - name = Path.GetFileNameWithoutExtension(f).ToUpperInvariant(); - if(name.Length > 8) name = name.Substring(0, 8); - - // Add image to list - images.Add(new FileImage(name, f)); + if(string.Compare(Path.GetFileNameWithoutExtension(f), beginswith, true) == 0) + return f; } - // Return result - return images; + return null; } - // This resizes a string array and adds to it - private void AddToArray(ref string[] array, string[] add) + // This loads an entire file in memory and returns the stream + // NOTE: Callers are responsible for disposing the stream! + protected override MemoryStream LoadFile(string filename) { - int insertindex = array.Length; - Array.Resize(ref array, array.Length + add.Length); - add.CopyTo(array, insertindex); + return new MemoryStream(File.ReadAllBytes(filename)); } - - // This copies images from a collection unless they already exist in the list - private void AddImagesToList(List targetlist, ICollection sourcelist) + + // 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) { - // Go for all source images - foreach(ImageData src in sourcelist) - { - // Check if exists in target list - bool alreadyexists = false; - foreach(ImageData tgt in targetlist) - { - if(tgt.LongName == src.LongName) - { - alreadyexists = true; - break; - } - } - - // Add source image to target list - if(!alreadyexists) targetlist.Add(src); - } + // Just copy the file + string tempfile = General.MakeTempFilename(General.Map.TempPath, "wad"); + File.Copy(filename, tempfile); + return tempfile; } - + #endregion } } diff --git a/Source/Data/FileImage.cs b/Source/Data/FileImage.cs index 007ab89a..79d8bd06 100644 --- a/Source/Data/FileImage.cs +++ b/Source/Data/FileImage.cs @@ -24,6 +24,7 @@ using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.IO; +using CodeImp.DoomBuilder.IO; #endregion @@ -34,34 +35,56 @@ namespace CodeImp.DoomBuilder.Data #region ================== Variables private string filepathname; + private int probableformat; + private float scalex; + private float scaley; #endregion #region ================== Constructor / Disposer // Constructor - public FileImage(string name, string filepathname) + public FileImage(string name, string filepathname, bool asflat) { // Initialize this.filepathname = filepathname; SetName(name); - // Temporarily load file - Bitmap bmp = (Bitmap)Bitmap.FromFile(filepathname); - - // Get width and height from image - width = bmp.Size.Width; - height = bmp.Size.Height; - scaledwidth = (float)bmp.Size.Width * General.Map.Config.DefaultTextureScale; - scaledheight = (float)bmp.Size.Height * General.Map.Config.DefaultTextureScale; - - // Unload file - bmp.Dispose(); + if(asflat) + { + probableformat = ImageDataFormat.DOOMFLAT; + scalex = General.Map.Config.DefaultFlatScale; + scaley = General.Map.Config.DefaultFlatScale; + } + else + { + probableformat = ImageDataFormat.DOOMPICTURE; + scalex = General.Map.Config.DefaultTextureScale; + scaley = General.Map.Config.DefaultTextureScale; + } // We have no destructor GC.SuppressFinalize(this); } + // Constructor + public FileImage(string name, string filepathname, bool asflat, float scalex, float scaley) + { + // Initialize + this.filepathname = filepathname; + this.scalex = scalex; + this.scaley = scaley; + SetName(name); + + if(asflat) + probableformat = ImageDataFormat.DOOMFLAT; + else + probableformat = ImageDataFormat.DOOMPICTURE; + + // We have no destructor + GC.SuppressFinalize(this); + } + #endregion #region ================== Methods @@ -74,17 +97,41 @@ namespace CodeImp.DoomBuilder.Data lock(this) { - // Load file - if(bitmap != null) bitmap.Dispose(); - bitmap = (Bitmap)Bitmap.FromFile(filepathname); + // Load file data + if(bitmap != null) bitmap.Dispose(); bitmap = null; + MemoryStream filedata = new MemoryStream(File.ReadAllBytes(filepathname)); + + // Get a reader for the data + IImageReader reader = ImageDataFormat.GetImageReader(filedata, probableformat, General.Map.Data.Palette); + if(!(reader is UnknownImageReader)) + { + // Load the image + filedata.Seek(0, SeekOrigin.Begin); + try { bitmap = reader.ReadAsBitmap(filedata); } + catch(InvalidDataException) + { + // Data cannot be read! + bitmap = null; + } + } + + // Not loaded? + if(bitmap == null) + { + General.WriteLogLine("WARNING: Image file '" + filepathname + "' data format could not be read, while loading texture '" + this.Name + "'!"); + loadfailed = true; + filedata.Dispose(); + return; + } // Get width and height from image width = bitmap.Size.Width; height = bitmap.Size.Height; - scaledwidth = (float)bitmap.Size.Width * General.Map.Config.DefaultTextureScale; - scaledheight = (float)bitmap.Size.Height * General.Map.Config.DefaultTextureScale; + scaledwidth = (float)bitmap.Size.Width * scalex; + scaledheight = (float)bitmap.Size.Height * scaley; // Pass on to base + filedata.Dispose(); base.LocalLoadImage(); } } diff --git a/Source/Data/ImageData.cs b/Source/Data/ImageData.cs index f3fe7918..dba20108 100644 --- a/Source/Data/ImageData.cs +++ b/Source/Data/ImageData.cs @@ -192,7 +192,7 @@ namespace CodeImp.DoomBuilder.Data int oldheight = height; float oldscaledwidth = scaledwidth; float oldscaledheight = scaledheight; - + // Do the loading LocalLoadImage(); diff --git a/Source/Data/PK3FileImage.cs b/Source/Data/PK3FileImage.cs new file mode 100644 index 00000000..2aa366d8 --- /dev/null +++ b/Source/Data/PK3FileImage.cs @@ -0,0 +1,125 @@ + +#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; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using CodeImp.DoomBuilder.IO; + +#endregion + +namespace CodeImp.DoomBuilder.Data +{ + public sealed class PK3FileImage : ImageData + { + #region ================== Variables + + private PK3Reader datareader; + private string filepathname; + private int probableformat; + private float scalex; + private float scaley; + + #endregion + + #region ================== Constructor / Disposer + + // Constructor + internal PK3FileImage(PK3Reader datareader, string name, string filepathname, bool asflat) + { + // Initialize + this.datareader = datareader; + this.filepathname = filepathname; + SetName(name); + + if(asflat) + { + probableformat = ImageDataFormat.DOOMFLAT; + scalex = General.Map.Config.DefaultFlatScale; + scaley = General.Map.Config.DefaultFlatScale; + } + else + { + probableformat = ImageDataFormat.DOOMPICTURE; + scalex = General.Map.Config.DefaultTextureScale; + scaley = General.Map.Config.DefaultTextureScale; + } + + // We have no destructor + GC.SuppressFinalize(this); + } + + #endregion + + #region ================== Methods + + // This loads the image + protected override void LocalLoadImage() + { + // Leave when already loaded + if(this.IsImageLoaded) return; + + lock(this) + { + // Load file data + if(bitmap != null) bitmap.Dispose(); bitmap = null; + MemoryStream filedata = datareader.ExtractFile(filepathname); + + // Get a reader for the data + IImageReader reader = ImageDataFormat.GetImageReader(filedata, probableformat, General.Map.Data.Palette); + if(!(reader is UnknownImageReader)) + { + // Load the image + filedata.Seek(0, SeekOrigin.Begin); + try { bitmap = reader.ReadAsBitmap(filedata); } + catch(InvalidDataException) + { + // Data cannot be read! + bitmap = null; + } + } + + // Not loaded? + if(bitmap == null) + { + General.WriteLogLine("WARNING: Image file '" + filepathname + "' data format could not be read, while loading texture '" + this.Name + "'!"); + loadfailed = true; + filedata.Dispose(); + return; + } + + // Get width and height from image + width = bitmap.Size.Width; + height = bitmap.Size.Height; + scaledwidth = (float)bitmap.Size.Width * scalex; + scaledheight = (float)bitmap.Size.Height * scaley; + + // Pass on to base + filedata.Dispose(); + base.LocalLoadImage(); + } + } + + #endregion + } +} diff --git a/Source/Data/PK3Reader.cs b/Source/Data/PK3Reader.cs index 0e0dc517..a9e2b953 100644 --- a/Source/Data/PK3Reader.cs +++ b/Source/Data/PK3Reader.cs @@ -31,17 +31,11 @@ using ICSharpCode.SharpZipLib.Zip; namespace CodeImp.DoomBuilder.Data { - internal sealed class PK3Reader : DataReader + internal sealed class PK3Reader : PK3StructuredReader { - #region ================== Constants - - #endregion - #region ================== Variables - #endregion - - #region ================== Properties + private List fileslist; #endregion @@ -50,21 +44,29 @@ namespace CodeImp.DoomBuilder.Data // Constructor public PK3Reader(DataLocation dl) : base(dl) { - // Initialize General.WriteLogLine("Opening PK3 resource '" + location.location + "'"); - - //TEST - /* - ZipInputStream z = new ZipInputStream(File.Open(dl.location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - ZipEntry ze; - while((ze = z.GetNextEntry()) != null) - { - General.WriteLogLine(ze.Name); - - } - z.Dispose(); - */ + // Open the zip file + ZipInputStream zipstream = OpenPK3File(); + + // Make list of all files + fileslist = new List(); + ZipEntry entry = zipstream.GetNextEntry(); + while(entry != null) + { + if(entry.IsFile) fileslist.Add(entry.Name.ToLowerInvariant()); + + // Next + entry = zipstream.GetNextEntry(); + } + + // Done with the zip file + zipstream.Close(); + zipstream.Dispose(); + + // Initialize without path (because we use paths relative to the PK3 file) + Initialize(""); + // We have no destructor GC.SuppressFinalize(this); } @@ -76,9 +78,7 @@ namespace CodeImp.DoomBuilder.Data if(!isdisposed) { General.WriteLogLine("Closing PK3 resource '" + location.location + "'"); - - // Clean up - + // Done base.Dispose(); } @@ -86,22 +86,148 @@ namespace CodeImp.DoomBuilder.Data #endregion - #region ================== Palette + #region ================== Management - // This loads the PLAYPAL palette - public override Playpal LoadPalette() + // This opens the zip file for reading + private ZipInputStream OpenPK3File() { - // Error when suspended - if(issuspended) throw new Exception("Data reader is suspended"); - - // Not yet implemented - return null; + FileStream filestream = File.Open(location.location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + filestream.Seek(0, SeekOrigin.Begin); + return new ZipInputStream(filestream); } #endregion + + #region ================== Methods + + // This creates an image + protected override ImageData CreateImage(string name, string filename, bool flat) + { + return new PK3FileImage(this, name, filename, flat); + } - #region ================== Textures + // This returns true if the specified file exists + protected override bool FileExists(string filename) + { + string lowfile = filename.ToLowerInvariant(); + foreach(string f in fileslist) + { + if((string.Compare(f, lowfile) == 0)) return true; + } + return false; + } + + // This returns all files in a given directory + protected override string[] GetAllFiles(string path) + { + List matches = new List(); + string lowpath = path.ToLowerInvariant(); + foreach(string f in fileslist) + { + if((string.Compare(Path.GetDirectoryName(f), lowpath) == 0) && + (Path.GetFileName(f).Length > 0)) + matches.Add(f); + } + + return matches.ToArray(); + } + + // This returns all files in a given directory that match the given extension + protected override string[] GetFilesWithExt(string path, string extension) + { + List matches = new List(); + string lowpath = path.ToLowerInvariant(); + string lowext = "." + extension.ToLowerInvariant(); + foreach(string f in fileslist) + { + if((string.Compare(Path.GetDirectoryName(f), lowpath) == 0) && f.EndsWith(lowext)) + matches.Add(f); + } + + return matches.ToArray(); + } + + // This finds the first file that has the specific name, regardless of file extension + protected override string FindFirstFile(string path, string beginswith) + { + string lowpath = path.ToLowerInvariant(); + string lowbegin = beginswith.ToLowerInvariant(); + foreach(string f in fileslist) + { + if((string.Compare(Path.GetDirectoryName(f), lowpath) == 0) && + (string.Compare(Path.GetFileNameWithoutExtension(f), lowbegin) == 0)) + return f; + } + + return null; + } + + // This loads an entire file in memory and returns the stream + // NOTE: Callers are responsible for disposing the stream! + protected override MemoryStream LoadFile(string filename) + { + MemoryStream filedata = null; + byte[] copybuffer = new byte[4096]; + + // Open the zip file + ZipInputStream zipstream = OpenPK3File(); + + ZipEntry entry = zipstream.GetNextEntry(); + while(entry != null) + { + // Is this the entry we are looking for? + if(string.Compare(entry.Name, filename, true) == 0) + { + int expectedsize = (int)entry.Size; + if(expectedsize < 1) expectedsize = 1024; + filedata = new MemoryStream(expectedsize); + int readsize = zipstream.Read(copybuffer, 0, copybuffer.Length); + while(readsize > 0) + { + filedata.Write(copybuffer, 0, readsize); + readsize = zipstream.Read(copybuffer, 0, copybuffer.Length); + } + break; + } + + // Next + entry = zipstream.GetNextEntry(); + } + + // Done with the zip file + zipstream.Close(); + zipstream.Dispose(); + + // Nothing found? + if(filedata == null) + { + throw new FileNotFoundException("Cannot find the file " + filename + " in PK3 file " + location.location + "."); + } + else + { + return filedata; + } + } + + // 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; + } + + // Public version to load a file + internal MemoryStream ExtractFile(string filename) + { + return LoadFile(filename); + } + #endregion } } diff --git a/Source/Data/PK3StructuredReader.cs b/Source/Data/PK3StructuredReader.cs new file mode 100644 index 00000000..721f3b56 --- /dev/null +++ b/Source/Data/PK3StructuredReader.cs @@ -0,0 +1,413 @@ + +#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; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using CodeImp.DoomBuilder.IO; + +#endregion + +namespace CodeImp.DoomBuilder.Data +{ + internal abstract class PK3StructuredReader : DataReader + { + #region ================== Constants + + private const string PATCHES_DIR = "patches"; + private const string TEXTURES_DIR = "textures"; + private const string FLATS_DIR = "flats"; + private const string HIRES_DIR = "hires"; + private const string SPRITES_DIR = "sprites"; + + #endregion + + #region ================== Variables + + // Source + private bool roottextures; + private bool rootflats; + + // Paths + protected string rootpath; + protected string patchespath; + protected string texturespath; + protected string flatspath; + protected string hirespath; + protected string spritespath; + + // WAD files that must be loaded as well + private List wads; + + #endregion + + #region ================== Properties + + #endregion + + #region ================== Constructor / Disposer + + // Constructor + public PK3StructuredReader(DataLocation dl) : base(dl) + { + // Initialize + this.roottextures = dl.textures; + this.rootflats = dl.flats; + } + + // Call this to initialize this class + protected virtual void Initialize(string rootpath) + { + // Initialize + this.rootpath = rootpath; + this.patchespath = Path.Combine(rootpath, PATCHES_DIR); + this.texturespath = Path.Combine(rootpath, TEXTURES_DIR); + this.flatspath = Path.Combine(rootpath, FLATS_DIR); + this.hirespath = Path.Combine(rootpath, HIRES_DIR); + this.spritespath = Path.Combine(rootpath, SPRITES_DIR); + + // Load all WAD files in the root as WAD resources + string[] wadfiles = GetFilesWithExt(rootpath, "wad"); + wads = new List(wadfiles.Length); + foreach(string w in wadfiles) + { + string tempfile = CreateTempFile(w); + DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, tempfile, false, false); + wads.Add(new WADReader(wdl)); + } + } + + // Disposer + public override void Dispose() + { + // Not already disposed? + if(!isdisposed) + { + // Clean up + foreach(WADReader wr in wads) wr.Dispose(); + + // Remove temp files + foreach(WADReader wr in wads) + { + try { File.Delete(wr.Location.location); } + catch(Exception) { } + } + + // Done + base.Dispose(); + } + } + + #endregion + + #region ================== Management + + // This suspends use of this resource + public override void Suspend() + { + foreach(WADReader wr in wads) wr.Suspend(); + base.Suspend(); + } + + // This resumes use of this resource + public override void Resume() + { + foreach(WADReader wr in wads) wr.Resume(); + base.Resume(); + } + + #endregion + + #region ================== Palette + + // This loads the PLAYPAL palette + public override Playpal LoadPalette() + { + // Error when suspended + if(issuspended) throw new Exception("Data reader is suspended"); + + // Palette from wad(s) + Playpal palette = null; + foreach(WADReader wr in wads) + { + Playpal wadpalette = wr.LoadPalette(); + if(wadpalette != null) palette = wadpalette; + } + + // Done + return palette; + } + + #endregion + + #region ================== Textures + + // This loads the textures + public override ICollection LoadTextures(PatchNames pnames) + { + List images = new List(); + ICollection collection; + + // Error when suspended + if(issuspended) throw new Exception("Data reader is suspended"); + + // Load from wad files (NOTE: backward order, because the last wad's images have priority) + for(int i = wads.Count - 1; i >= 0; i--) + { + collection = wads[i].LoadTextures(pnames); + AddImagesToList(images, collection); + } + + // Should we load the images in this directory as textures? + if(roottextures) + { + collection = LoadDirectoryImages(rootpath, false); + AddImagesToList(images, collection); + } + + // TODO: Add support for hires texture here + + // Add images from texture directory + collection = LoadDirectoryImages(texturespath, false); + AddImagesToList(images, collection); + + // Load TEXTURE1 lump file + List imgset = new List(); + string texture1file = FindFirstFile(rootpath, "TEXTURE1"); + if((texture1file != null) && FileExists(texture1file)) + { + MemoryStream filedata = LoadFile(texture1file); + WADReader.LoadTextureSet(filedata, ref imgset, pnames); + filedata.Dispose(); + } + + // Load TEXTURE2 lump file + string texture2file = FindFirstFile(rootpath, "TEXTURE2"); + if((texture2file != null) && FileExists(texture2file)) + { + MemoryStream filedata = LoadFile(texture2file); + WADReader.LoadTextureSet(filedata, ref imgset, pnames); + filedata.Dispose(); + } + + // Add images from TEXTURE1 and TEXTURE2 lump files + AddImagesToList(images, imgset); + + return images; + } + + // This returns the patch names from the PNAMES lump + // A directory resource does not support this lump, but the wads in the directory may contain this lump + public override PatchNames LoadPatchNames() + { + PatchNames pnames; + + // Error when suspended + if(issuspended) throw new Exception("Data reader is suspended"); + + // Load from wad files + // Note the backward order, because the last wad's images have priority + for(int i = wads.Count - 1; i >= 0; i--) + { + pnames = wads[i].LoadPatchNames(); + if(pnames != null) return pnames; + } + + // If none of the wads provides patch names, let's see if we can + string pnamesfile = FindFirstFile(rootpath, "PNAMES"); + if((pnamesfile != null) && FileExists(pnamesfile)) + { + MemoryStream pnamesdata = LoadFile(pnamesfile); + pnames = new PatchNames(pnamesdata); + pnamesdata.Dispose(); + return pnames; + } + + return null; + } + + // This finds and returns a patch stream + public override Stream GetPatchData(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].GetPatchData(pname); + if(data != null) return data; + } + + // Find in patches directory + string filename = FindFirstFile(patchespath, pname); + if((filename != null) && FileExists(filename)) + { + return LoadFile(filename); + } + + // Nothing found + return null; + } + + #endregion + + #region ================== Flats + + // This loads the textures + public override ICollection LoadFlats() + { + List images = new List(); + ICollection collection; + + // Error when suspended + if(issuspended) throw new Exception("Data reader is suspended"); + + // Load from wad files + // Note the backward order, because the last wad's images have priority + for(int i = wads.Count - 1; i >= 0; i--) + { + collection = wads[i].LoadFlats(); + AddImagesToList(images, collection); + } + + // Should we load the images in this directory as flats? + if(rootflats) + { + collection = LoadDirectoryImages(rootpath, true); + AddImagesToList(images, collection); + } + + // Add images from flats directory + collection = LoadDirectoryImages(flatspath, true); + AddImagesToList(images, collection); + + return images; + } + + #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(spritespath, pfilename); + if((filename != null) && FileExists(filename)) + { + return LoadFile(filename); + } + + // Nothing found + return null; + } + + #endregion + + #region ================== Methods + + // This loads the images in this directory + private ICollection LoadDirectoryImages(string path, bool flats) + { + List images = new List(); + string[] files; + string name; + + // Go for all files + files = GetAllFiles(path); + foreach(string f in files) + { + // Make the texture name from filename without extension + name = Path.GetFileNameWithoutExtension(f).ToUpperInvariant(); + if(name.Length > 8) name = name.Substring(0, 8); + + // Add image to list + images.Add(CreateImage(name, f, flats)); + } + + // Return result + return images; + } + + // This copies images from a collection unless they already exist in the list + private void AddImagesToList(List targetlist, ICollection sourcelist) + { + // Go for all source images + foreach(ImageData src in sourcelist) + { + // Check if exists in target list + bool alreadyexists = false; + foreach(ImageData tgt in targetlist) + { + if(tgt.LongName == src.LongName) + { + alreadyexists = true; + break; + } + } + + // Add source image to target list + if(!alreadyexists) targetlist.Add(src); + } + } + + // This must create an image + protected abstract ImageData CreateImage(string name, string filename, bool flat); + + // This must return true if the specified file exists + protected abstract bool FileExists(string filename); + + // This must return all files in a given directory + protected abstract string[] GetAllFiles(string path); + + // This must return all files in a given directory that match the given extension + protected abstract string[] GetFilesWithExt(string path, string extension); + + // This must find the first file that has the specific name, regardless of file extension + protected abstract string FindFirstFile(string path, string beginswith); + + // This must load an entire file in memory and returns the stream + // NOTE: Callers are responsible for disposing the stream! + protected abstract MemoryStream LoadFile(string filename); + + // This must create 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 abstract string CreateTempFile(string filename); + + #endregion + } +} diff --git a/Source/Data/SimpleTextureImage.cs b/Source/Data/SimpleTextureImage.cs index 02676b1f..5678a31b 100644 --- a/Source/Data/SimpleTextureImage.cs +++ b/Source/Data/SimpleTextureImage.cs @@ -80,6 +80,7 @@ namespace CodeImp.DoomBuilder.Data lock(this) { // Get the patch data stream + if(bitmap != null) bitmap.Dispose(); bitmap = null; patchdata = General.Map.Data.GetPatchData(lumpname); if(patchdata != null) { @@ -92,16 +93,31 @@ namespace CodeImp.DoomBuilder.Data // Get a reader for the data reader = ImageDataFormat.GetImageReader(mem, ImageDataFormat.DOOMPICTURE, General.Map.Data.Palette); - if(reader is UnknownImageReader) + if(!(reader is UnknownImageReader)) { - // Data is in an unknown format! - General.WriteLogLine("WARNING: Image lump '" + lumpname + "' data format could not be read, while loading texture '" + this.Name + "'!"); + // Load the image + mem.Seek(0, SeekOrigin.Begin); + try { bitmap = reader.ReadAsBitmap(mem); } + catch(InvalidDataException) + { + // Data cannot be read! + bitmap = null; + } } - // Load the image - mem.Seek(0, SeekOrigin.Begin); - bitmap = reader.ReadAsBitmap(mem); - if(bitmap == null) return; + // Not loaded? + if(bitmap == null) + { + General.WriteLogLine("WARNING: Image lump '" + lumpname + "' data format could not be read, while loading texture '" + this.Name + "'!"); + loadfailed = true; + return; + } + + // Get width and height from image + width = bitmap.Size.Width; + height = bitmap.Size.Height; + scaledwidth = (float)width * scalex; + scaledheight = (float)height * scaley; // Done mem.Dispose(); diff --git a/Source/Data/WADReader.cs b/Source/Data/WADReader.cs index 0a5aeea6..8e8967cc 100644 --- a/Source/Data/WADReader.cs +++ b/Source/Data/WADReader.cs @@ -201,7 +201,7 @@ namespace CodeImp.DoomBuilder.Data } // This loads a set of textures - private void LoadTextureSet(Stream texturedata, ref List images, PatchNames pnames) + public static void LoadTextureSet(Stream texturedata, ref List images, PatchNames pnames) { BinaryReader reader = new BinaryReader(texturedata); int flags, width, height, patches, px, py, pi; diff --git a/Source/Editing/GridSetup.cs b/Source/Editing/GridSetup.cs index 3d1b8f23..0c6be244 100644 --- a/Source/Editing/GridSetup.cs +++ b/Source/Editing/GridSetup.cs @@ -176,7 +176,7 @@ namespace CodeImp.DoomBuilder.Editing break; case SOURCE_FILE: - backimage = new FileImage(background, background); + backimage = new FileImage(background, background, false, 1.0f, 1.0f); break; } diff --git a/Source/General/General.cs b/Source/General/General.cs index 0ebe3baf..d5a2ad56 100644 --- a/Source/General/General.cs +++ b/Source/General/General.cs @@ -56,7 +56,7 @@ namespace CodeImp.DoomBuilder internal static extern void ZeroMemory(IntPtr dest, int size); [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] - internal static extern unsafe void CopyMemory(void* dst, void* src, UIntPtr length); + internal static extern unsafe void CopyMemory(void* dst, void* src, uint length); [DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true, CallingConvention = CallingConvention.StdCall)] internal static extern int SendMessage(IntPtr hwnd, uint Msg, int wParam, int lParam); diff --git a/Source/IO/DoomFlatReader.cs b/Source/IO/DoomFlatReader.cs index cd42e7ff..bbc0456b 100644 --- a/Source/IO/DoomFlatReader.cs +++ b/Source/IO/DoomFlatReader.cs @@ -103,7 +103,7 @@ namespace CodeImp.DoomBuilder.IO targetdata = (PixelColor*)bitmapdata.Scan0.ToPointer(); // Copy the pixels - General.CopyMemory((void*)targetdata, (void*)pixeldata.Pointer, new UIntPtr((uint)(width * height * sizeof(PixelColor)))); + General.CopyMemory(targetdata, pixeldata.Pointer, (uint)(width * height * sizeof(PixelColor))); // Done bmp.UnlockBits(bitmapdata); diff --git a/Source/IO/DoomPictureReader.cs b/Source/IO/DoomPictureReader.cs index cc27a6ab..f8eae896 100644 --- a/Source/IO/DoomPictureReader.cs +++ b/Source/IO/DoomPictureReader.cs @@ -119,7 +119,7 @@ namespace CodeImp.DoomBuilder.IO targetdata = (PixelColor*)bitmapdata.Scan0.ToPointer(); // Copy the pixels - General.CopyMemory((void*)targetdata, (void*)pixeldata.Pointer, new UIntPtr((uint)(width * height * sizeof(PixelColor)))); + General.CopyMemory(targetdata, pixeldata.Pointer, (uint)(width * height * sizeof(PixelColor))); // Done bmp.UnlockBits(bitmapdata); diff --git a/Source/IO/Lump.cs b/Source/IO/Lump.cs index 53aef521..3e0b2b6f 100644 --- a/Source/IO/Lump.cs +++ b/Source/IO/Lump.cs @@ -119,7 +119,7 @@ namespace CodeImp.DoomBuilder.IO fixed(void* bp = namebytes) { - General.CopyMemory(&value, bp, new UIntPtr(bytes)); + General.CopyMemory(&value, bp, bytes); } return value; diff --git a/Source/Windows/GridSetupForm.cs b/Source/Windows/GridSetupForm.cs index 0e712e3e..4e04181e 100644 --- a/Source/Windows/GridSetupForm.cs +++ b/Source/Windows/GridSetupForm.cs @@ -132,7 +132,7 @@ namespace CodeImp.DoomBuilder.Windows // Set this file as background backgroundname = browsefile.FileName; backgroundsource = GridSetup.SOURCE_FILE; - ImageData img = new FileImage(backgroundname, backgroundname); + ImageData img = new FileImage(backgroundname, backgroundname, false, 1.0f, 1.0f); img.LoadImage(); General.DisplayZoomedImage(backgroundimage, new Bitmap(img.Bitmap)); img.Dispose();