From 60b32d23ee92add81869d7376b4da2b83cd7d410 Mon Sep 17 00:00:00 2001 From: figment Date: Tue, 22 Apr 2014 20:48:28 -0700 Subject: [PATCH] Additional changes to support multiple games (In theory can read Morrowind,Oblivion,Fallout3,FalloutNV,Skyrim) Decode more of Oblivion file into record structure. Add ElementGroup information in HTML renderer (basically for repeat areas show as array index) --- Application/Properties/Settings.Designer.cs | 2 +- Application/Properties/Settings.settings | 2 +- Application/UI/Forms/MainView.cs | 28 + Application/UI/Forms/MainView.designer.cs | 10 +- Application/UI/Forms/MainView.resx | 19 +- Application/UI/Services/Options.cs | 21 +- Application/app.config | 2 +- Domain/Data/DomainDefinition.cs | 23 +- Domain/Model/Element.cs | 62 +- Domain/Model/GroupRecord.cs | 26 +- Domain/Model/Plugin.cs | 59 +- Domain/Model/Record.cs | 19 +- Domain/Model/SubRecord.cs | 20 +- conf/Domains.ini | 11 +- conf/Morrowind/RecordStructure.xml | 1280 +++++++++++++++++++ conf/Oblivion/RecordStructure.xml | 167 ++- scripts/plugins/shared/util.py | 6 +- scripts/renderer.py | 20 +- test/test4.py | 16 +- 19 files changed, 1595 insertions(+), 198 deletions(-) create mode 100644 conf/Morrowind/RecordStructure.xml diff --git a/Application/Properties/Settings.Designer.cs b/Application/Properties/Settings.Designer.cs index 1c2d265..fc098a0 100644 --- a/Application/Properties/Settings.Designer.cs +++ b/Application/Properties/Settings.Designer.cs @@ -394,7 +394,7 @@ public string ExternalPythonEditor { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] + [global::System.Configuration.DefaultSettingValueAttribute("Skyrim")] public string DefaultDomain { get { return ((string)(this["DefaultDomain"])); diff --git a/Application/Properties/Settings.settings b/Application/Properties/Settings.settings index bd46e81..c0ef779 100644 --- a/Application/Properties/Settings.settings +++ b/Application/Properties/Settings.settings @@ -96,7 +96,7 @@ - + Skyrim \ No newline at end of file diff --git a/Application/UI/Forms/MainView.cs b/Application/UI/Forms/MainView.cs index 347cd47..c3e4c8f 100644 --- a/Application/UI/Forms/MainView.cs +++ b/Application/UI/Forms/MainView.cs @@ -165,6 +165,7 @@ public MainView() BaseRecord.ChildListChanged += PluginList_ChildListChanged; htmlContent.OnLinkClicked += htmlContent_OnLinkClicked; + InitializeGameOptions(); LocalizeApp(); PyInterpreter.InitPyInterpreter(); HtmlRenderer.Initialize(); @@ -172,6 +173,33 @@ public MainView() mruRegKey + "\\MRU", true, 16); } + private void InitializeGameOptions() + { + string defaultDomain = Properties.Settings.Default.DefaultDomain ?? "Skyrim"; + defaultGameSettingsToolStripMenuItem.DropDownItems.Clear(); + foreach (var domain in TESVSnip.Domain.Data.DomainDefinition.AllDomains()) + { + var item = new ToolStripMenuItem + { + Name = domain.Name, + Text = string.IsNullOrWhiteSpace(domain.DisplayName) ? domain.Name : domain.DisplayName, + Visible = true, + Enabled = true, + Tag = domain.Name, + Checked = defaultDomain == domain.Name, + }; + defaultGameSettingsToolStripMenuItem.DropDownItems.Add(item); + } + defaultGameSettingsToolStripMenuItem.DropDownItemClicked += + delegate(object sender, ToolStripItemClickedEventArgs e) + { + Properties.Settings.Default.DefaultDomain = e.ClickedItem.Tag.ToString(); + foreach (var item in defaultGameSettingsToolStripMenuItem.DropDownItems.OfType()) + item.Checked = Properties.Settings.Default.DefaultDomain == item.Tag.ToString(); + Options.Value.Reconfigure(); + }; + } + public static object Clipboard { get { return GetClipboardData(); } diff --git a/Application/UI/Forms/MainView.designer.cs b/Application/UI/Forms/MainView.designer.cs index 5a22531..75e7bcb 100644 --- a/Application/UI/Forms/MainView.designer.cs +++ b/Application/UI/Forms/MainView.designer.cs @@ -169,6 +169,7 @@ private void InitializeComponent() { this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripIncrInvalidRecStatus = new System.Windows.Forms.ToolStripLabel(); this.dockPanel = new WeifenLuo.WinFormsUI.Docking.DockPanel(); + this.defaultGameSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.toolStripIncrFind.SuspendLayout(); @@ -452,7 +453,8 @@ private void InitializeComponent() { this.resetDockingWindowsToolStripMenuItem, this.eSMFilterSettingsToolStripMenuItem, this.compressionSettingsToolStripMenuItem, - this.resetSettingsToDefaultsToolStripMenuItem}); + this.resetSettingsToDefaultsToolStripMenuItem, + this.defaultGameSettingsToolStripMenuItem}); this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; resources.ApplyResources(this.optionsToolStripMenuItem, "optionsToolStripMenuItem"); // @@ -1171,6 +1173,11 @@ private void InitializeComponent() { dockPanelSkin1.DockPaneStripSkin = dockPaneStripSkin1; this.dockPanel.Skin = dockPanelSkin1; // + // defaultGameSettingsToolStripMenuItem + // + this.defaultGameSettingsToolStripMenuItem.Name = "defaultGameSettingsToolStripMenuItem"; + resources.ApplyResources(this.defaultGameSettingsToolStripMenuItem, "defaultGameSettingsToolStripMenuItem"); + // // MainView // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -1332,5 +1339,6 @@ private void InitializeComponent() { private System.Windows.Forms.ToolStripMenuItem globalScriptsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem selectionScriptsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem editScriptsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem defaultGameSettingsToolStripMenuItem; } } \ No newline at end of file diff --git a/Application/UI/Forms/MainView.resx b/Application/UI/Forms/MainView.resx index db1d316..1777909 100644 --- a/Application/UI/Forms/MainView.resx +++ b/Application/UI/Forms/MainView.resx @@ -474,6 +474,15 @@ Reset settings to defaults + + 208, 22 + + + Default Game Settings + + + When Game context is not known, this game will be used as defaults. + 61, 20 @@ -805,7 +814,7 @@ 155, 17 - Skyrim (*.esm, *.esp)|*.esm;*.esp|Skyrim plugin (*.esp)|*.esp|Skyrim Master file (*.esm)|*.esm|All (*.*)|*.* + All Files (*.esm, *.esp)|*.esm;*.esp|Plugin file (*.esp)|*.esp|Master file (*.esm)|*.esm|All (*.*)|*.* Select plugin(s) to open @@ -814,7 +823,7 @@ 17, 17 - Skyrim (*.esm, *.esp)|*.esm;*.esp|Skyrim plugin (*.esp)|*.esp|Skyrim Master file (*.esm)|*.esm|All (*.*)|*.* + All Files (*.esm, *.esp)|*.esm;*.esp|Plugin file (*.esp)|*.esp|Master file (*.esm)|*.esm|All (*.*)|*.* Select path to save to @@ -2228,6 +2237,12 @@ System.Windows.Forms.ToolStripLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + defaultGameSettingsToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + MainView diff --git a/Application/UI/Services/Options.cs b/Application/UI/Services/Options.cs index c1b283f..16d24d7 100644 --- a/Application/UI/Services/Options.cs +++ b/Application/UI/Services/Options.cs @@ -20,11 +20,11 @@ private Options(string[] args) { this.SettingsDirectory = Environment.CurrentDirectory; this.ApplicationDirectory = Environment.CurrentDirectory; - this.SetupGameDirectory(); this.SetupApplicationDirectory(); this.SetupScriptHostDirectory(); + Reconfigure(); + SetupApplicationDirectory(); this.ParseCommandLine(args); - this.PrepareDirectories(); } /// @@ -83,6 +83,12 @@ public static void Initialize(string[] args) _instance = new Options(args); } + public void Reconfigure() + { + this.SetupGameDirectory(); + this.PrepareDirectories(); + } + /// /// Parse the command line array. /// @@ -198,12 +204,17 @@ private void SetupGameDirectory() { try { - using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Bethesda Softworks\Skyrim")) + string defaultDomain = Properties.Settings.Default.DefaultDomain ?? "Skyrim"; + var domain = TESVSnip.Domain.Data.DomainDefinition.Lookup(defaultDomain); + if (domain == null) + return; + + using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\" + domain.RegistryKey)) { //on 64bits if (key == null) { - using (var key2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Bethesda Softworks\Skyrim")) + using (var key2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\" + domain.RegistryKey)) { //on 32bits if (key2 == null) return; @@ -217,6 +228,8 @@ private void SetupGameDirectory() if (gameDirectory != null) { this.GameDataDirectory = Path.Combine(gameDirectory, "Data"); + if ( !Directory.Exists(this.GameDataDirectory) ) + this.GameDataDirectory = Path.Combine(gameDirectory, "Data files"); } } } diff --git a/Application/app.config b/Application/app.config index a51a8a0..41bcaa0 100644 --- a/Application/app.config +++ b/Application/app.config @@ -137,7 +137,7 @@ - + Skyrim diff --git a/Domain/Data/DomainDefinition.cs b/Domain/Data/DomainDefinition.cs index d3e17c6..258db71 100644 --- a/Domain/Data/DomainDefinition.cs +++ b/Domain/Data/DomainDefinition.cs @@ -34,6 +34,9 @@ public class DomainDefinition public string[] AllESMRecords { get; private set; } public string HEDRType { get; private set; } public float HEDRVersion { get; private set; } + public int HEDROffset { get; private set; } + public int HEDRRecSize { get; private set; } + public int RecSize { get; private set; } public Settings Settings { get; private set; } public bool Loaded { get; private set; } @@ -47,7 +50,7 @@ static DomainDefinition() var iniFile = Path.Combine(Folders.SettingsDirectory, "Domains.ini"); foreach (var section in IniFile.GetSectionNames(iniFile)) { - var values = IniFile.GetPropertyValues(section); + var values = IniFile.GetPropertyValues(iniFile, section); var define = new DomainDefinition(section); define.DisplayName = GetValue(values, "Display", section); define.Master = GetValue(values, "Master", section+".esm"); @@ -56,6 +59,9 @@ static DomainDefinition() define.AllESMRecords = GetValue(values, "AllESMRecords", "").Split(';'); define.HEDRType = GetValue(values, "HEDRType", "TES4"); define.HEDRVersion = float.Parse(GetValue(values, "HEDRVersion", "1.0")); + define.HEDROffset = int.Parse(GetValue(values, "HEDROffset", "4")); + define.HEDRRecSize = int.Parse(GetValue(values, "HEDRRecSize", "2")); + define.RecSize = int.Parse(GetValue(values, "RecSize", "16")); Domains[section] = define; } } @@ -77,10 +83,23 @@ public DomainDefinition(string name) } public static IEnumerable LoadedDomains() + { + return Domains.Values.Where(domain => domain.Loaded); + } + + public static IEnumerable AllDomains() { return Domains.Values; } + public static DomainDefinition Lookup(string p) + { + DomainDefinition define; + if (Domains.TryGetValue(p, out define)) + return define; + return null; + } + public static DomainDefinition Load(string p) { DomainDefinition define; @@ -116,6 +135,8 @@ public static DomainDefinition DetectDefinitionFromVersion(string type, float ve foreach (var domain in Domains.Values.Where(domain => type == domain.HEDRType && Math.Abs(version - domain.HEDRVersion) < EPSILON)) { + if (!domain.Loaded) + Load(domain.Name); return domain; } throw new Exception("File is not a known TES4 file (Unexpected version)"); diff --git a/Domain/Model/Element.cs b/Domain/Model/Element.cs index adc9b7d..b933269 100644 --- a/Domain/Model/Element.cs +++ b/Domain/Model/Element.cs @@ -16,22 +16,24 @@ public Element() { } - internal Element(ElementStructure es, byte[] data, int offset, int count) - : this(es, new ArraySegment(data, offset, count)) + internal Element(ElementStructure es, byte[] data, int offset, int count, Tuple[] indices) + : this(es, new ArraySegment(data, offset, count), indices) { } - internal Element(ElementStructure es, ArraySegment data) + internal Element(ElementStructure es, ArraySegment data, Tuple[] indices) { this.Structure = es; this.Data = data; + this.Indices = indices; } - internal Element(ElementStructure es, ElementValueType vt, ArraySegment data) + internal Element(ElementStructure es, ElementValueType vt, ArraySegment data, Tuple[] indices) { this.Structure = es; this.Data = data; this.type = vt; + this.Indices = indices; } /// @@ -53,6 +55,8 @@ public Element(ElementValueType vt, object value) public ElementStructure Structure { get; private set; } + public Tuple[] Indices { get; private set; } + public ElementValueType Type { get @@ -108,7 +112,7 @@ public object Value } } - internal static Element CreateElement(ElementStructure es, byte[] data, ref int offset, bool rawData) + internal static Element CreateElement(ElementStructure es, byte[] data, ref int offset, bool rawData, Tuple[] indices) { int maxlen = data.Length - offset; Element elem = null; @@ -119,38 +123,38 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int { case ElementValueType.Int: len = maxlen >= sizeof(int) ? sizeof(int) : maxlen; - elem = new Element(es, ElementValueType.Int, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.Int, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.UInt: case ElementValueType.FormID: len = maxlen >= sizeof(uint) ? sizeof(uint) : maxlen; - elem = new Element(es, ElementValueType.UInt, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.UInt, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.Float: len = maxlen >= sizeof(float) ? sizeof(float) : maxlen; - elem = new Element(es, ElementValueType.Float, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.Float, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.Short: len = maxlen >= sizeof(short) ? sizeof(short) : maxlen; - elem = new Element(es, ElementValueType.Short, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.Short, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.UShort: len = maxlen >= sizeof(ushort) ? sizeof(ushort) : maxlen; - elem = new Element(es, ElementValueType.UShort, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.UShort, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.SByte: len = maxlen >= sizeof(sbyte) ? sizeof(sbyte) : maxlen; - elem = new Element(es, ElementValueType.SByte, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.SByte, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.Byte: len = maxlen >= sizeof(byte) ? sizeof(byte) : maxlen; - elem = new Element(es, ElementValueType.Byte, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.Byte, new ArraySegment(data, offset, len), indices); offset += len; break; case ElementValueType.String: @@ -164,12 +168,12 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int { // raw form includes the zero termination byte len = len == 0 ? 0 : len + 1; - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len), indices); offset += len; } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len), indices); offset += len == 0 ? 0 : len + 1; } @@ -182,12 +186,12 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int if (rawData) { // raw data includes short prefix - elem = new Element(es, ElementValueType.BString, new ArraySegment(data, offset, len + 2)); + elem = new Element(es, ElementValueType.BString, new ArraySegment(data, offset, len + 2), indices); offset += len + 2; } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset + 2, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset + 2, len), indices); offset += len + 2; } } @@ -195,11 +199,11 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int { if (rawData) { - elem = new Element(es, ElementValueType.BString, new ArraySegment(new byte[2] { 0, 0 })); + elem = new Element(es, ElementValueType.BString, new ArraySegment(new byte[2] { 0, 0 }), indices); } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(new byte[0])); + elem = new Element(es, ElementValueType.String, new ArraySegment(new byte[0]), indices); } offset += maxlen; @@ -214,12 +218,12 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int if (rawData) { // raw data includes int prefix - elem = new Element(es, ElementValueType.IString, new ArraySegment(data, offset, len + 4)); + elem = new Element(es, ElementValueType.IString, new ArraySegment(data, offset, len + 4), indices); offset += len + 4; } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset + 4, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset + 4, len), indices); offset += len + 4; } } @@ -227,11 +231,11 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int { if (rawData) { - elem = new Element(es, ElementValueType.IString, new ArraySegment(new byte[4] { 0, 0, 0, 0 })); + elem = new Element(es, ElementValueType.IString, new ArraySegment(new byte[4] { 0, 0, 0, 0 }), indices); } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(new byte[0])); + elem = new Element(es, ElementValueType.String, new ArraySegment(new byte[0]), indices); } offset += maxlen; @@ -242,11 +246,11 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int len = maxlen >= 4 ? 4 : maxlen; if (rawData) { - elem = new Element(es, ElementValueType.Str4, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.Str4, new ArraySegment(data, offset, len), indices); } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len), indices); } offset += len; @@ -255,7 +259,7 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int case ElementValueType.LString: if (maxlen < sizeof(int)) { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, maxlen)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, maxlen), indices); offset += maxlen; } else @@ -265,7 +269,7 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int bool isString = TypeConverter.IsLikelyString(blob); if (!isString) { - elem = new Element(es, ElementValueType.UInt, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.UInt, new ArraySegment(data, offset, len), indices); offset += 4; } else @@ -280,12 +284,12 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int { // lstring as raw string includes the terminating null len = len == 0 ? 0 : len + 1; - elem = new Element(es, ElementValueType.LString, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.LString, new ArraySegment(data, offset, len), indices); offset += len; } else { - elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len)); + elem = new Element(es, ElementValueType.String, new ArraySegment(data, offset, len), indices); offset += len == 0 ? 0 : len + 1; } } @@ -294,7 +298,7 @@ internal static Element CreateElement(ElementStructure es, byte[] data, ref int break; default: - elem = new Element(es, ElementValueType.Blob, new ArraySegment(data, offset, maxlen)); + elem = new Element(es, ElementValueType.Blob, new ArraySegment(data, offset, maxlen), indices); offset += maxlen; break; } diff --git a/Domain/Model/GroupRecord.cs b/Domain/Model/GroupRecord.cs index 3eced8a..1a654d6 100644 --- a/Domain/Model/GroupRecord.cs +++ b/Domain/Model/GroupRecord.cs @@ -45,21 +45,20 @@ public GroupRecord(string data) this.UpdateShortDescription(); } - internal GroupRecord(uint Size, BinaryReader br, float version, Func recFilter, bool filterAll) + internal GroupRecord(uint Size, BinaryReader br, TESVSnip.Domain.Data.DomainDefinition define, Func recFilter, bool filterAll) { - bool isOblivion = Math.Abs(version - 1.0f) <= float.Epsilon * 10.0f; Name = "GRUP"; this.data = br.ReadBytes(4); this.groupType = br.ReadUInt32(); this.dateStamp = br.ReadUInt32(); string contentType = this.groupType == 0 ? Encoding.Instance.GetString(this.data) : string.Empty; - if (!isOblivion) + if (define.RecSize >= 16) { this.flags = br.ReadUInt32(); } uint amountRead = 0; - while (amountRead < Size - (isOblivion ? 20 : 24)) + while (amountRead < Size - (define.RecSize+8)) { string s = ReadRecName(br); uint recsize = br.ReadUInt32(); @@ -68,7 +67,7 @@ internal GroupRecord(uint Size, BinaryReader br, float version, Func 0) size += 4; br.BaseStream.Position += size; // just read past the data - amountRead += (uint)(recsize + (isOblivion ? 20 : 24)); + amountRead += (uint)(recsize + (define.RecSize+8)); } else { try { - var r = new Record(s, recsize, br, version); + var r = new Record(s, recsize, br, define); this.AddRecord(r); } catch (Exception e) @@ -107,17 +106,17 @@ internal GroupRecord(uint Size, BinaryReader br, float version, Func includeFilter var fi = new FileInfo(filePath); using (var br = new BinaryReader(fi.OpenRead())) { - define = this.DetectVersion(br); + define = this.DetectVersion(br, filePath); br.BaseStream.Position = 0; this.LoadPluginData(br, headerOnly, includeFilter); } @@ -994,16 +994,28 @@ public void UpdateRecordCount() } } - private DomainDefinition DetectVersion(BinaryReader br) + private DomainDefinition DetectVersion(BinaryReader br, string fileName) { - var s = ReadRecName(br); - if (s == "TES3") + // Quick check for master esm. Skyrim.esm uses same as fallout. so harder to detect + if (!string.IsNullOrEmpty(fileName)) + { + foreach (var domain in DomainDefinition.AllDomains().Where(domain => + string.Compare(domain.Master, Path.GetFileName(fileName),StringComparison.InvariantCultureIgnoreCase) == 0)) + { + if (!domain.Loaded) + DomainDefinition.Load(domain.Name); + return domain; + } + } + + var tes = ReadRecName(br); + if (tes == "TES3") return DomainDefinition.Load("Morrowind"); // hardcoded? - if (s != "TES4") + if (tes != "TES4") throw new Exception("File is not a valid TES4 plugin (Missing TES4 record)"); // Check for file version by checking the position of the HEDR field in the file. (ie. how big are the record header.) br.BaseStream.Position = 20; - s = ReadRecName(br); + var s = ReadRecName(br); if (s == "HEDR") return DomainDefinition.Load("Oblivion"); // hardcoded? s = ReadRecName(br); @@ -1011,7 +1023,7 @@ private DomainDefinition DetectVersion(BinaryReader br) throw new Exception("File is not a valid TES4 plugin (Missing HEDR subrecord in the TES4 record)"); var recsize = br.ReadUInt16(); var version = br.ReadSingle(); - return DomainDefinition.DetectDefinitionFromVersion(s, version); + return DomainDefinition.DetectDefinitionFromVersion(tes, version); } private DomainDefinition DetectVersion() @@ -1033,7 +1045,6 @@ private void LoadPluginData(BinaryReader br, bool headerOnly, Func { string s; uint recsize; - float version = 0.0f; this.Filtered = includeFilter != null; @@ -1041,41 +1052,27 @@ private void LoadPluginData(BinaryReader br, bool headerOnly, Func Decompressor.Init(); s = ReadRecName(br); - if (s != "TES4") + if (s != this.define.HEDRType) { throw new Exception("File is not a valid TES4 plugin (Missing TES4 record)"); } // Check for file version by checking the position of the HEDR field in the file. (ie. how big are the record header.) - br.BaseStream.Position = 20; + br.BaseStream.Position = define.HEDROffset; s = ReadRecName(br); - if (s == "HEDR") - { - // Record Header is 20 bytes - } - else - { - s = ReadRecName(br); - if (s != "HEDR") - { - throw new Exception("File is not a valid TES4 plugin (Missing HEDR subrecord in the TES4 record)"); - } - // Record Header is 24 bytes. Or the file is illegal - } - br.ReadUInt16(); - version = br.ReadSingle(); - + if (s != "HEDR") + throw new Exception(string.Format("File is not a valid {0} plugin (Missing HEDR subrecord in the {1} record)",define.Name,define.HEDRType)); br.BaseStream.Position = 4; recsize = br.ReadUInt32(); try { - this.AddRecord(new Record("TES4", recsize, br, version)); + this.AddRecord(new Record(this.define.HEDRType, recsize, br, this.define)); } catch (Exception e) { Alerts.Show(e.Message); } - bool hasExtraFlags = Math.Abs(version - 1.0f) > float.Epsilon * 10.0f; + //bool hasExtraFlags = Math.Abs(version - 1.0f) > float.Epsilon * 10.0f; if (!headerOnly) { @@ -1087,7 +1084,7 @@ private void LoadPluginData(BinaryReader br, bool headerOnly, Func { try { - this.AddRecord(new GroupRecord(recsize, br, version, includeFilter, false)); + this.AddRecord(new GroupRecord(recsize, br, this.define, includeFilter, false)); } catch (Exception e) { @@ -1099,7 +1096,7 @@ private void LoadPluginData(BinaryReader br, bool headerOnly, Func bool skip = includeFilter != null && !includeFilter(s); if (skip) { - long size = recsize + 8 + (hasExtraFlags ? 4 : 0); + long size = recsize + define.RecSize; if ((br.ReadUInt32() & 0x00040000) > 0) { size += 4; // Add 4 bytes for compressed record since the decompressed size is not included in the record size. @@ -1111,7 +1108,7 @@ private void LoadPluginData(BinaryReader br, bool headerOnly, Func { try { - this.AddRecord(new Record(s, recsize, br, version)); + this.AddRecord(new Record(s, recsize, br, define)); } catch (Exception e) { diff --git a/Domain/Model/Record.cs b/Domain/Model/Record.cs index cc080d7..2f73ec5 100644 --- a/Domain/Model/Record.cs +++ b/Domain/Model/Record.cs @@ -47,9 +47,8 @@ public Record() this.FixSubrecordOwner(); } - internal Record(string name, uint dataSize, BinaryReader recordReader, float version) + internal Record(string name, uint dataSize, BinaryReader recordReader, TESVSnip.Domain.Data.DomainDefinition define) { - bool isOblivion = Math.Abs(version - 1.0f) <= float.Epsilon*10.0f; this.dataSize = dataSize; int estimatedCount = Math.Max( Math.Min(16, (int)dataSize/10), 0 ); @@ -58,11 +57,10 @@ internal Record(string name, uint dataSize, BinaryReader recordReader, float ver Name = name; Flags1 = recordReader.ReadUInt32(); FormID = recordReader.ReadUInt32(); - Flags2 = recordReader.ReadUInt32(); - if (!isOblivion) - { + if (define.RecSize >= 12) + Flags2 = recordReader.ReadUInt32(); + if (define.RecSize >= 16) Flags3 = recordReader.ReadUInt32(); - } bool compressed = (Flags1 & 0x00040000) != 0; uint amountRead = 0; @@ -77,10 +75,9 @@ internal Record(string name, uint dataSize, BinaryReader recordReader, float ver using (var stream = new MemoryStream(recordReader.ReadBytes((int)dataSize),false)) using (var br = new BinaryReader(stream)) { - BinaryReader dataReader = compressed - ? Decompressor.Decompress(br, (int)dataSize, (int)realSize, - out compressLevel) - : br; + var dataReader = compressed + ? Decompressor.Decompress(br, (int)dataSize, (int)realSize, out compressLevel) + : br; { while (true) { @@ -100,7 +97,7 @@ internal Record(string name, uint dataSize, BinaryReader recordReader, float ver } else { - size = dataReader.ReadUInt16(); + size = define.HEDRRecSize == 2 ? dataReader.ReadUInt16() : dataReader.ReadUInt32(); } var record = new SubRecord(this, type, dataReader, size); diff --git a/Domain/Model/SubRecord.cs b/Domain/Model/SubRecord.cs index 09f64d0..5822c18 100644 --- a/Domain/Model/SubRecord.cs +++ b/Domain/Model/SubRecord.cs @@ -327,13 +327,13 @@ public IEnumerable EnumerateElements(bool rawData) { if (this.Structure == null) { - yield return new Element(new ElementStructure(), new ArraySegment(this.GetData())); + yield return new Element(new ElementStructure(), new ArraySegment(this.GetData()), null); } else { byte[] data = this.GetReadonlyData(); int offset = 0; - foreach (var item in EnumerateElements(this.Structure.elementTree, data, offset, rawData)) + foreach (var item in EnumerateElements(this.Structure.elementTree, data, offset, rawData, null)) yield return item.Item2; } } @@ -346,8 +346,7 @@ public IEnumerable EnumerateElements(bool rawData) /// /// /// - private IEnumerable> EnumerateElements(IEnumerable elements, byte[] data, - int offset, bool rawData) + private IEnumerable> EnumerateElements(IEnumerable elements, byte[] data, int offset, bool rawData, Tuple[] indices ) { foreach (var es in elements) { @@ -360,7 +359,11 @@ private IEnumerable> EnumerateElements(IEnumerable[(indices == null) ? 1 : indices.Length + 1]; + if (indices != null) Array.Copy(indices, 0, groupIndices, 0, indices.Length); + groupIndices[groupIndices.Length-1] = new Tuple(es, count); + + foreach (var item in EnumerateElements(((ElementGroup)es).elements, data, offset, rawData, groupIndices)) { offset = item.Item1; yield return item; @@ -368,7 +371,12 @@ private IEnumerable> EnumerateElements(IEnumerable 0 ? 1 : 0; + var groupIndices = new Tuple[(indices == null) ? repeatCount : indices.Length + repeatCount]; + if (indices != null && indices.Length > 0) Array.Copy(indices, 0, groupIndices, 0, indices.Length); + if (repeatCount > 0) + groupIndices[groupIndices.Length - 1] = new Tuple(es, count); + var elem = Element.CreateElement((ElementStructure) es, data, ref offset, rawData, groupIndices); yield return new Tuple(offset, elem); } if (es.repeat == 0) break; diff --git a/conf/Domains.ini b/conf/Domains.ini index 1565c53..59c89db 100644 --- a/conf/Domains.ini +++ b/conf/Domains.ini @@ -3,7 +3,10 @@ Display=Morrowind Master=Morrowind.esm Registry=Bethesda Softworks\Morrowind HEDRType=TES3 -HEDRVersion=1.0 +HEDRVersion=1.3 +HEDROffset=16 +HEDRRecSize=4 +RecSize=8 [Oblivion] Display=Oblivion @@ -13,6 +16,8 @@ FilteredESM=RGDL;CLDC;PWAT;SCOL;SCPT;HAIR;REGN;NAVI;WRLD;DIAL;CELL;IMAD;WTHR AllESMRecords=AACT;ACTI;ADDN;ALCH;AMMO;ANIO;APPA;ARMA;ARMO;ARTO;ASPC;ASTP;AVIF;BOOK;BPTD;CAMS;CELL;CLAS;CLDC;CLFM;CLMT;COBJ;COLL;CONT;CPTH;CSTY;DEBR;DIAL;DLBR;DLVW;DOBJ;DOOR;DUAL;ECZN;EFSH;ENCH;EQUP;EXPL;EYES;FACT;FLOR;FLST;FSTP;FSTS;FURN;GLOB;GMST;GRAS;HAIR;HAZD;HDPT;IDLE;IDLM;IMAD;IMGS;INGR;IPCT;IPDS;KEYM;KYWD;LCRT;LCTN;LGTM;LIGH;LSCR;LTEX;LVLI;LVLN;LVSP;MATO;MATT;MESG;MGEF;MISC;MOVT;MSTT;MUSC;MUST;NAVI;NPC_;OTFT;PACK;PERK;PROJ;PWAT;QUST;RACE;REGN;RELA;REVB;RFCT;RGDL;SCEN;SCOL;SCPT;SCTX;SCRL;SHOU;SLGM;SMBN;SMEN;SMQN;SNCT;SNDR;SOPM;SOUN;SPEL;SPGD;STAT;TACT;TREE;TXST;VTYP;WATR;WEAP;WOOP;WRLD;WTHR HEDRType=TES4 HEDRVersion=1.0 +HEDROffset=20 +RecSize=12 [Fallout3] Display=Fallout3 @@ -22,6 +27,7 @@ FilteredESM=RGDL;CLDC;PWAT;SCOL;SCPT;HAIR;REGN;NAVI;WRLD;DIAL;CELL;IMAD;WTHR AllESMRecords=AACT;ACTI;ADDN;ALCH;AMMO;ANIO;APPA;ARMA;ARMO;ARTO;ASPC;ASTP;AVIF;BOOK;BPTD;CAMS;CELL;CLAS;CLDC;CLFM;CLMT;COBJ;COLL;CONT;CPTH;CSTY;DEBR;DIAL;DLBR;DLVW;DOBJ;DOOR;DUAL;ECZN;EFSH;ENCH;EQUP;EXPL;EYES;FACT;FLOR;FLST;FSTP;FSTS;FURN;GLOB;GMST;GRAS;HAIR;HAZD;HDPT;IDLE;IDLM;IMAD;IMGS;INGR;IPCT;IPDS;KEYM;KYWD;LCRT;LCTN;LGTM;LIGH;LSCR;LTEX;LVLI;LVLN;LVSP;MATO;MATT;MESG;MGEF;MISC;MOVT;MSTT;MUSC;MUST;NAVI;NPC_;OTFT;PACK;PERK;PROJ;PWAT;QUST;RACE;REGN;RELA;REVB;RFCT;RGDL;SCEN;SCOL;SCPT;SCTX;SCRL;SHOU;SLGM;SMBN;SMEN;SMQN;SNCT;SNDR;SOPM;SOUN;SPEL;SPGD;STAT;TACT;TREE;TXST;VTYP;WATR;WEAP;WOOP;WRLD;WTHR HEDRType=TES4 HEDRVersion=0.94 +RecSize=16 [FalloutNV] Display=Fallout New Vegas.esm @@ -31,6 +37,7 @@ FilteredESM=RGDL;CLDC;PWAT;SCOL;SCPT;HAIR;REGN;NAVI;WRLD;DIAL;CELL;IMAD;WTHR AllESMRecords=AACT;ACTI;ADDN;ALCH;AMMO;ANIO;APPA;ARMA;ARMO;ARTO;ASPC;ASTP;AVIF;BOOK;BPTD;CAMS;CELL;CLAS;CLDC;CLFM;CLMT;COBJ;COLL;CONT;CPTH;CSTY;DEBR;DIAL;DLBR;DLVW;DOBJ;DOOR;DUAL;ECZN;EFSH;ENCH;EQUP;EXPL;EYES;FACT;FLOR;FLST;FSTP;FSTS;FURN;GLOB;GMST;GRAS;HAIR;HAZD;HDPT;IDLE;IDLM;IMAD;IMGS;INGR;IPCT;IPDS;KEYM;KYWD;LCRT;LCTN;LGTM;LIGH;LSCR;LTEX;LVLI;LVLN;LVSP;MATO;MATT;MESG;MGEF;MISC;MOVT;MSTT;MUSC;MUST;NAVI;NPC_;OTFT;PACK;PERK;PROJ;PWAT;QUST;RACE;REGN;RELA;REVB;RFCT;RGDL;SCEN;SCOL;SCPT;SCTX;SCRL;SHOU;SLGM;SMBN;SMEN;SMQN;SNCT;SNDR;SOPM;SOUN;SPEL;SPGD;STAT;TACT;TREE;TXST;VTYP;WATR;WEAP;WOOP;WRLD;WTHR HEDRType=TES4 HEDRVersion=1.32 +RecSize=16 [Skyrim] Display=Skyrim @@ -40,3 +47,5 @@ FilteredESM=RGDL;CLDC;PWAT;SCOL;SCPT;HAIR;REGN;NAVI;WRLD;DIAL;CELL;IMAD;WTHR AllESMRecords=AACT;ACTI;ADDN;ALCH;AMMO;ANIO;APPA;ARMA;ARMO;ARTO;ASPC;ASTP;AVIF;BOOK;BPTD;CAMS;CELL;CLAS;CLDC;CLFM;CLMT;COBJ;COLL;CONT;CPTH;CSTY;DEBR;DIAL;DLBR;DLVW;DOBJ;DOOR;DUAL;ECZN;EFSH;ENCH;EQUP;EXPL;EYES;FACT;FLOR;FLST;FSTP;FSTS;FURN;GLOB;GMST;GRAS;HAIR;HAZD;HDPT;IDLE;IDLM;IMAD;IMGS;INGR;IPCT;IPDS;KEYM;KYWD;LCRT;LCTN;LGTM;LIGH;LSCR;LTEX;LVLI;LVLN;LVSP;MATO;MATT;MESG;MGEF;MISC;MOVT;MSTT;MUSC;MUST;NAVI;NPC_;OTFT;PACK;PERK;PROJ;PWAT;QUST;RACE;REGN;RELA;REVB;RFCT;RGDL;SCEN;SCOL;SCPT;SCTX;SCRL;SHOU;SLGM;SMBN;SMEN;SMQN;SNCT;SNDR;SOPM;SOUN;SPEL;SPGD;STAT;TACT;TREE;TXST;VTYP;WATR;WEAP;WOOP;WRLD;WTHR HEDRType=TES4 HEDRVersion=1.7 +HEDROffset=24 +RecSize=16 \ No newline at end of file diff --git a/conf/Morrowind/RecordStructure.xml b/conf/Morrowind/RecordStructure.xml new file mode 100644 index 0000000..6c6371a --- /dev/null +++ b/conf/Morrowind/RecordStructure.xml @@ -0,0 +1,1280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/conf/Oblivion/RecordStructure.xml b/conf/Oblivion/RecordStructure.xml index a629b02..922a325 100644 --- a/conf/Oblivion/RecordStructure.xml +++ b/conf/Oblivion/RecordStructure.xml @@ -191,110 +191,91 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + - - - - - + + + - - - - - - - - - + + + - + + - - - - - - - - - + + + - + - - - - - - - - + + + + + + + + @@ -305,20 +286,36 @@ - - + + - - + + - - + + - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/plugins/shared/util.py b/scripts/plugins/shared/util.py index 6ee441e..6853c1c 100644 --- a/scripts/plugins/shared/util.py +++ b/scripts/plugins/shared/util.py @@ -82,11 +82,11 @@ def alternateLoadMasterPluginIndex(): r.Close() -def getGameDirectory(): +def getGameDirectory(game='Skyrim'): from System.IO import Path from Microsoft.Win32 import Registry, RegistryValueOptions - key = Registry.LocalMachine.OpenSubKey(r"SOFTWARE\Wow6432Node\Bethesda Softworks\Skyrim") - if not key: key = Registry.LocalMachine.OpenSubKey(r"SOFTWARE\Bethesda Softworks\Skyrim") + key = Registry.LocalMachine.OpenSubKey(r"SOFTWARE\Wow6432Node\Bethesda Softworks\%s"%game) + if not key: key = Registry.LocalMachine.OpenSubKey(r"SOFTWARE\Bethesda Softworks\%s"%game) result = key.GetValue("Installed Path", '', RegistryValueOptions.None) if key: key.Dispose() if not result: return None diff --git a/scripts/renderer.py b/scripts/renderer.py index 17c6215..e0b48a7 100644 --- a/scripts/renderer.py +++ b/scripts/renderer.py @@ -194,6 +194,23 @@ def GetDescription(self, rec): except Exception, e: pass + def GetElementName(self, elem): + sselem = elem.Structure + indices = elem.Indices + if indices: + ssname = '' + for index in indices: + if ssname: ssname += '.' + ssname += index.Item1.name + if index.Item1.repeat > 0: + ssname += '[%s]'%(str(index.Item2)) + if not sselem.repeat: + if ssname: ssname += '.' + ssname += sselem.name + return ssname + else: + return sselem.name + def GetElementValue(self, elem): # presumably we can just straight up execute this but for now this is compatible if elem.Structure.funcr: @@ -274,11 +291,12 @@ def GetDescriptionSubRecord(self, rec): with p.tbody(): for elem in elems: sselem = elem.Structure + ssname = self.GetElementName(elem) value = self.GetElementValue(elem) strValue = str(value) with p.tr(): - p.td(sselem.name, width="33%", class_='label') + p.td(ssname, width="33%", class_='label') if sselem.type == ElementValueType.Blob: p.td(TypeConverter.GetHexData(elem.Data), class_='value', colspan='4') diff --git a/test/test4.py b/test/test4.py index 9a72292..f4ccc4b 100644 --- a/test/test4.py +++ b/test/test4.py @@ -1,7 +1,7 @@ import sys import startup import shared.util as util -gameDir = util.getGameDirectory() +gameDir = util.getGameDirectory('Oblivion') import System from System.Diagnostics import Stopwatch @@ -32,14 +32,14 @@ def do_GET(self): server.handle_request() sw = Stopwatch.StartNew() -plugin = TESVSnip.Domain.Model.Plugin(gameDir + 'imoen.esp') +plugin = TESVSnip.Domain.Model.Plugin(gameDir + 'asdf.esp') rec = None -for rec in plugin.Records: - if isinstance(rec, GroupRecord): - break -#for kvp in plugin.EnumerateRecords('NPC_'): -# rec = kvp.Value -# #break +# for rec in plugin.Records: + # if isinstance(rec, GroupRecord): + # break +for kvp in plugin.EnumerateRecords('RACE'): + rec = kvp.Value + #break cssfile = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(sys.argv[0]),'../scripts/renderer.css')) html = renderer.HTMLRenderer(title="Record",css=( cssfile, ) ) html.GetHeader(rec)