Eq2Reader.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. #region License information
  2. // ----------------------------------------------------------------------------
  3. //
  4. // libeq2 - A library for analyzing the Everquest II File Format
  5. // Blaz (blaz@blazlabs.com)
  6. //
  7. // This program is free software; you can redistribute it and/or
  8. // modify it under the terms of the GNU General Public License
  9. // as published by the Free Software Foundation; either version 2
  10. // of the License, or (at your option) any later version.
  11. //
  12. // This program is distributed in the hope that it will be useful,
  13. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. // GNU General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU General Public License
  18. // along with this program; if not, write to the Free Software
  19. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  20. //
  21. // ( The full text of the license can be found in the License.txt file )
  22. //
  23. // ----------------------------------------------------------------------------
  24. #endregion
  25. #region Using directives
  26. using System;
  27. using System.IO;
  28. using System.Diagnostics;
  29. using System.Reflection;
  30. using System.Collections.Generic;
  31. using Everquest2.Visualization;
  32. using Everquest2.Visualization.ParticleGenerator;
  33. #endregion
  34. namespace Everquest2.Util
  35. {
  36. public class Eq2Reader : BinaryReader
  37. {
  38. #region Methods
  39. public Eq2Reader(Stream stream) : base(stream, System.Text.Encoding.ASCII)
  40. {
  41. }
  42. #region Disposable semantics
  43. public void Dispose()
  44. {
  45. base.Dispose(true);
  46. }
  47. ~Eq2Reader()
  48. {
  49. base.Dispose(false);
  50. }
  51. #endregion
  52. /// <summary>
  53. /// Reads an Everquest 2 object from the stream.
  54. /// </summary>
  55. /// <remarks>
  56. /// If the object is a node object, none of its children (if any) are read.
  57. /// To read a node object and all its children use <see cref="ReadNodeObject()"/>.
  58. /// </remarks>
  59. /// <exception cref="DeserializationException">Error encountered while deserializing the object.</exception>
  60. /// <returns>Deserialized object.</returns>
  61. public virtual VeBase ReadObject()
  62. {
  63. long startPos = BaseStream.Position;
  64. // Read class name
  65. string className = ReadString();
  66. if (className.Length < 1)
  67. return null;
  68. ConstructorInfo constructor = null;
  69. // Lookup class in cache
  70. if (classCache.ContainsKey(className))
  71. {
  72. constructor = classCache[className];
  73. }
  74. else
  75. {
  76. // Image(2020): ClassNames no longer include "Ve"
  77. if (!className.StartsWith("Ve"))
  78. className = "Ve" + className;
  79. // Find class in current assembly
  80. Type classType = GetType().Assembly.GetType("Everquest2.Visualization." + className, false);
  81. string filename = null;
  82. if (typeof(FileStream).IsInstanceOfType(BaseStream))
  83. {
  84. filename = (BaseStream as System.IO.FileStream).Name;
  85. }
  86. //Debug.Assert(classType != null, "Invalid class name!", "Error getting class type at index {0}{1}",
  87. // BaseStream.Position,
  88. // filename != null ? "\n in file " + filename : "");
  89. // Find deserializing constructor
  90. constructor = classType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
  91. null,
  92. new Type[] { typeof(Eq2Reader), typeof(StreamingContext) },
  93. null);
  94. Debug.Assert(constructor != null, "Deserializing constructor not found on class " + classType.Name);
  95. classCache[className] = constructor;
  96. }
  97. // Create streaming context
  98. StreamingContext context = new StreamingContext();
  99. // Read parent chain count
  100. byte parentChainCount = ReadByte();
  101. byte realParentChainCount = 0;
  102. Type type = constructor.DeclaringType;
  103. while (type != typeof(Object))
  104. {
  105. type = type.BaseType;
  106. ++realParentChainCount;
  107. }
  108. Debug.Assert(parentChainCount == realParentChainCount, "Parent chain count changed for class " + className + " (is " + parentChainCount + ", should be " + realParentChainCount + ")");
  109. // Read class versions. They are stored from derived to base.
  110. byte[] classVersions = ReadBytes(parentChainCount);
  111. Type curClassType = constructor.DeclaringType;
  112. uint curClassIndex = 0;
  113. context.ClassVersions = new Dictionary<Type, byte>(parentChainCount);
  114. // Traverse the class hierarchy from derived to base setting the class version info
  115. while (curClassIndex < parentChainCount)
  116. {
  117. context.ClassVersions[curClassType] = classVersions[curClassIndex];
  118. // TODO: Enforce correct class versions. From the time being just skip those class version bytes.
  119. curClassType = curClassType.BaseType;
  120. ++curClassIndex;
  121. }
  122. // Invoke deserialization
  123. object eq2Object = constructor.Invoke(new object[] { this, context });
  124. // Forces an exception to be thrown if the object is not a VeBase
  125. return (VeBase)eq2Object;
  126. }
  127. /// <summary>
  128. /// Reads an Everquest 2 node hierarchy from the stream.
  129. /// </summary>
  130. /// <remarks>
  131. /// This methods deserializes a hierarchy of Everquest 2 nodes.
  132. /// An exception is thrown if any of the objects read is not a node.
  133. /// </remarks>
  134. /// <exception cref="InvalidCastException">One of the objects from the hierarchy is not an Everquest 2 node.</exception>
  135. /// <exception cref="DeserializationException">Error encountered while deserializing the object.</exception>
  136. /// <returns>Deserialized hierarchy of objects.</returns>
  137. public virtual VeNode ReadNodeObject()
  138. {
  139. // Read root node. If the object is not a VeNode the cast will throw an exception.
  140. VeNode node = (VeNode)ReadObject();
  141. if (node == null)
  142. return null;
  143. // Read children count
  144. uint childrenCount = ReadUInt32();
  145. // Read children
  146. for (uint i = 0; i < childrenCount; ++i)
  147. {
  148. VeNode child = ReadNodeObject();
  149. node.AddChild(child);
  150. }
  151. return node;
  152. }
  153. /// <summary>
  154. /// Reads an Everquest 2 particle generator operation from the stream.
  155. /// </summary>
  156. /// <remarks>
  157. /// This method is usually called during the deserialization of a VeParticleGeneratorNode object.
  158. /// An exception is the particle generator operation is unknown.
  159. /// </remarks>
  160. /// <param name="classVersion">Class version of the calling particle generator class.</param>
  161. /// <exception cref="DeserializationException">Error encountered while deserializing the operation.</exception>
  162. /// <returns>Deserialized particle generator operation.</returns>
  163. public virtual VeParticleGeneratorOp ReadParticleGeneratorOp(byte classVersion)
  164. {
  165. // Read operation name
  166. string name = ReadString(2);
  167. ConstructorInfo constructor = null;
  168. // Lookup op name in cache
  169. if (particleOpCache.ContainsKey(name))
  170. {
  171. constructor = particleOpCache[name];
  172. }
  173. else
  174. {
  175. // Find the operation class given its name as read from the stream
  176. Type[] opClasses = GetType().Module.FindTypes
  177. (
  178. delegate(Type type, object opName)
  179. {
  180. if (type.Namespace == "Everquest2.Visualization.ParticleGenerator" &&
  181. type.Name.StartsWith("VeParticleGenerator") &&
  182. type.Name.EndsWith("Op") &&
  183. type.Name != "VeParticleGeneratorOp")
  184. {
  185. // Note: 19 == "VeParticleGenerator".Length and 21 == "VeParticleGeneratorOp".Length
  186. string extractedOpName = type.Name.Substring(19, type.Name.Length - 21);
  187. String tmpOpName = opName.ToString();
  188. String endName = Char.ToString(tmpOpName[0]);
  189. for (int i = 1; i < tmpOpName.ToString().Length; i++)
  190. {
  191. endName += Char.ToLower(tmpOpName[i]);
  192. }
  193. return String.Compare(endName, extractedOpName, true) == 0;
  194. }
  195. return false;
  196. },
  197. name
  198. );
  199. string filename = null;
  200. if (typeof(FileStream).IsInstanceOfType(BaseStream))
  201. {
  202. filename = (BaseStream as FileStream).Name;
  203. }
  204. //Debug.Assert(opClasses.Length > 0, "Error deserializing ParticleGenOp", "'{0}' unknown\nat index {1}{2}",
  205. // name, BaseStream.Position, filename != null ? "\nin file " + filename : "");
  206. // Find deserializing constructor
  207. constructor = opClasses[0].GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
  208. null,
  209. new Type[] { typeof(Eq2Reader), typeof(byte) },
  210. null);
  211. Debug.Assert(constructor != null, "Deserializing constructor not found on class " + opClasses[0].Name);
  212. particleOpCache[name] = constructor;
  213. }
  214. // Invoke deserialization
  215. object particleGeneratorOp = constructor.Invoke(new object[] { this, classVersion });
  216. // Forces an exception to be thrown if the object is not a VeBase
  217. return (VeParticleGeneratorOp)particleGeneratorOp;
  218. }
  219. /// <summary>
  220. /// Reads a string in Everquest 2 format from the stream.
  221. /// </summary>
  222. /// <remarks>
  223. /// Calling this method is equivalent to calling ReadString(1).
  224. /// </remarks>
  225. /// <exception cref="DeserializationException">Error encountered while deserializing the string.</exception>
  226. /// <returns>String read from the stream.</returns>
  227. public override string ReadString()
  228. {
  229. return ReadString(1);
  230. }
  231. /// <summary>
  232. /// Reads a string in Everquest 2 format from the stream using the specified length scale.
  233. /// </summary>
  234. /// <remarks>
  235. /// The Everquest 2 binary format stores the length of a string directly preceding the string contents.
  236. /// This length has a variable size of 1 to 4 bytes and is stored in little-endian format.
  237. /// </remarks>
  238. /// <param name="lengthSize">Size in bytes of string length.</param>
  239. /// <exception cref="DeserializationException">Error encountered while deserializing the string.</exception>
  240. /// <returns>String read from the stream.</returns>
  241. public virtual string ReadString(uint lengthSize)
  242. {
  243. #region Preconditions
  244. Debug.Assert(lengthSize >= 1 && lengthSize <= 4, "lengthSize must be between 1 and 4 bytes");
  245. #endregion
  246. // Read string length
  247. uint length = 0;
  248. string str = "";
  249. if ( lengthSize > 1 )
  250. {
  251. for (int i = 0; i < lengthSize; ++i)
  252. {
  253. length += (uint)ReadByte() << 8 * i;
  254. }
  255. // Read string contents
  256. str = new string(ReadChars((int)length));
  257. return str;
  258. }
  259. bool override_ = false;
  260. if (this.BaseStream.Position > 0)
  261. override_ = true;
  262. do
  263. {
  264. long pos = this.BaseStream.Position;
  265. char curChar = (char)PeekChar();
  266. if (this.BaseStream.Position+1 >= this.BaseStream.Length)
  267. break;
  268. if (curChar == 0)
  269. {
  270. byte val = ReadByte();
  271. curChar = (char)PeekChar();
  272. }
  273. bool isStr = Char.IsLetterOrDigit(curChar);
  274. if (!isStr || override_)
  275. {
  276. byte val = ReadByte();
  277. char[] chars_ = ReadChars(val);
  278. for(int i=0;i<chars_.Length;i++)
  279. {
  280. if (i == 0 && chars_[i] != '/' && chars_[i] != '.' && chars_[i] != '_' && !Char.IsLetterOrDigit(chars_[i]))
  281. {
  282. this.BaseStream.Position = pos;
  283. break;
  284. }
  285. else
  286. str += chars_[i];
  287. }
  288. break;
  289. }
  290. else
  291. str += ReadChar();
  292. } while (true);
  293. return str;
  294. }
  295. #endregion
  296. #region Fields
  297. private IDictionary<string, ConstructorInfo> classCache = new Dictionary<string, ConstructorInfo>();
  298. private IDictionary<string, ConstructorInfo> particleOpCache = new Dictionary<string, ConstructorInfo>();
  299. #endregion
  300. }
  301. }