Eq2Reader.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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. try
  91. {
  92. constructor = classType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
  93. null,
  94. new Type[] { typeof(Eq2Reader), typeof(StreamingContext) },
  95. null);
  96. Debug.Assert(constructor != null, "Deserializing constructor not found on class " + classType.Name);
  97. classCache[className] = constructor;
  98. }catch(Exception ex)
  99. {
  100. return null;
  101. }
  102. }
  103. // Create streaming context
  104. StreamingContext context = new StreamingContext();
  105. // Read parent chain count
  106. byte parentChainCount = ReadByte();
  107. byte realParentChainCount = 0;
  108. Type type = constructor.DeclaringType;
  109. while (type != typeof(Object))
  110. {
  111. type = type.BaseType;
  112. ++realParentChainCount;
  113. }
  114. Debug.Assert(parentChainCount == realParentChainCount, "Parent chain count changed for class " + className + " (is " + parentChainCount + ", should be " + realParentChainCount + ")");
  115. // Read class versions. They are stored from derived to base.
  116. byte[] classVersions = ReadBytes(parentChainCount);
  117. Type curClassType = constructor.DeclaringType;
  118. uint curClassIndex = 0;
  119. context.ClassVersions = new Dictionary<Type, byte>(parentChainCount);
  120. // Traverse the class hierarchy from derived to base setting the class version info
  121. while (curClassIndex < parentChainCount)
  122. {
  123. context.ClassVersions[curClassType] = classVersions[curClassIndex];
  124. // TODO: Enforce correct class versions. From the time being just skip those class version bytes.
  125. curClassType = curClassType.BaseType;
  126. ++curClassIndex;
  127. }
  128. // Invoke deserialization
  129. object eq2Object = constructor.Invoke(new object[] { this, context });
  130. // Forces an exception to be thrown if the object is not a VeBase
  131. return (VeBase)eq2Object;
  132. }
  133. /// <summary>
  134. /// Reads an Everquest 2 node hierarchy from the stream.
  135. /// </summary>
  136. /// <remarks>
  137. /// This methods deserializes a hierarchy of Everquest 2 nodes.
  138. /// An exception is thrown if any of the objects read is not a node.
  139. /// </remarks>
  140. /// <exception cref="InvalidCastException">One of the objects from the hierarchy is not an Everquest 2 node.</exception>
  141. /// <exception cref="DeserializationException">Error encountered while deserializing the object.</exception>
  142. /// <returns>Deserialized hierarchy of objects.</returns>
  143. public virtual VeNode ReadNodeObject()
  144. {
  145. // Read root node. If the object is not a VeNode the cast will throw an exception.
  146. VeNode node = (VeNode)ReadObject();
  147. if (node == null)
  148. return null;
  149. // Read children count
  150. uint childrenCount = ReadUInt32();
  151. // Read children
  152. for (uint i = 0; i < childrenCount; ++i)
  153. {
  154. VeNode child = ReadNodeObject();
  155. node.AddChild(child);
  156. }
  157. return node;
  158. }
  159. /// <summary>
  160. /// Reads an Everquest 2 particle generator operation from the stream.
  161. /// </summary>
  162. /// <remarks>
  163. /// This method is usually called during the deserialization of a VeParticleGeneratorNode object.
  164. /// An exception is the particle generator operation is unknown.
  165. /// </remarks>
  166. /// <param name="classVersion">Class version of the calling particle generator class.</param>
  167. /// <exception cref="DeserializationException">Error encountered while deserializing the operation.</exception>
  168. /// <returns>Deserialized particle generator operation.</returns>
  169. public virtual VeParticleGeneratorOp ReadParticleGeneratorOp(byte classVersion)
  170. {
  171. // Read operation name
  172. string name = ReadString(2);
  173. ConstructorInfo constructor = null;
  174. // Lookup op name in cache
  175. if (particleOpCache.ContainsKey(name))
  176. {
  177. constructor = particleOpCache[name];
  178. }
  179. else
  180. {
  181. // Find the operation class given its name as read from the stream
  182. Type[] opClasses = GetType().Module.FindTypes
  183. (
  184. delegate(Type type, object opName)
  185. {
  186. if (type.Namespace == "Everquest2.Visualization.ParticleGenerator" &&
  187. type.Name.StartsWith("VeParticleGenerator") &&
  188. type.Name.EndsWith("Op") &&
  189. type.Name != "VeParticleGeneratorOp")
  190. {
  191. // Note: 19 == "VeParticleGenerator".Length and 21 == "VeParticleGeneratorOp".Length
  192. string extractedOpName = type.Name.Substring(19, type.Name.Length - 21);
  193. String tmpOpName = opName.ToString();
  194. String endName = Char.ToString(tmpOpName[0]);
  195. for (int i = 1; i < tmpOpName.ToString().Length; i++)
  196. {
  197. endName += Char.ToLower(tmpOpName[i]);
  198. }
  199. return String.Compare(endName, extractedOpName, true) == 0;
  200. }
  201. return false;
  202. },
  203. name
  204. );
  205. string filename = null;
  206. if (typeof(FileStream).IsInstanceOfType(BaseStream))
  207. {
  208. filename = (BaseStream as FileStream).Name;
  209. }
  210. //Debug.Assert(opClasses.Length > 0, "Error deserializing ParticleGenOp", "'{0}' unknown\nat index {1}{2}",
  211. // name, BaseStream.Position, filename != null ? "\nin file " + filename : "");
  212. // Find deserializing constructor
  213. constructor = opClasses[0].GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
  214. null,
  215. new Type[] { typeof(Eq2Reader), typeof(byte) },
  216. null);
  217. Debug.Assert(constructor != null, "Deserializing constructor not found on class " + opClasses[0].Name);
  218. particleOpCache[name] = constructor;
  219. }
  220. // Invoke deserialization
  221. object particleGeneratorOp = constructor.Invoke(new object[] { this, classVersion });
  222. // Forces an exception to be thrown if the object is not a VeBase
  223. return (VeParticleGeneratorOp)particleGeneratorOp;
  224. }
  225. /// <summary>
  226. /// Reads a string in Everquest 2 format from the stream.
  227. /// </summary>
  228. /// <remarks>
  229. /// Calling this method is equivalent to calling ReadString(1).
  230. /// </remarks>
  231. /// <exception cref="DeserializationException">Error encountered while deserializing the string.</exception>
  232. /// <returns>String read from the stream.</returns>
  233. public override string ReadString()
  234. {
  235. return ReadString(1);
  236. }
  237. /// <summary>
  238. /// Reads a string in Everquest 2 format from the stream using the specified length scale.
  239. /// </summary>
  240. /// <remarks>
  241. /// The Everquest 2 binary format stores the length of a string directly preceding the string contents.
  242. /// This length has a variable size of 1 to 4 bytes and is stored in little-endian format.
  243. /// </remarks>
  244. /// <param name="lengthSize">Size in bytes of string length.</param>
  245. /// <exception cref="DeserializationException">Error encountered while deserializing the string.</exception>
  246. /// <returns>String read from the stream.</returns>
  247. public virtual string ReadString(uint lengthSize)
  248. {
  249. #region Preconditions
  250. Debug.Assert(lengthSize >= 1 && lengthSize <= 4, "lengthSize must be between 1 and 4 bytes");
  251. #endregion
  252. // Read string length
  253. uint length = 0;
  254. string str = "";
  255. if ( lengthSize > 1 )
  256. {
  257. for (int i = 0; i < lengthSize; ++i)
  258. {
  259. length += (uint)ReadByte() << 8 * i;
  260. }
  261. // Read string contents
  262. str = new string(ReadChars((int)length));
  263. return str;
  264. }
  265. bool override_ = false;
  266. if (this.BaseStream.Position > 0)
  267. override_ = true;
  268. do
  269. {
  270. long pos = this.BaseStream.Position;
  271. char curChar = (char)PeekChar();
  272. if (this.BaseStream.Position+1 >= this.BaseStream.Length)
  273. break;
  274. if (curChar == 0)
  275. {
  276. byte val = ReadByte();
  277. curChar = (char)PeekChar();
  278. }
  279. bool isStr = Char.IsLetterOrDigit(curChar);
  280. if (!isStr || override_)
  281. {
  282. byte val = ReadByte();
  283. char[] chars_ = ReadChars(val);
  284. for(int i=0;i<chars_.Length;i++)
  285. {
  286. if (i == 0 && chars_[i] != '/' && chars_[i] != '.' && chars_[i] != '_' && !Char.IsLetterOrDigit(chars_[i]))
  287. {
  288. this.BaseStream.Position = pos;
  289. break;
  290. }
  291. else
  292. str += chars_[i];
  293. }
  294. break;
  295. }
  296. else
  297. str += ReadChar();
  298. } while (true);
  299. return str;
  300. }
  301. #endregion
  302. #region Fields
  303. private IDictionary<string, ConstructorInfo> classCache = new Dictionary<string, ConstructorInfo>();
  304. private IDictionary<string, ConstructorInfo> particleOpCache = new Dictionary<string, ConstructorInfo>();
  305. #endregion
  306. }
  307. }