From f9bda5835a3bb98d8c0c2a4fc59c1bf2c7f27ace Mon Sep 17 00:00:00 2001 From: dexyfex Date: Thu, 11 Jan 2018 12:10:03 +1100 Subject: [PATCH] RPF Explorer Edit mode - auto change archive encryption type when editing, import/export raw child RPFs --- ExploreForm.cs | 128 +++++++++++++++++++++++++++++---- GameFiles/Resources/RpfFile.cs | 74 ++++++++++++++----- Todo.txt | 1 + 3 files changed, 174 insertions(+), 29 deletions(-) diff --git a/ExploreForm.cs b/ExploreForm.cs index 9d6217c..cd2aec6 100644 --- a/ExploreForm.cs +++ b/ExploreForm.cs @@ -1132,11 +1132,21 @@ namespace CodeWalker private byte[] GetFileData(MainListItem file) { byte[] data = null; - if (file.File != null) + if (file.Folder != null) + { + var entry = file.Folder.RpfFile?.ParentFileEntry; + if (entry != null) + { + data = entry.File.ExtractFile(entry);//extract an RPF from another. + } + } + else if (file.File != null) { //load file from RPF - if (file.File.File == null) return null; //no RPF file? go no further - data = file.File.File.ExtractFile(file.File); + if (file.File.File != null) //need the reference to the RPF archive + { + data = file.File.File.ExtractFile(file.File); + } } else if (!string.IsNullOrEmpty(file.FullPath)) { @@ -1515,22 +1525,27 @@ namespace CodeWalker bool isitem = false; bool isfile = false; bool isfolder = false; + bool isarchive = false; bool isfilesys = false; bool issearch = CurrentFolder?.IsSearchResults ?? false; bool canview = false; bool canexportxml = false; + bool canextract = false; bool canimport = EditMode && (CurrentFolder?.RpfFolder != null) && !issearch; bool canedit = false; if (item != null) { + var entry = item.GetRpfEntry(); + isitem = true; + isfilesys = (entry == null); + isarchive = (item.Folder?.RpfFile != null); isfolder = (item.Folder != null); - isfilesys = (item.File == null) && (item.Folder == null); + isfile = !isfolder; canview = CanViewFile(item); canexportxml = CanExportXml(item); - isitem = true; - isfile = !isfolder; canedit = EditMode && !issearch; + canextract = isfile || (isarchive && !isfilesys); } @@ -1538,7 +1553,7 @@ namespace CodeWalker ListContextViewHexMenu.Enabled = isfile; ListContextExportXmlMenu.Enabled = canexportxml; - ListContextExtractRawMenu.Enabled = isfile; + ListContextExtractRawMenu.Enabled = canextract; ListContextExtractUncompressedMenu.Enabled = isfile; ListContextNewMenu.Visible = EditMode; @@ -1605,6 +1620,50 @@ namespace CodeWalker + + private bool EnsureRpfValidEncryption() + { + if (CurrentFolder.RpfFolder == null) return false; + + var rpf = CurrentFolder.RpfFolder.File; + + if (rpf == null) return false; + + bool needsupd = false; + var f = rpf; + List files = new List(); + while (f != null) + { + if (f.Encryption != RpfEncryption.OPEN) + { + var msg = "Archive " + f.Name + " is currently set to " + f.Encryption.ToString() + " encryption.\nAre you sure you want to change this archive to OPEN encryption?\nLoading by the game will require OpenIV.asi."; + if (MessageBox.Show(msg, "Change RPF encryption type", MessageBoxButtons.YesNo) != DialogResult.Yes) + { + return false; + } + needsupd = true; + } + if (needsupd) + { + files.Add(f); + } + f = f.Parent; + } + + //change encryption types, starting from the root rpf. + files.Reverse(); + foreach (var file in files) + { + RpfFile.SetEncryptionType(file, RpfEncryption.OPEN); + } + + return true; + } + + + + + private void ViewSelected() { for (int i = 0; i < MainListView.SelectedIndices.Count; i++) @@ -1731,7 +1790,8 @@ namespace CodeWalker var idx = MainListView.SelectedIndices[0]; if ((idx < 0) || (idx >= CurrentFiles.Count)) return; var file = CurrentFiles[idx]; - if (file.Folder == null) + + if ((file.Folder == null) || (file.Folder.RpfFile != null)) { byte[] data = GetFileData(file); if (data == null) @@ -1744,7 +1804,7 @@ namespace CodeWalker RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry; if (rrfe != null) //add resource header if this is a resource file. { - data = ResourceBuilder.Compress(data); + data = ResourceBuilder.Compress(data); //not completely ideal to recompress it... data = ResourceBuilder.AddResourceHeader(rrfe, data); } @@ -1777,7 +1837,7 @@ namespace CodeWalker var idx = MainListView.SelectedIndices[i]; if ((idx < 0) || (idx >= CurrentFiles.Count)) continue; var file = CurrentFiles[idx]; - if (file.Folder == null) + if ((file.Folder == null) || (file.Folder.RpfFile != null)) { var path = folderpath + file.Name; var data = GetFileData(file); @@ -1791,7 +1851,7 @@ namespace CodeWalker RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry; if (rrfe != null) //add resource header if this is a resource file. { - data = ResourceBuilder.Compress(data); + data = ResourceBuilder.Compress(data); //not completely ideal to recompress it... data = ResourceBuilder.AddResourceHeader(rrfe, data); } @@ -1893,7 +1953,7 @@ namespace CodeWalker foreach (var file in CurrentFiles) { - if (file.Folder == null) + if ((file.Folder == null) || (file.Folder.RpfFile != null)) { var path = folderpath + file.Name; var data = GetFileData(file); @@ -1950,6 +2010,8 @@ namespace CodeWalker { if (CurrentFolder.RpfFolder != null) { + if (!EnsureRpfValidEncryption()) return; + //create new directory entry in the RPF. newdir = RpfFile.CreateDirectory(CurrentFolder.RpfFolder, fname); @@ -2007,6 +2069,8 @@ namespace CodeWalker { if (CurrentFolder.RpfFolder != null) { + if (!EnsureRpfValidEncryption()) return; + //adding a new RPF as a child of another newrpf = RpfFile.CreateNew(CurrentFolder.RpfFolder, fname, encryption); } @@ -2043,6 +2107,9 @@ namespace CodeWalker return; } + if (!EnsureRpfValidEncryption()) return; + + OpenFileDialog.Filter = "XML Files|*.xml"; if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) { @@ -2140,6 +2207,9 @@ namespace CodeWalker return; } + if (!EnsureRpfValidEncryption()) return; + + OpenFileDialog.Filter = string.Empty; if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) { @@ -2181,7 +2251,31 @@ namespace CodeWalker } } - RpfFile.CreateFile(parentrpffldr, fname, data); + var entry = RpfFile.CreateFile(parentrpffldr, fname, data); + + + var newrpf = parentrpffldr.File?.FindChildArchive(entry); + if (newrpf != null) + { + //an RPF file was imported. add its structure to the UI! + var rootpath = GetRootPath(); + var tnf = CreateRpfTreeFolder(newrpf, newrpf.Path, rootpath + newrpf.Path); + if (CurrentFolder.Children != null) //make sure any existing (replaced!) one is removed first! + { + foreach (var child in CurrentFolder.Children) + { + if (child.Path == tnf.Path) + { + CurrentFolder.Children.Remove(child); + child.TreeNode.Remove(); + break; + } + } + } + CurrentFolder.AddChildToHierarchy(tnf); + RecurseMainTreeViewRPF(tnf, AllRpfs); + RecurseAddMainTreeViewNodes(tnf, CurrentFolder.TreeNode); + } } } @@ -2281,6 +2375,8 @@ namespace CodeWalker } if (entry != null) { + if (!EnsureRpfValidEncryption()) return; + //renaming an entry in an RPF RpfFile.RenameEntry(entry, newname); } @@ -2327,6 +2423,9 @@ namespace CodeWalker if (MainListView.SelectedIndices.Count != 1) return; MessageBox.Show("ReplaceSelected TODO..."); //delete the selected items, and replace with... choose + + //if (!EnsureRpfEncryptionType()) return; + } private void DeleteSelected() { @@ -2368,6 +2467,8 @@ namespace CodeWalker if (parent.RpfFolder != null) { //delete an item in an RPF. + if (!EnsureRpfValidEncryption()) return; + RpfEntry entry = item.GetRpfEntry(); RpfFile.DeleteEntry(entry); @@ -3251,6 +3352,7 @@ namespace CodeWalker { FileSize = fld.RpfFile.FileSize; FileSizeText = TextUtil.GetBytesReadable(FileSize); + Attributes += fld.RpfFile.Encryption.ToString() + " encryption"; } else { diff --git a/GameFiles/Resources/RpfFile.cs b/GameFiles/Resources/RpfFile.cs index 287a4d0..fc2138a 100644 --- a/GameFiles/Resources/RpfFile.cs +++ b/GameFiles/Resources/RpfFile.cs @@ -885,15 +885,6 @@ namespace CodeWalker.GameFiles //entries may have been updated, so need to do this after ensuring header space var entriesdata = GetHeaderEntriesData(); - //now there's enough space, it's safe to write the header data... - bw.BaseStream.Position = StartPos; - - bw.Write(Version); - bw.Write(EntryCount); - bw.Write(NamesLength); - bw.Write((uint)Encryption); - - //FileSize = ... //need to make sure this is updated for NG encryption... switch (Encryption) { @@ -916,6 +907,13 @@ namespace CodeWalker.GameFiles break; } + //now there's enough space, it's safe to write the header data... + bw.BaseStream.Position = StartPos; + + bw.Write(Version); + bw.Write(EntryCount); + bw.Write(NamesLength); + bw.Write((uint)Encryption); bw.Write(entriesdata); bw.Write(namesdata); @@ -1337,7 +1335,7 @@ namespace CodeWalker.GameFiles } } - private RpfFile FindChildArchive(RpfFileEntry f) + public RpfFile FindChildArchive(RpfFileEntry f) { RpfFile c = null; if (Children != null) @@ -1442,8 +1440,8 @@ namespace CodeWalker.GameFiles { //create a new directory inside the given parent dir - string namel = name.ToLowerInvariant(); RpfFile parent = dir.File; + string namel = name.ToLowerInvariant(); string fpath = parent.GetPhysicalFilePath(); string rpath = dir.Path + "\\" + namel; @@ -1486,8 +1484,9 @@ namespace CodeWalker.GameFiles public static RpfFileEntry CreateFile(RpfDirectoryEntry dir, string name, byte[] data) { RpfFile parent = dir.File; + string namel = name.ToLowerInvariant(); string fpath = parent.GetPhysicalFilePath(); - string rpath = dir.Path + "\\" + name; + string rpath = dir.Path + "\\" + namel; if (!File.Exists(fpath)) { throw new Exception("Root RPF file " + fpath + " does not exist!"); @@ -1497,8 +1496,15 @@ namespace CodeWalker.GameFiles RpfFileEntry entry = null; uint len = (uint)data.Length; - //check if this is RSC7 data, import as a resource if it is... - if ((len >= 16) && (BitConverter.ToUInt32(data, 0) == 0x37435352)) + + bool isrpf = false; + uint hdr = 0; + if (len >= 16) + { + hdr = BitConverter.ToUInt32(data, 0); + } + + if (hdr == 0x37435352) //'RSC7' { //RSC header is present... import as resource var rentry = new RpfResourceFileEntry(); @@ -1519,16 +1525,20 @@ namespace CodeWalker.GameFiles entry = rentry; } + if (namel.EndsWith(".rpf") && (hdr == 0x52504637)) //'RPF7' + { + isrpf = true; + } if (entry == null) { //no RSC7 header present, import as a binary file. - var compressed = CompressBytes(data); + var compressed = isrpf ? data : CompressBytes(data); var bentry = new RpfBinaryFileEntry(); bentry.EncryptionType = 0;//TODO: binary encryption bentry.IsEncrypted = false; bentry.FileUncompressedSize = (uint)data.Length; - bentry.FileSize = (uint)compressed.Length; + bentry.FileSize = isrpf ? 0 : (uint)compressed.Length; if (bentry.FileSize > 0xFFFFFF) { bentry.FileSize = 0; @@ -1577,6 +1587,25 @@ namespace CodeWalker.GameFiles } + if (isrpf) + { + //importing a raw RPF archive. create the new RpfFile object, and read its headers etc. + RpfFile file = new RpfFile(name, rpath, data.LongLength); + file.Parent = parent; + file.ParentFileEntry = entry as RpfBinaryFileEntry; + file.StartPos = parent.StartPos + (entry.FileOffset * 512); + parent.Children.Add(file); + + using (var fstream = File.OpenRead(fpath)) + { + using (var br = new BinaryReader(fstream)) + { + fstream.Position = file.StartPos; + file.ScanStructure(br, null, null); + } + } + } + return entry; } @@ -1690,6 +1719,19 @@ namespace CodeWalker.GameFiles } + public static void SetEncryptionType(RpfFile file, RpfEncryption encryption) + { + file.Encryption = encryption; + string fpath = file.GetPhysicalFilePath(); + using (var fstream = File.Open(fpath, FileMode.Open, FileAccess.ReadWrite)) + { + using (var bw = new BinaryWriter(fstream)) + { + file.WriteHeader(bw); + } + } + } + private static string GetParentPath(string path) diff --git a/Todo.txt b/Todo.txt index aa5edfc..e7a34db 100644 --- a/Todo.txt +++ b/Todo.txt @@ -105,6 +105,7 @@ entitySets - like mapDataGroups but for interiors..... in Mlo data World view - delete key - delete item! RPF Explorer Edit mode AWC audio player (dav90 WIP) +Invert mouse option [v.28]