#region License information
// ----------------------------------------------------------------------------
//
// libeq2 - A library for analyzing the Everquest II File Format
// Blaz (blaz@blazlabs.com)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// 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.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
//
// ( The full text of the license can be found in the License.txt file )
//
// ----------------------------------------------------------------------------
#endregion
#region Using directives
using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using Everquest2.Visualization;
using Everquest2.Visualization.ParticleGenerator;
#endregion
namespace Everquest2.Util
{
public class Eq2Reader : BinaryReader
{
#region Methods
public Eq2Reader(Stream stream) : base(stream, System.Text.Encoding.ASCII)
{
}
#region Disposable semantics
public void Dispose()
{
base.Dispose(true);
}
~Eq2Reader()
{
base.Dispose(false);
}
#endregion
///
/// Reads an Everquest 2 object from the stream.
///
///
/// If the object is a node object, none of its children (if any) are read.
/// To read a node object and all its children use .
///
/// Error encountered while deserializing the object.
/// Deserialized object.
public virtual VeBase ReadObject()
{
long startPos = BaseStream.Position;
// Read class name
string className = ReadString();
if (className.Length < 1)
return null;
ConstructorInfo constructor = null;
// Lookup class in cache
if (classCache.ContainsKey(className))
{
constructor = classCache[className];
}
else
{
// Image(2020): ClassNames no longer include "Ve"
if (!className.StartsWith("Ve"))
className = "Ve" + className;
// Find class in current assembly
Type classType = GetType().Assembly.GetType("Everquest2.Visualization." + className, false);
string filename = null;
if (typeof(FileStream).IsInstanceOfType(BaseStream))
{
filename = (BaseStream as System.IO.FileStream).Name;
}
//Debug.Assert(classType != null, "Invalid class name!", "Error getting class type at index {0}{1}",
// BaseStream.Position,
// filename != null ? "\n in file " + filename : "");
// Find deserializing constructor
constructor = classType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
new Type[] { typeof(Eq2Reader), typeof(StreamingContext) },
null);
Debug.Assert(constructor != null, "Deserializing constructor not found on class " + classType.Name);
classCache[className] = constructor;
}
// Create streaming context
StreamingContext context = new StreamingContext();
// Read parent chain count
byte parentChainCount = ReadByte();
byte realParentChainCount = 0;
Type type = constructor.DeclaringType;
while (type != typeof(Object))
{
type = type.BaseType;
++realParentChainCount;
}
Debug.Assert(parentChainCount == realParentChainCount, "Parent chain count changed for class " + className + " (is " + parentChainCount + ", should be " + realParentChainCount + ")");
// Read class versions. They are stored from derived to base.
byte[] classVersions = ReadBytes(parentChainCount);
Type curClassType = constructor.DeclaringType;
uint curClassIndex = 0;
context.ClassVersions = new Dictionary(parentChainCount);
// Traverse the class hierarchy from derived to base setting the class version info
while (curClassIndex < parentChainCount)
{
context.ClassVersions[curClassType] = classVersions[curClassIndex];
// TODO: Enforce correct class versions. From the time being just skip those class version bytes.
curClassType = curClassType.BaseType;
++curClassIndex;
}
// Invoke deserialization
object eq2Object = constructor.Invoke(new object[] { this, context });
// Forces an exception to be thrown if the object is not a VeBase
return (VeBase)eq2Object;
}
///
/// Reads an Everquest 2 node hierarchy from the stream.
///
///
/// This methods deserializes a hierarchy of Everquest 2 nodes.
/// An exception is thrown if any of the objects read is not a node.
///
/// One of the objects from the hierarchy is not an Everquest 2 node.
/// Error encountered while deserializing the object.
/// Deserialized hierarchy of objects.
public virtual VeNode ReadNodeObject()
{
// Read root node. If the object is not a VeNode the cast will throw an exception.
VeNode node = (VeNode)ReadObject();
if (node == null)
return null;
// Read children count
uint childrenCount = ReadUInt32();
// Read children
for (uint i = 0; i < childrenCount; ++i)
{
VeNode child = ReadNodeObject();
node.AddChild(child);
}
return node;
}
///
/// Reads an Everquest 2 particle generator operation from the stream.
///
///
/// This method is usually called during the deserialization of a VeParticleGeneratorNode object.
/// An exception is the particle generator operation is unknown.
///
/// Class version of the calling particle generator class.
/// Error encountered while deserializing the operation.
/// Deserialized particle generator operation.
public virtual VeParticleGeneratorOp ReadParticleGeneratorOp(byte classVersion)
{
// Read operation name
string name = ReadString(2);
ConstructorInfo constructor = null;
// Lookup op name in cache
if (particleOpCache.ContainsKey(name))
{
constructor = particleOpCache[name];
}
else
{
// Find the operation class given its name as read from the stream
Type[] opClasses = GetType().Module.FindTypes
(
delegate(Type type, object opName)
{
if (type.Namespace == "Everquest2.Visualization.ParticleGenerator" &&
type.Name.StartsWith("VeParticleGenerator") &&
type.Name.EndsWith("Op") &&
type.Name != "VeParticleGeneratorOp")
{
// Note: 19 == "VeParticleGenerator".Length and 21 == "VeParticleGeneratorOp".Length
string extractedOpName = type.Name.Substring(19, type.Name.Length - 21);
String tmpOpName = opName.ToString();
String endName = Char.ToString(tmpOpName[0]);
for (int i = 1; i < tmpOpName.ToString().Length; i++)
{
endName += Char.ToLower(tmpOpName[i]);
}
return String.Compare(endName, extractedOpName, true) == 0;
}
return false;
},
name
);
string filename = null;
if (typeof(FileStream).IsInstanceOfType(BaseStream))
{
filename = (BaseStream as FileStream).Name;
}
//Debug.Assert(opClasses.Length > 0, "Error deserializing ParticleGenOp", "'{0}' unknown\nat index {1}{2}",
// name, BaseStream.Position, filename != null ? "\nin file " + filename : "");
// Find deserializing constructor
constructor = opClasses[0].GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
new Type[] { typeof(Eq2Reader), typeof(byte) },
null);
Debug.Assert(constructor != null, "Deserializing constructor not found on class " + opClasses[0].Name);
particleOpCache[name] = constructor;
}
// Invoke deserialization
object particleGeneratorOp = constructor.Invoke(new object[] { this, classVersion });
// Forces an exception to be thrown if the object is not a VeBase
return (VeParticleGeneratorOp)particleGeneratorOp;
}
///
/// Reads a string in Everquest 2 format from the stream.
///
///
/// Calling this method is equivalent to calling ReadString(1).
///
/// Error encountered while deserializing the string.
/// String read from the stream.
public override string ReadString()
{
return ReadString(1);
}
///
/// Reads a string in Everquest 2 format from the stream using the specified length scale.
///
///
/// The Everquest 2 binary format stores the length of a string directly preceding the string contents.
/// This length has a variable size of 1 to 4 bytes and is stored in little-endian format.
///
/// Size in bytes of string length.
/// Error encountered while deserializing the string.
/// String read from the stream.
public virtual string ReadString(uint lengthSize)
{
#region Preconditions
Debug.Assert(lengthSize >= 1 && lengthSize <= 4, "lengthSize must be between 1 and 4 bytes");
#endregion
// Read string length
uint length = 0;
string str = "";
if ( lengthSize > 1 )
{
for (int i = 0; i < lengthSize; ++i)
{
length += (uint)ReadByte() << 8 * i;
}
// Read string contents
str = new string(ReadChars((int)length));
return str;
}
bool override_ = false;
if (this.BaseStream.Position > 0)
override_ = true;
do
{
long pos = this.BaseStream.Position;
char curChar = (char)PeekChar();
if (this.BaseStream.Position+1 >= this.BaseStream.Length)
break;
if (curChar == 0)
{
byte val = ReadByte();
curChar = (char)PeekChar();
}
bool isStr = Char.IsLetterOrDigit(curChar);
if (!isStr || override_)
{
byte val = ReadByte();
char[] chars_ = ReadChars(val);
for(int i=0;i classCache = new Dictionary();
private IDictionary particleOpCache = new Dictionary();
#endregion
}
}