using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Threading;
using HakInstaller.Utilities;
namespace HakInstaller
{
///
/// This class is a dictionary of hak properties. Each property contains
/// a string collection of values tied to the property.
///
public class HakPropertyDictionary: DictionaryBase
{
#region public properties/methods
///
/// Indexer to get the StringCollection for a given property
///
public StringCollection this[string property]
{
get { return InnerHashtable[property] as StringCollection; }
}
///
/// Default Constructor
///
public HakPropertyDictionary()
{
}
///
/// Adds a new property to the collection, creating a blank StringCollection
/// for it.
///
///
public void Add(string property)
{
InnerHashtable.Add(property, new StringCollection());
}
#endregion
}
///
/// This class represents a .hif file. This file contains information about
/// a 'hak' (hak in this case consisting of a collection of ERF, TLK, and HAK
/// files, along with a list of items in the module to modify). It provides
/// functionality to read the file into memory and access the various pieces of
/// the file.
///
public class HakInfo
{
#region public properties/methods
///
/// Gets the name of the HIF minus the extension.
///
public string Name
{ get { return Path.GetFileNameWithoutExtension(fileInfo.Name); } }
///
/// Gets the title of the HIF. This is the HIF's title property if
/// it has one, or it's file name if it doesn't.
///
public string Title
{
get
{
StringCollection title = GetStrings(TitleKey, string.Empty);
return null == title || 0 == title.Count || string.Empty == title[0] ?
Name : title[0];
}
}
///
/// Gets the version number of the HIF.
///
public float Version
{
get
{
try
{
StringCollection version = GetStrings(VersionKey, string.Empty);
return (float) Convert.ToDouble(version[0], cultureUSA);
}
catch (Exception)
{
return 0;
}
}
}
///
/// Gets the version of the HIF as a text string.
///
public string VersionText
{
get
{
try
{
return GetStrings(VersionKey, string.Empty)[0];
}
catch (Exception)
{
return string.Empty;
}
}
}
///
/// Gets the minimum version number of NWN required to install the HIF.
///
public float RequiredNWNVersion
{
get
{
try
{
// Get the required string array and look for a string that starts with a
// digit, that would be the NWN version, return it if we find it.
StringCollection required = GetStrings(RequiredNWNVersionKey, string.Empty);
foreach (string s in required)
{
if (Char.IsDigit(s[0])) return (float) Convert.ToDouble(required[0], cultureUSA);
}
return 0;
}
catch (Exception)
{
return 0;
}
}
}
///
/// Returns true if XP1 is required.
///
public bool IsXP1Required
{
get
{
try
{
// Get the required string array and look for "XP1" or "Undrentide".
StringCollection required = GetStrings(RequiredNWNVersionKey, string.Empty);
foreach (string s in required)
{
if (0 == string.Compare("XP1", s, true, CultureInfo.InvariantCulture) ||
0 == string.Compare("Undrentide", s, true, CultureInfo.InvariantCulture))
return true;
}
}
catch (Exception)
{}
return false;
}
}
///
/// Returns true if XP2 is required.
///
public bool IsXP2Required
{
get
{
try
{
// Get the required string array and look for "XP1" or "Undrentide".
StringCollection required = GetStrings(RequiredNWNVersionKey, string.Empty);
foreach (string s in required)
{
if (0 == string.Compare("XP2", s, true, CultureInfo.InvariantCulture) ||
0 == string.Compare("Underdark", s, true, CultureInfo.InvariantCulture))
return true;
}
}
catch (Exception)
{}
return false;
}
}
///
/// Gets the list of ERF files to add to the collection.
///
public StringCollection Erfs { get { return components[ErfKey] as StringCollection; } }
///
/// Gets the dictionary of module properties that must be added/modified.
///
public HakPropertyDictionary ModuleProperties
{ get { return components[ModuleKey] as HakPropertyDictionary; } }
///
/// Class constructor to load a .hif file from disk.
///
/// The name of the hif file, hif files should
/// all live in the hak directory.
public HakInfo(string fileName)
{
// Force the thread to use the invariant culture to make the install
// code work on foreign language versions of windows.
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
try
{
fileInfo = new FileInfo(fileName);
InitializeComponents();
using(StreamReader reader = new StreamReader(fileName))
{
// Loop through all of the lines in the file.
for (int i = 1; reader.Peek() > -1; i++)
{
// Read the references line and split off the 2da file name and the references.
string line = reader.ReadLine();
line = line.Trim();
// If the line is blank or begins with a '#' ignore it.
if (0 == line.Length || '#' == line[0]) continue;
// Split the line into the type and references. If we don't get both
// parts the the line has a syntax error.
string[] strings = line.Split(':');
if (2 != strings.Length)
ThrowException("{0}: line {1}: syntax error", fileName, i.ToString());
// Save the component type and data.
string componentType = strings[0].Trim().ToLower();
string data = strings[1].Trim();
// Check to see if the component has a property. If there is
// a '.' in the name then it has a sub type, we need to split
// the component type into the type and property.
string componentProperty = string.Empty;
if (-1 != componentType.IndexOf('.'))
{
strings = componentType.Split('.');
componentType = strings[0];
componentProperty = strings[1];
}
// Split the various values, in case this can contain multiple
// values, and add each to the collection.
strings = data.Split(',');
StringCollection coll = GetStrings(componentType, componentProperty);
foreach (string s in strings)
coll.Add(s.Trim());
}
}
// The hak may or may not have a version number in it, if it doesn't add 0 so
// we always have a version number for lookup.
StringCollection version = GetStrings(VersionKey, string.Empty);
if (0 == version.Count) version.Add("0");
}
finally
{
Thread.CurrentThread.CurrentCulture = currentCulture;
}
}
///
/// Validates the HIF to make sure that it can be installed, returning an error
/// message if it cannot.
///
/// The error message if the HIF cannot be installed, or
/// string.Empty if the HIF can be installed
/// True if the HIF can be installed, false if it cannot.
public bool Validate(out string error)
{
error = string.Empty;
// If the HIF has a minimum required version and the current NWN install isn't
// high enough then error out right away.
if (RequiredNWNVersion > 0 &&
(float) Convert.ToDouble(NWN.NWNInfo.Version, cultureUSA) < RequiredNWNVersion)
{
error = StringResources.GetString("ValidateNWNVersionError",
Title, RequiredNWNVersion, NWN.NWNInfo.Version);
return false;
}
// If the content requires XP1 then validate it.
if (IsXP1Required && !NWN.NWNInfo.IsXP1Installed)
{
error = StringResources.GetString("ValidateNWNXP1Error", Title);
return false;
}
// If the content requies XP2 then validate it.
if (IsXP2Required && !NWN.NWNInfo.IsXP2Installed)
{
error = StringResources.GetString("ValidateNWNXP2Error", Title);
return false;
}
// Build a list of ALL of the files referenced by the HIF.
StringCollection files = new StringCollection();
StringCollection strings = this.GetStrings(ErfKey, string.Empty);
foreach (string s in strings) files.Add(NWN.NWNInfo.GetFullFilePath(s));
strings = GetStrings(ModuleKey, "hak");
foreach (string s in strings) files.Add(NWN.NWNInfo.GetFullFilePath(s));
strings = GetStrings(ModuleKey, "tlk");
foreach (string s in strings) files.Add(NWN.NWNInfo.GetFullFilePath(s));
// Loop through all of the files checking to see which, if any, are missing.
string missingFiles = string.Empty;
foreach (string file in files)
{
// If the file is missing add it to our missing files string.
if (!File.Exists(file))
{
if (0 == missingFiles.Length) missingFiles += "\r\n";
missingFiles += "\r\n\t";
missingFiles += NWN.NWNInfo.GetPartialFilePath(Path.GetFileName(file));
}
}
// If there are missing files then format the error message and return it.
if (missingFiles.Length > 0)
error = StringResources.GetString("ValidateMissingFilesError", Title, missingFiles);
return 0 == error.Length;
}
///
/// Override of ToString() to return the name of the HIF.
///
///
public override string ToString()
{
return Name;
}
#endregion
#region private static fields/properties/methods
// Create a CultureInfo for US English to do proper number conversion.
private static CultureInfo cultureUSA = new CultureInfo(0x0409);
#endregion
#region private fields/properties/methods
///
/// Gets the string collection for the specified (type, property) from the
/// hash table.
///
/// The component type
/// The property, or string.Empty if
/// the type does not support properties
/// The string collection for the (type, property).
private StringCollection GetStrings(string componentType, string componentProperty)
{
// Get the value for the given component if we can't find it
// then throw an exception.
object o = components[componentType];
if (null == o) ThrowException("Unknown type {0}", componentType);
if (string.Empty == componentProperty)
{
// No sub-type, the value for this component type should be
// a string collection.
if (!(o is StringCollection)) ThrowException("Type {0} requires a property", componentType);
return (StringCollection) o;
}
else
{
// Get the hashtable for the type, if there is no hashtable
// then throw an exception.
HakPropertyDictionary hash = o as HakPropertyDictionary;
if (null == hash) ThrowException("Type {0} cannot have properties", componentType);
// Get the string collection for the property, if there is no
// collection for the property yet then create one.
StringCollection coll = hash[componentProperty];
if (null == coll)
{
hash.Add(componentProperty);
coll = hash[componentProperty];
}
return coll;
}
}
///
/// Initializes the components hash table.
///
private void InitializeComponents()
{
// Create the hashtable then walk the key/value arrays, creating
// the proper value objects for the keys.
components = new Hashtable();
for (int i = 0; i < Keys.Length; i++)
{
System.Reflection.ConstructorInfo ci = Types[i].GetConstructor(new Type[0]);
object val = ci.Invoke(new object[0]);
components.Add(Keys[i], val);
}
}
///
/// Throws an exception with the specified message.
///
/// Format string
/// Format arguments
private void ThrowException(string format, params object[] args)
{
System.Text.StringBuilder b = new System.Text.StringBuilder();
b.AppendFormat(format, args);
throw new Exception(b.ToString());
}
// Define constants for the various component types.
private const string ErfKey = "erf";
private const string ModuleKey = "module";
private const string VersionKey = "version";
private const string RequiredNWNVersionKey = "minnwnversion";
private const string TitleKey = "title";
// Arrays that define the supported component types. Keys is an array of
// key values which match what is on the left of the ':' in the hif file.
// Types is the type of object that is placed in the hash table for each
// key.
private string[] Keys = new string[]
{
VersionKey,
RequiredNWNVersionKey,
ErfKey,
TitleKey,
ModuleKey
};
private Type[] Types = new Type[]
{
typeof(StringCollection),
typeof(StringCollection),
typeof(StringCollection),
typeof(StringCollection),
typeof(HakPropertyDictionary)
};
private Hashtable components;
private FileInfo fileInfo;
#endregion
}
}