RPF Explorer Edit mode - auto change archive encryption type when editing, import/export raw child RPFs

This commit is contained in:
dexyfex 2018-01-11 12:10:03 +11:00
parent 262b50303d
commit f9bda5835a
3 changed files with 174 additions and 29 deletions

View File

@ -1132,12 +1132,22 @@ namespace CodeWalker
private byte[] GetFileData(MainListItem file) private byte[] GetFileData(MainListItem file)
{ {
byte[] data = null; 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 //load file from RPF
if (file.File.File == null) return null; //no RPF file? go no further if (file.File.File != null) //need the reference to the RPF archive
{
data = file.File.File.ExtractFile(file.File); data = file.File.File.ExtractFile(file.File);
} }
}
else if (!string.IsNullOrEmpty(file.FullPath)) else if (!string.IsNullOrEmpty(file.FullPath))
{ {
//load file from filesystem //load file from filesystem
@ -1515,22 +1525,27 @@ namespace CodeWalker
bool isitem = false; bool isitem = false;
bool isfile = false; bool isfile = false;
bool isfolder = false; bool isfolder = false;
bool isarchive = false;
bool isfilesys = false; bool isfilesys = false;
bool issearch = CurrentFolder?.IsSearchResults ?? false; bool issearch = CurrentFolder?.IsSearchResults ?? false;
bool canview = false; bool canview = false;
bool canexportxml = false; bool canexportxml = false;
bool canextract = false;
bool canimport = EditMode && (CurrentFolder?.RpfFolder != null) && !issearch; bool canimport = EditMode && (CurrentFolder?.RpfFolder != null) && !issearch;
bool canedit = false; bool canedit = false;
if (item != null) if (item != null)
{ {
var entry = item.GetRpfEntry();
isitem = true;
isfilesys = (entry == null);
isarchive = (item.Folder?.RpfFile != null);
isfolder = (item.Folder != null); isfolder = (item.Folder != null);
isfilesys = (item.File == null) && (item.Folder == null); isfile = !isfolder;
canview = CanViewFile(item); canview = CanViewFile(item);
canexportxml = CanExportXml(item); canexportxml = CanExportXml(item);
isitem = true;
isfile = !isfolder;
canedit = EditMode && !issearch; canedit = EditMode && !issearch;
canextract = isfile || (isarchive && !isfilesys);
} }
@ -1538,7 +1553,7 @@ namespace CodeWalker
ListContextViewHexMenu.Enabled = isfile; ListContextViewHexMenu.Enabled = isfile;
ListContextExportXmlMenu.Enabled = canexportxml; ListContextExportXmlMenu.Enabled = canexportxml;
ListContextExtractRawMenu.Enabled = isfile; ListContextExtractRawMenu.Enabled = canextract;
ListContextExtractUncompressedMenu.Enabled = isfile; ListContextExtractUncompressedMenu.Enabled = isfile;
ListContextNewMenu.Visible = EditMode; 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<RpfFile> files = new List<RpfFile>();
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() private void ViewSelected()
{ {
for (int i = 0; i < MainListView.SelectedIndices.Count; i++) for (int i = 0; i < MainListView.SelectedIndices.Count; i++)
@ -1731,7 +1790,8 @@ namespace CodeWalker
var idx = MainListView.SelectedIndices[0]; var idx = MainListView.SelectedIndices[0];
if ((idx < 0) || (idx >= CurrentFiles.Count)) return; if ((idx < 0) || (idx >= CurrentFiles.Count)) return;
var file = CurrentFiles[idx]; var file = CurrentFiles[idx];
if (file.Folder == null)
if ((file.Folder == null) || (file.Folder.RpfFile != null))
{ {
byte[] data = GetFileData(file); byte[] data = GetFileData(file);
if (data == null) if (data == null)
@ -1744,7 +1804,7 @@ namespace CodeWalker
RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry; RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry;
if (rrfe != null) //add resource header if this is a resource file. 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); data = ResourceBuilder.AddResourceHeader(rrfe, data);
} }
@ -1777,7 +1837,7 @@ namespace CodeWalker
var idx = MainListView.SelectedIndices[i]; var idx = MainListView.SelectedIndices[i];
if ((idx < 0) || (idx >= CurrentFiles.Count)) continue; if ((idx < 0) || (idx >= CurrentFiles.Count)) continue;
var file = CurrentFiles[idx]; var file = CurrentFiles[idx];
if (file.Folder == null) if ((file.Folder == null) || (file.Folder.RpfFile != null))
{ {
var path = folderpath + file.Name; var path = folderpath + file.Name;
var data = GetFileData(file); var data = GetFileData(file);
@ -1791,7 +1851,7 @@ namespace CodeWalker
RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry; RpfResourceFileEntry rrfe = file.File as RpfResourceFileEntry;
if (rrfe != null) //add resource header if this is a resource file. 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); data = ResourceBuilder.AddResourceHeader(rrfe, data);
} }
@ -1893,7 +1953,7 @@ namespace CodeWalker
foreach (var file in CurrentFiles) foreach (var file in CurrentFiles)
{ {
if (file.Folder == null) if ((file.Folder == null) || (file.Folder.RpfFile != null))
{ {
var path = folderpath + file.Name; var path = folderpath + file.Name;
var data = GetFileData(file); var data = GetFileData(file);
@ -1950,6 +2010,8 @@ namespace CodeWalker
{ {
if (CurrentFolder.RpfFolder != null) if (CurrentFolder.RpfFolder != null)
{ {
if (!EnsureRpfValidEncryption()) return;
//create new directory entry in the RPF. //create new directory entry in the RPF.
newdir = RpfFile.CreateDirectory(CurrentFolder.RpfFolder, fname); newdir = RpfFile.CreateDirectory(CurrentFolder.RpfFolder, fname);
@ -2007,6 +2069,8 @@ namespace CodeWalker
{ {
if (CurrentFolder.RpfFolder != null) if (CurrentFolder.RpfFolder != null)
{ {
if (!EnsureRpfValidEncryption()) return;
//adding a new RPF as a child of another //adding a new RPF as a child of another
newrpf = RpfFile.CreateNew(CurrentFolder.RpfFolder, fname, encryption); newrpf = RpfFile.CreateNew(CurrentFolder.RpfFolder, fname, encryption);
} }
@ -2043,6 +2107,9 @@ namespace CodeWalker
return; return;
} }
if (!EnsureRpfValidEncryption()) return;
OpenFileDialog.Filter = "XML Files|*.xml"; OpenFileDialog.Filter = "XML Files|*.xml";
if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) if (OpenFileDialog.ShowDialog(this) != DialogResult.OK)
{ {
@ -2140,6 +2207,9 @@ namespace CodeWalker
return; return;
} }
if (!EnsureRpfValidEncryption()) return;
OpenFileDialog.Filter = string.Empty; OpenFileDialog.Filter = string.Empty;
if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) 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 (entry != null)
{ {
if (!EnsureRpfValidEncryption()) return;
//renaming an entry in an RPF //renaming an entry in an RPF
RpfFile.RenameEntry(entry, newname); RpfFile.RenameEntry(entry, newname);
} }
@ -2327,6 +2423,9 @@ namespace CodeWalker
if (MainListView.SelectedIndices.Count != 1) return; if (MainListView.SelectedIndices.Count != 1) return;
MessageBox.Show("ReplaceSelected TODO..."); MessageBox.Show("ReplaceSelected TODO...");
//delete the selected items, and replace with... choose //delete the selected items, and replace with... choose
//if (!EnsureRpfEncryptionType()) return;
} }
private void DeleteSelected() private void DeleteSelected()
{ {
@ -2368,6 +2467,8 @@ namespace CodeWalker
if (parent.RpfFolder != null) if (parent.RpfFolder != null)
{ {
//delete an item in an RPF. //delete an item in an RPF.
if (!EnsureRpfValidEncryption()) return;
RpfEntry entry = item.GetRpfEntry(); RpfEntry entry = item.GetRpfEntry();
RpfFile.DeleteEntry(entry); RpfFile.DeleteEntry(entry);
@ -3251,6 +3352,7 @@ namespace CodeWalker
{ {
FileSize = fld.RpfFile.FileSize; FileSize = fld.RpfFile.FileSize;
FileSizeText = TextUtil.GetBytesReadable(FileSize); FileSizeText = TextUtil.GetBytesReadable(FileSize);
Attributes += fld.RpfFile.Encryption.ToString() + " encryption";
} }
else else
{ {

View File

@ -885,15 +885,6 @@ namespace CodeWalker.GameFiles
//entries may have been updated, so need to do this after ensuring header space //entries may have been updated, so need to do this after ensuring header space
var entriesdata = GetHeaderEntriesData(); 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... //FileSize = ... //need to make sure this is updated for NG encryption...
switch (Encryption) switch (Encryption)
{ {
@ -916,6 +907,13 @@ namespace CodeWalker.GameFiles
break; 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(entriesdata);
bw.Write(namesdata); bw.Write(namesdata);
@ -1337,7 +1335,7 @@ namespace CodeWalker.GameFiles
} }
} }
private RpfFile FindChildArchive(RpfFileEntry f) public RpfFile FindChildArchive(RpfFileEntry f)
{ {
RpfFile c = null; RpfFile c = null;
if (Children != null) if (Children != null)
@ -1442,8 +1440,8 @@ namespace CodeWalker.GameFiles
{ {
//create a new directory inside the given parent dir //create a new directory inside the given parent dir
string namel = name.ToLowerInvariant();
RpfFile parent = dir.File; RpfFile parent = dir.File;
string namel = name.ToLowerInvariant();
string fpath = parent.GetPhysicalFilePath(); string fpath = parent.GetPhysicalFilePath();
string rpath = dir.Path + "\\" + namel; string rpath = dir.Path + "\\" + namel;
@ -1486,8 +1484,9 @@ namespace CodeWalker.GameFiles
public static RpfFileEntry CreateFile(RpfDirectoryEntry dir, string name, byte[] data) public static RpfFileEntry CreateFile(RpfDirectoryEntry dir, string name, byte[] data)
{ {
RpfFile parent = dir.File; RpfFile parent = dir.File;
string namel = name.ToLowerInvariant();
string fpath = parent.GetPhysicalFilePath(); string fpath = parent.GetPhysicalFilePath();
string rpath = dir.Path + "\\" + name; string rpath = dir.Path + "\\" + namel;
if (!File.Exists(fpath)) if (!File.Exists(fpath))
{ {
throw new Exception("Root RPF file " + fpath + " does not exist!"); throw new Exception("Root RPF file " + fpath + " does not exist!");
@ -1497,8 +1496,15 @@ namespace CodeWalker.GameFiles
RpfFileEntry entry = null; RpfFileEntry entry = null;
uint len = (uint)data.Length; 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 //RSC header is present... import as resource
var rentry = new RpfResourceFileEntry(); var rentry = new RpfResourceFileEntry();
@ -1519,16 +1525,20 @@ namespace CodeWalker.GameFiles
entry = rentry; entry = rentry;
} }
if (namel.EndsWith(".rpf") && (hdr == 0x52504637)) //'RPF7'
{
isrpf = true;
}
if (entry == null) if (entry == null)
{ {
//no RSC7 header present, import as a binary file. //no RSC7 header present, import as a binary file.
var compressed = CompressBytes(data); var compressed = isrpf ? data : CompressBytes(data);
var bentry = new RpfBinaryFileEntry(); var bentry = new RpfBinaryFileEntry();
bentry.EncryptionType = 0;//TODO: binary encryption bentry.EncryptionType = 0;//TODO: binary encryption
bentry.IsEncrypted = false; bentry.IsEncrypted = false;
bentry.FileUncompressedSize = (uint)data.Length; bentry.FileUncompressedSize = (uint)data.Length;
bentry.FileSize = (uint)compressed.Length; bentry.FileSize = isrpf ? 0 : (uint)compressed.Length;
if (bentry.FileSize > 0xFFFFFF) if (bentry.FileSize > 0xFFFFFF)
{ {
bentry.FileSize = 0; 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; 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) private static string GetParentPath(string path)

View File

@ -105,6 +105,7 @@ entitySets - like mapDataGroups but for interiors..... in Mlo data
World view - delete key - delete item! World view - delete key - delete item!
RPF Explorer Edit mode RPF Explorer Edit mode
AWC audio player (dav90 WIP) AWC audio player (dav90 WIP)
Invert mouse option
[v.28] [v.28]