UltimateZoneBuilder/Source/Core/IO/WAD.cs
MaxED 002d6e9c89 Fixed inability to disable bilinear filtering in Visual mode some users experienced.
Fixed occasional TreeView flickering in Edit Things window, Browse Action window and Tag Explorer panel.
Updated Thing category icons in the Edit Things window. They now have "opened" and "closed" states.
Internal: added BufferedTreeView to the core controls.
Updated ZDoom game configurations (sector crush mode).
Updated ZDoom ACC.
2016-03-29 14:38:07 +00:00

464 lines
12 KiB
C#

#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.Security.Cryptography;
using System.Text;
using System.IO;
#endregion
namespace CodeImp.DoomBuilder.IO
{
internal class WAD : IDisposable
{
#region ================== Constants
// WAD types
private const string TYPE_IWAD = "IWAD";
private const string TYPE_PWAD = "PWAD";
// Encoder
public static readonly Encoding ENCODING = Encoding.ASCII;
//mxd. Official IWAD MD5 hashes
private static readonly HashSet<string> IWAD_HASHES = new HashSet<string>
{
////// DOOM IWADS //////
"d9153ced9fd5b898b36cc5844e35b520", // DOOM2 1.666g MD5
"30e3c2d0350b67bfbf47271970b74b2f", // DOOM2 1.666 MD5
"ea74a47a791fdef2e9f2ea8b8a9da13b", // DOOM2 1.7 MD5
"d7a07e5d3f4625074312bc299d7ed33f", // DOOM2 1.7a MD5
"c236745bb01d89bbb866c8fed81b6f8c", // DOOM2 1.8 MD5
"25e1459ca71d321525f84628f45ca8cd", // DOOM2 1.9 MD5
"3cb02349b3df649c86290907eed64e7b", // DOOM2 French MD5
"c3bea40570c23e511a7ed3ebcd9865f7", // BFG DOOM2 MD5
"981b03e6d1dc033301aa3095acc437ce", // DOOM 1.1 MD5
"792fd1fea023d61210857089a7c1e351", // DOOM 1.2 MD5
"464e3723a7e7f97039ac9fd057096adb", // DOOM 1.6b MD5
"54978d12de87f162b9bcc011676cb3c0", // DOOM 1.666 MD5
"11e1cd216801ea2657723abc86ecb01f", // DOOM 1.8 MD5
"1cd63c5ddff1bf8ce844237f580e9cf3", // DOOM 1.9 MD5
"fb35c4a5a9fd49ec29ab6e900572c524", // BFG DOOM MD5
"c4fe9fd920207691a9f493668e0a2083", // ULTIMATE DOOM MD5
"75c8cf89566741fa9d22447604053bd7", // PLUTONIA MD5
"3493be7e1e2588bc9c8b31eab2587a04", // PLUTONIA RARE MD5
"4e158d9953c79ccf97bd0663244cc6b6", // TNT MD5
"1d39e405bf6ee3df69a8d2646c8d5c49", // TNT Fixed MD5
"be626c12b7c9d94b1dfb9c327566b4ff", // PSN TNT MD5
////// HERETIC IWADS //////
"3117e399cdb4298eaa3941625f4b2923", // HERETIC 1.0 MD5
"1e4cb4ef075ad344dd63971637307e04", // HERETIC 1.2 MD5
"66d686b1ed6d35ff103f15dbd30e0341", // HERETIC 1.3 MD5
////// HEXEN IWADS //////
"c88a2bb3d783e2ad7b599a8e301e099e", // HEXEN Beta MD5
"b2543a03521365261d0a0f74d5dd90f0", // HEXEN 1.0 MD5
"abb033caf81e26f12a2103e1fa25453f", // HEXEN 1.1 MD5
"1077432e2690d390c256ac908b5f4efa", // HEXEN DK 1.0 MD5
"78d5898e99e220e4de64edaa0e479593", // HEXEN DK 1.1 MD5
////// STRIFE IWADS //////
"8f2d3a6a289f5d2f2f9c1eec02b47299", // STRIFE 1.0 MD5
"2fed2031a5b03892106e0f117f17901f", // STRIFE 1.2 MD5
};
#endregion
#region ================== Variables
// File objects
private string filename;
private FileStream file;
private BinaryReader reader;
private BinaryWriter writer;
// Header
private int numlumps;
private int lumpsoffset;
private bool isiwad; //mxd
private bool isofficialiwad; //mxd
// Lumps
private List<Lump> lumps;
// Status
private bool isreadonly;
private bool isdisposed;
#endregion
#region ================== Properties
public string Filename { get { return filename; } }
public Encoding Encoding { get { return ENCODING; } }
public bool IsReadOnly { get { return isreadonly; } }
public bool IsDisposed { get { return isdisposed; } }
public bool IsIWAD { get { return isiwad; } set { isiwad = value; } } //mxd
public bool IsOfficialIWAD { get { return isofficialiwad; } } //mxd
public List<Lump> Lumps { get { return lumps; } }
#endregion
#region ================== Constructor / Disposer
// Constructor to open or create a WAD file
public WAD(string pathfilename)
{
// Initialize
this.isreadonly = false;
this.Open(pathfilename);
}
// Constructor to open or create a WAD file
public WAD(string pathfilename, bool openreadonly)
{
// Initialize
this.isreadonly = openreadonly;
this.Open(pathfilename);
}
// Destructor
/*~WAD()
{
// Make sure everything is disposed
this.Dispose();
}*/
// Disposer
public void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Only possible when not read-only
if(!isreadonly)
{
// Flush writing changes
if(writer != null) writer.Flush();
if(file != null) file.Flush();
}
// Clean up
if(lumps != null) foreach(Lump l in lumps) l.Dispose();
if(writer != null) writer.Close();
if(reader != null) reader.Close();
if(file != null) file.Dispose();
// Done
isdisposed = true;
GC.SuppressFinalize(this); //mxd
}
}
#endregion
#region ================== IO
// Open a WAD file
private void Open(string pathfilename)
{
FileAccess access;
FileShare share;
// Keep filename
filename = pathfilename;
//mxd
CheckHash();
// 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;
}
// Open the file stream
file = File.Open(pathfilename, FileMode.OpenOrCreate, access, share);
// Create file handling tools
reader = new BinaryReader(file, ENCODING);
if(!isreadonly) writer = new BinaryWriter(file, ENCODING);
// Is the WAD file zero length?
if(file.Length < 4)
{
// Create the headers in file
CreateHeaders();
}
else
{
// Read information from file
ReadHeaders();
}
}
// This creates new file headers
private void CreateHeaders()
{
// Default settings
isiwad = false; //mxd
isofficialiwad = false; //mxd
lumpsoffset = 12;
// New lumps array
lumps = new List<Lump>(numlumps);
// Write the headers
if(!isreadonly) WriteHeaders();
}
// This reads the WAD header and lumps table
private void ReadHeaders()
{
// Make sure the write is finished writing
if(!isreadonly) writer.Flush();
// Seek to beginning
file.Seek(0, SeekOrigin.Begin);
// Read WAD type
isiwad = (ENCODING.GetString(reader.ReadBytes(4)) == TYPE_IWAD); //mxd
// Number of lumps
numlumps = reader.ReadInt32();
if(numlumps < 0) throw new IOException("Invalid number of lumps in wad file.");
// Lumps table offset
lumpsoffset = reader.ReadInt32();
if(lumpsoffset < 0) throw new IOException("Invalid lumps offset in wad file.");
// Seek to the lumps table
file.Seek(lumpsoffset, SeekOrigin.Begin);
// Dispose old lumps and create new list
if(lumps != null) foreach(Lump l in lumps) l.Dispose();
lumps = new List<Lump>(numlumps);
// Go for all lumps
for(int i = 0; i < numlumps; i++)
{
// Read lump information
int offset = reader.ReadInt32();
int length = reader.ReadInt32();
byte[] fixedname = reader.ReadBytes(8);
// Create the lump
lumps.Add(new Lump(file, this, fixedname, offset, length));
}
}
// This writes the WAD header and lumps table
public void WriteHeaders()
{
// Seek to beginning
file.Seek(0, SeekOrigin.Begin);
// Write WAD type
writer.Write(ENCODING.GetBytes(isiwad ? TYPE_IWAD : TYPE_PWAD));
// Number of lumps
writer.Write(numlumps);
// Lumps table offset
writer.Write(lumpsoffset);
// Seek to the lumps table
file.Seek(lumpsoffset, SeekOrigin.Begin);
// Go for all lumps
for(int i = 0; i < lumps.Count; i++)
{
// Write lump information
writer.Write(lumps[i].Offset);
writer.Write(lumps[i].Length);
writer.Write(lumps[i].FixedName);
}
}
//mxd
private void CheckHash()
{
// Open the file stream
FileStream fs = File.Open(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
// Empty file can't be official iwad
if(fs.Length > 4)
{
BinaryReader r = new BinaryReader(fs, ENCODING);
// Read WAD type
if(ENCODING.GetString(r.ReadBytes(4)) == TYPE_IWAD)
{
// Rewind
r.BaseStream.Position = 0;
isofficialiwad = IWAD_HASHES.Contains(MD5Hash.Get(r.BaseStream));
if(!isreadonly && isofficialiwad) isreadonly = true;
}
// Close the reader
r.Close();
}
else
{
// Close the file
fs.Dispose();
}
}
// This flushes writing changes
/*public void Flush()
{
// Only possible when not read-only
if(!isreadonly)
{
// Flush writing changes
if(writer != null) writer.Flush();
if(file != null) file.Flush();
}
}*/
#endregion
#region ================== Lumps
// This creates a new lump in the WAD file
public Lump Insert(string name, int position, int datalength)
{
// We will be adding a lump
numlumps++;
// Extend the file
file.SetLength(file.Length + datalength + 16);
// Create the lump
Lump lump = new Lump(file, this, Lump.MakeFixedName(name, ENCODING), lumpsoffset, datalength);
lumps.Insert(position, lump);
// Advance lumps table offset
lumpsoffset += datalength;
// Write the new headers
WriteHeaders();
// Return the new lump
return lump;
}
// This removes a lump from the WAD file by index
public void RemoveAt(int index)
{
// Remove from list
Lump l = lumps[index];
lumps.RemoveAt(index);
l.Dispose();
numlumps--;
// Write the new headers
WriteHeaders();
}
// This removes a lump from the WAD file
public void Remove(Lump lump)
{
// Remove from list
lumps.Remove(lump);
lump.Dispose();
numlumps--;
// Write the new headers
WriteHeaders();
}
// This finds a lump by name, returns null when not found
public Lump FindLump(string name)
{
int index = FindLumpIndex(name);
return (index == -1 ? null : lumps[index]);
}
// This finds a lump by name, returns null when not found
public Lump FindLump(string name, int start)
{
int index = FindLumpIndex(name, start);
return (index == -1 ? null : lumps[index]);
}
// This finds a lump by name, returns null when not found
public Lump FindLump(string name, int start, int end)
{
int index = FindLumpIndex(name, start, end);
return (index == -1 ? null : lumps[index]);
}
// This finds a lump by name, returns -1 when not found
public int FindLumpIndex(string name)
{
// Do search
return FindLumpIndex(name, 0, lumps.Count - 1);
}
// This finds a lump by name, returns -1 when not found
public int FindLumpIndex(string name, int start)
{
// Do search
return FindLumpIndex(name, start, lumps.Count - 1);
}
// This finds a lump by name, returns -1 when not found
public int FindLumpIndex(string name, int start, int end)
{
if(name.Length > 8) return -1;//mxd. Can't be here. Go away!
long longname = Lump.MakeLongName(name);
// Fix end when it exceeds length
if(end > (lumps.Count - 1)) end = lumps.Count - 1;
// Loop through the lumps
for(int i = start; i <= end; i++)
{
// Check if the lump name matches
if(lumps[i].LongName == longname)
{
// Found the lump!
return i;
}
}
// Nothing found
return -1;
}
#endregion
}
}