00001 using System;
00002 using System.Collections.Generic;
00003 using System.IO;
00004
00005 namespace TESPluginParser {
00006
00007
00008
00009
00010
00011
00012
00013 public delegate void dValueChange(BaseRecord br, long ChangedBy);
00014 public class TESParserException : Exception { public TESParserException(string msg) : base(msg) { } }
00015
00016 public abstract class BaseRecord {
00017 public string Name;
00018 private long _Size;
00019
00020 public long Size {
00021 get { return _Size; }
00022 protected set {
00023 if(value==_Size) return;
00024 long temp=_Size;
00025 _Size=value;
00026 if(OnSizeChange!=null) OnSizeChange(this, value-temp);
00027 }
00028 }
00029
00030 public event dValueChange OnSizeChange;
00031 public abstract string GetDesc();
00032 public abstract void DeleteRecord(BaseRecord br);
00033 public abstract void AddRecord(BaseRecord br);
00034 public abstract List<string> GetIDs(bool lower);
00035 internal abstract void SaveData(BinaryWriter bw);
00036
00037 protected static string ReadRecName(BinaryReader br) {
00038 byte[] b = new byte[4];
00039 br.Read(b, 0, 4);
00040 return ""+((char)b[0])+((char)b[1])+((char)b[2])+((char)b[3]);
00041 }
00042 protected static void WriteString(BinaryWriter bw,string s) {
00043 byte[] b = new byte[s.Length];
00044 for(int i=0;i<s.Length;i++) b[i]=(byte)s[i];
00045 bw.Write(b, 0, s.Length);
00046 }
00047 }
00048
00049 public sealed class Plugin : BaseRecord {
00050 public readonly List<Rec> Records = new List<Rec>();
00051
00052 internal void RecordSizeChanged(BaseRecord br, long change) {
00053 if((br is Rec)&&Records.Contains((Rec)br)) Size+=change;
00054 }
00055
00056 public override void DeleteRecord(BaseRecord br) {
00057 Rec r = br as Rec;
00058 if(r==null) return;
00059 for(int i=0;i<Records.Count;i++) {
00060 if(Records[i]==r) {
00061 if(r is GroupRecord) {
00062 Size-=r.Size;
00063 } else {
00064 Size-=r.Size+20;
00065 }
00066 Records.RemoveAt(i);
00067 return;
00068 }
00069 }
00070 }
00071 public override void AddRecord(BaseRecord br) {
00072 Rec r=br as Rec;
00073 if(r==null) throw new TESParserException("Record to add was not of the correct type."+
00074 Environment.NewLine+"Plugins can only hold Groups or Records.");
00075 Records.Add(r);
00076 if(r is GroupRecord) {
00077 Size+=r.Size;
00078 } else {
00079 Size+=r.Size+20;
00080 }
00081 }
00082
00083 private void LoadPlugin(BinaryReader br) {
00084 string s;
00085
00086 s=ReadRecName(br);
00087 if(s!="TES4") throw new Exception("File is not a valid TES4 plugin");
00088 Records.Add(new Tes4Record(br));
00089 while(br.PeekChar()!=-1) {
00090 Records.Add(Rec.New(br));
00091 }
00092 foreach(Rec r in Records) {
00093 r.OnSizeChange+=RecordSizeChanged;
00094 }
00095 }
00096
00097 public Plugin(string FilePath) {
00098 Name=Path.GetFileName(FilePath);
00099 FileInfo fi=new FileInfo(FilePath);
00100 Size=fi.Length;
00101 BinaryReader br=new BinaryReader(fi.OpenRead());
00102 try {
00103 LoadPlugin(br);
00104 } finally {
00105 br.Close();
00106 }
00107 }
00108 public Plugin() {
00109 Name="New plugin";
00110 Size=0;
00111 }
00112
00113 public override string GetDesc() {
00114 return "[Oblivion plugin]"+Environment.NewLine+
00115 "Filename: "+Name+Environment.NewLine+
00116 "File size: "+Size+Environment.NewLine+
00117 "Records: "+Records.Count;
00118 }
00119
00120 public void Save(string FilePath) {
00121 if(File.Exists(FilePath)) File.Delete(FilePath);
00122 BinaryWriter bw=new BinaryWriter(File.OpenWrite(FilePath));
00123 try {
00124 SaveData(bw);
00125 Name=Path.GetFileName(FilePath);
00126 } finally {
00127 bw.Close();
00128 }
00129 }
00130
00131 internal override void SaveData(BinaryWriter bw) {
00132 foreach(Rec r in Records) r.SaveData(bw);
00133 }
00134
00135 public override List<string> GetIDs(bool lower) {
00136 List<string> list= new List<string>();
00137 foreach(Rec r in Records) list.AddRange(r.GetIDs(lower));
00138 return list;
00139 }
00140
00141 public string Merge(Plugin p, bool MergeLists) {
00142 string result="";
00143
00144 for(int i=0;i<Records.Count;i++) {
00145 if(Records[i] is GroupRecord) {
00146 GroupRecord gr=(GroupRecord)Records[i--];
00147 foreach(Record r in gr.Records) {
00148 AddRecord(r);
00149 }
00150 DeleteRecord(gr);
00151 }
00152 }
00153
00154 List<string> ids=GetIDs(true);
00155
00156 List<Record> toadd=new List<Record>();
00157 foreach(Rec r in p.Records) {
00158 if(r is Record) toadd.Add((Record)r);
00159 else toadd.AddRange(((GroupRecord)r).Records);
00160 }
00161
00162 foreach(Record r in toadd) {
00163 if(r.Name=="TES4") continue;
00164 List<string> id=r.GetIDs(true);
00165 if(id.Count==0||!ids.Contains(id[0])) {
00166 AddRecord(r);
00167 continue;
00168 }
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210 result+="Record "+id[0]+" of type "+r.Name+" could not be merged because it conflicted with an existing record."+Environment.NewLine;
00211 }
00212 return result;
00213 }
00214 }
00215
00216 public abstract class Rec : BaseRecord {
00217 internal static Rec New(BinaryReader br) {
00218 string s=Plugin.ReadRecName(br);
00219 br.ReadUInt32();
00220 bool b=((br.ReadUInt32()&0x00040000)>0);
00221 br.BaseStream.Position-=8;
00222 return New(s, br, b);
00223 }
00224 internal static Rec New(string s, BinaryReader br) {
00225 br.ReadUInt32();
00226 bool b=((br.ReadUInt32()&0x00040000)>0);
00227 br.BaseStream.Position-=8;
00228 return New(s, br, b);
00229 }
00230 internal static Rec New(string s, BinaryReader br,bool compressed) {
00231 if(s=="GRUP") return new GroupRecord(br);
00232 else {
00233 switch(s) {
00234 case "TES4":
00235 return new Tes4Record(br);
00236 default:
00237 if(compressed) return new CompressedRecord(s, br);
00238 else return new Record(s, br);
00239 }
00240 }
00241 }
00242 }
00243
00244 public sealed class GroupRecord : Rec {
00245 public readonly List<Record> Records=new List<Record>();
00246 public readonly uint[] HeaderInfo=new uint[2];
00247
00248 internal void RecordSizeChanged(BaseRecord br, long change) {
00249 if((br is Record)&&Records.Contains((Record)br)) Size+=change;
00250 }
00251
00252 public override void DeleteRecord(BaseRecord br) {
00253 Record r = br as Record;
00254 if(r==null) return;
00255 for(int i=0;i<Records.Count;i++) {
00256 if(Records[i]==r) {
00257 Size-=r.Size+20;
00258 Records.RemoveAt(i);
00259 return;
00260 }
00261 }
00262 }
00263 public override void AddRecord(BaseRecord br) {
00264 Record r=br as Record;
00265 if(r==null) throw new TESParserException("Record to add was not of the correct type."+
00266 Environment.NewLine+"Groups can only hold Records.");
00267 Records.Add(r);
00268 Size+=r.Size+20;
00269 }
00270
00271 internal GroupRecord(BinaryReader br) {
00272 Size=br.ReadUInt32();
00273 Name=Plugin.ReadRecName(br);
00274 for(int i=0;i<2;i++) HeaderInfo[i]=br.ReadUInt32();
00275 uint AmountRead=0;
00276 while(AmountRead<Size-20) {
00277 string s=Plugin.ReadRecName(br);
00278 if(s=="GRUP") {
00279 for(int i=0;i<4;i++) br.ReadInt32();
00280 Size-=20;
00281 continue;
00282 }
00283 Record r=(Record)Rec.New(s, br);
00284 AmountRead+=(uint)(r.Size+20);
00285 Records.Add(r);
00286 r.OnSizeChange+=RecordSizeChanged;
00287 }
00288 }
00289
00290 public override string GetDesc() {
00291 return "[Record group]"+Environment.NewLine+
00292 "Type: "+Name+Environment.NewLine+
00293 "Records: "+Records.Count.ToString()+Environment.NewLine+
00294 "Size: "+Size.ToString()+" bytes (including header)";
00295 }
00296
00297 internal override void SaveData(BinaryWriter bw) {
00298 WriteString(bw, "GRUP");
00299 bw.Write((uint)Size);
00300 WriteString(bw,Name);
00301 for(int i=0;i<2;i++) bw.Write((uint)HeaderInfo[i]);
00302 foreach(Record r in Records) r.SaveData(bw);
00303 }
00304
00305 public override List<string> GetIDs(bool lower) {
00306 List<string> list= new List<string>();
00307 foreach(Record r in Records) list.AddRange(r.GetIDs(lower));
00308 return list;
00309 }
00310 }
00311
00312 public sealed class Tes4Record : Record {
00313 public readonly string Author;
00314 public readonly string Description;
00315
00316 internal Tes4Record(BinaryReader br) : base("TES4",br) {
00317 foreach(SubRecord sr in SubRecords) {
00318 switch(sr.Name) {
00319 case "CNAM":
00320 Author=sr.GetStrData();
00321 break;
00322 case "SNAM":
00323 Description=sr.GetStrData();
00324 break;
00325 }
00326 }
00327 }
00328
00329 public override string GetDesc() {
00330 return "[TES4 main header]"+Environment.NewLine+GetBaseDesc()+Environment.NewLine+Environment.NewLine+
00331 "[Record specific data]"+Environment.NewLine+
00332 "Mod author: "+Author+Environment.NewLine+
00333 "Description: "+Description;
00334 }
00335 }
00336
00337 public sealed class CompressedRecord : Record {
00338 byte[] CompressedData;
00339 uint UncompressedSize;
00340
00341 internal CompressedRecord(string name,BinaryReader br) {
00342 Name=name;
00343 Size=br.ReadUInt32();
00344 for(int i=0;i<3;i++) HeaderInfo[i]=br.ReadUInt32();
00345 byte[] b=new byte[Size-4];
00346 byte[] output=new byte[br.ReadUInt32()];
00347 br.Read(b, 0, (int)(Size-4));
00348
00349 ICSharpCode.SharpZipLib.Zip.Compression.Inflater inf;
00350 inf=new ICSharpCode.SharpZipLib.Zip.Compression.Inflater(false);
00351
00352 inf.SetInput(b, 0, b.Length);
00353 inf.Inflate(output);
00354
00355 CompressedData=b;
00356 UncompressedSize=(uint)output.Length;
00357
00358 BinaryReader ubr=new BinaryReader(new MemoryStream(output));
00359 while(ubr.PeekChar()!=-1) {
00360 string s=Plugin.ReadRecName(ubr);
00361 if(s=="GRUP") {
00362 for(int i=0;i<16;i++) ubr.ReadUInt32();
00363 Size-=20;
00364 continue;
00365 }
00366 SubRecord r=new SubRecord(s, ubr);
00367 SubRecords.Add(r);
00368 r.OnSizeChange+=CompressedSizeChanged;
00369 }
00370 ubr.Close();
00371 }
00372
00373 public override void AddRecord(BaseRecord br) {
00374 SubRecord sr=br as SubRecord;
00375 if(sr==null) throw new TESParserException("Record to add was not of the correct type."+
00376 Environment.NewLine+"Records can only hold Subrecords.");
00377 SubRecords.Add(sr);
00378 CompressedSizeChanged(null, 0);
00379 }
00380
00381 public override void DeleteRecord(BaseRecord br) {
00382 SubRecord sr = br as SubRecord;
00383 if(sr==null) return;
00384 for(int i=0;i<SubRecords.Count;i++) {
00385 if(SubRecords[i]==sr) {
00386 SubRecords.RemoveAt(i);
00387 break;
00388 }
00389 }
00390 CompressedSizeChanged(null, 0);
00391 }
00392
00393 public void CompressedSizeChanged(BaseRecord br, long ChangedBy) {
00394
00395 ICSharpCode.SharpZipLib.Zip.Compression.Deflater def;
00396 def=new ICSharpCode.SharpZipLib.Zip.Compression.Deflater(9);
00397
00398 MemoryStream ms=new MemoryStream();
00399 BinaryWriter bw=new BinaryWriter(ms);
00400 foreach(SubRecord sr in SubRecords) sr.SaveData(bw);
00401 bw.Flush();
00402
00403 byte[] input=new byte[ms.Length];
00404 ms.Seek(0, SeekOrigin.Begin);
00405 ms.Read(input, 0, input.Length);
00406 def.SetInput(input);
00407 UncompressedSize=(uint)input.Length;
00408
00409 def.Finish();
00410 byte[] compressed=new byte[Size*2];
00411 int compressedsize=0;
00412 while(true) {
00413 int i=def.Deflate(compressed);
00414 if(i==0) break;
00415 compressedsize+=i;
00416 }
00417 Size=compressedsize+4;
00418 Array.Resize<byte>(ref compressed, compressedsize);
00419 CompressedData=compressed;
00420 }
00421
00422
00423
00424 internal override void SaveData(BinaryWriter bw) {
00425 WriteString(bw, Name);
00426 bw.Write((uint)Size);
00427 for(int i=0;i<3;i++) bw.Write((uint)HeaderInfo[i]);
00428 bw.Write((uint)UncompressedSize);
00429 bw.Write(CompressedData);
00430 }
00431 }
00432
00433 public class Record : Rec {
00434 public readonly List<SubRecord> SubRecords=new List<SubRecord>();
00435 public readonly uint[] HeaderInfo=new uint[3];
00436
00437 internal void RecordSizeChanged(BaseRecord br, long change) {
00438 if((br is SubRecord)&&SubRecords.Contains((SubRecord)br)) Size+=change;
00439 }
00440
00441 public override void DeleteRecord(BaseRecord br) {
00442 SubRecord sr = br as SubRecord;
00443 if(sr==null) return;
00444 for(int i=0;i<SubRecords.Count;i++) {
00445 if(SubRecords[i]==sr) {
00446 Size-=sr.Size+6;
00447 SubRecords.RemoveAt(i);
00448 return;
00449 }
00450 }
00451 }
00452 public override void AddRecord(BaseRecord br) {
00453 SubRecord sr=br as SubRecord;
00454 if(sr==null) throw new TESParserException("Record to add was not of the correct type."+
00455 Environment.NewLine+"Records can only hold Subrecords.");
00456 SubRecords.Add(sr);
00457 Size+=sr.Size+6;
00458 }
00459
00460 internal Record(string name, BinaryReader br) {
00461 Name=name;
00462 Size=br.ReadUInt32();
00463 for(int i=0;i<3;i++) HeaderInfo[i]=br.ReadUInt32();
00464 uint AmountRead=0;
00465 while(AmountRead<Size) {
00466 string s=Plugin.ReadRecName(br);
00467 if(s=="XXXX") {
00468 br.ReadUInt16();
00469 uint i=br.ReadUInt32();
00470 br.BaseStream.Position+=i+6;
00471 Size-=i+16;
00472 continue;
00473 }
00474 SubRecord r=new SubRecord(s, br);
00475 AmountRead+=(uint)(r.Size+6);
00476 SubRecords.Add(r);
00477 r.OnSizeChange+=RecordSizeChanged;
00478 }
00479 }
00480 protected Record() { }
00481
00482 protected string GetBaseDesc() {
00483 return "Type: "+Name+Environment.NewLine+
00484 "Subrecords: "+SubRecords.Count.ToString()+Environment.NewLine+
00485 "Size: "+Size.ToString()+" bytes (excluding header)";
00486 }
00487
00488 public override string GetDesc() {
00489 return "[Unknown record]"+Environment.NewLine+GetBaseDesc();
00490 }
00491
00492 internal override void SaveData(BinaryWriter bw) {
00493 WriteString(bw, Name);
00494 bw.Write((uint)Size);
00495 for(int i=0;i<3;i++) bw.Write((uint)HeaderInfo[i]);
00496 foreach(SubRecord sr in SubRecords) sr.SaveData(bw);
00497 }
00498
00499 public override List<string> GetIDs(bool lower) {
00500 List<string> list= new List<string>();
00501 foreach(SubRecord sr in SubRecords) list.AddRange(sr.GetIDs(lower));
00502 return list;
00503 }
00504 }
00505
00506 public class SubRecord : BaseRecord {
00507 private byte[] Data;
00508
00509 public byte[] GetData() {
00510 return (byte[])Data.Clone();
00511 }
00512 public void SetData(byte[] data) {
00513 Data=(byte[])data.Clone();
00514 Size=data.Length;
00515 }
00516
00517 internal SubRecord(string name, BinaryReader br) {
00518 Name=name;
00519 Size=br.ReadUInt16();
00520 Data=new byte[Size];
00521 br.Read(Data, 0, Data.Length);
00522 }
00523
00524 internal override void SaveData(BinaryWriter bw) {
00525 WriteString(bw, Name);
00526 bw.Write((ushort)Size);
00527 bw.Write(Data, 0, Data.Length);
00528 }
00529
00530 public override string GetDesc() {
00531 return "[Unknown subrecord]"+Environment.NewLine+
00532 "Name: "+Name+Environment.NewLine+
00533 "Size: "+Size.ToString()+" bytes (Excluding header)";
00534 }
00535 public override void DeleteRecord(BaseRecord br) { }
00536 public override void AddRecord(BaseRecord br) {
00537 throw new TESParserException("Subrecords cannot contain additional data.");
00538 }
00539 public string GetStrData() {
00540 string s="";
00541 foreach(byte b in Data) {
00542 if(b==0) break;
00543 s+=(char)b;
00544 }
00545 return s;
00546 }
00547 public string GetHexData() {
00548 string s="";
00549 foreach(byte b in Data) s+=b.ToString("X").PadLeft(2, '0')+" ";
00550 return s;
00551 }
00552 public override List<string> GetIDs(bool lower) {
00553 List<string> list= new List<string>();
00554 if(Name=="EDID") {
00555 if(lower) {
00556 list.Add(this.GetStrData().ToLower());
00557 } else {
00558 list.Add(this.GetStrData());
00559 }
00560 }
00561 return list;
00562 }
00563 }
00564 }