mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-29 10:22:53 +08:00
1289 lines
42 KiB
C#
1289 lines
42 KiB
C#
using CodeWalker.GameFiles;
|
|
using CodeWalker.Properties;
|
|
using CodeWalker.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace CodeWalker
|
|
{
|
|
public partial class BrowseForm : Form
|
|
{
|
|
private volatile bool KeysLoaded = false;
|
|
private volatile bool InProgress = false;
|
|
private volatile bool AbortOperation = false;
|
|
|
|
private int TotalFileCount = 0;
|
|
private List<RpfFile> ScannedFiles = new List<RpfFile>();
|
|
private List<RpfFile> RootFiles = new List<RpfFile>();
|
|
|
|
private List<SearchResult> SearchResults = new List<SearchResult>();
|
|
private RpfEntry SelectedEntry = null;
|
|
private int SelectedOffset = -1;
|
|
private int SelectedLength = 0;
|
|
|
|
private bool TextureTabPageVisible = false;
|
|
|
|
private bool FlatStructure = false;
|
|
|
|
public BrowseForm()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private void BrowseForm_Load(object sender, EventArgs e)
|
|
{
|
|
var info = DetailsPropertyGrid.GetType().GetProperty("Controls");
|
|
var collection = info.GetValue(DetailsPropertyGrid, null) as Control.ControlCollection;
|
|
foreach (var control in collection)
|
|
{
|
|
var ctyp = control.GetType();
|
|
if (ctyp.Name == "PropertyGridView")
|
|
{
|
|
var prop = ctyp.GetField("labelRatio");
|
|
var val = prop.GetValue(control);
|
|
prop.SetValue(control, 4.0); //somehow this sets the width of the property grid's label column...
|
|
}
|
|
}
|
|
|
|
FolderTextBox.Text = GTAFolder.CurrentGTAFolder;
|
|
DataHexLineCombo.Text = "16";
|
|
|
|
DataTextBox.SetTabStopWidth(3);
|
|
|
|
HideTexturesTab();
|
|
|
|
try
|
|
{
|
|
GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key);
|
|
KeysLoaded = true;
|
|
UpdateStatus("Ready to scan...");
|
|
}
|
|
catch
|
|
{
|
|
UpdateStatus("Keys not loaded! This should not happen.");
|
|
}
|
|
}
|
|
|
|
private void BrowseForm_FormClosed(object sender, FormClosedEventArgs e)
|
|
{
|
|
if (!TextureTabPageVisible)
|
|
{
|
|
TexturesTabPage.Dispose();
|
|
}
|
|
}
|
|
|
|
private void FolderBrowseButton_Click(object sender, EventArgs e)
|
|
{
|
|
GTAFolder.UpdateGTAFolder(false);
|
|
FolderTextBox.Text = GTAFolder.CurrentGTAFolder;
|
|
}
|
|
|
|
private void ScanButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (InProgress) return;
|
|
|
|
if (!KeysLoaded) //this shouldn't ever really happen anymore
|
|
{
|
|
MessageBox.Show("Please scan a GTA 5 exe dump for keys first, or include key files in this app's folder!");
|
|
return;
|
|
}
|
|
if (!Directory.Exists(FolderTextBox.Text))
|
|
{
|
|
MessageBox.Show("Folder doesn't exist: " + FolderTextBox.Text);
|
|
return;
|
|
}
|
|
|
|
InProgress = true;
|
|
AbortOperation = false;
|
|
ScannedFiles.Clear();
|
|
RootFiles.Clear();
|
|
|
|
MainTreeView.Nodes.Clear();
|
|
|
|
string searchpath = FolderTextBox.Text;
|
|
string replpath = searchpath + "\\";
|
|
|
|
Task.Run(() =>
|
|
{
|
|
|
|
UpdateStatus("Starting scan...");
|
|
|
|
string[] allfiles = Directory.GetFiles(searchpath, "*.rpf", SearchOption.AllDirectories);
|
|
|
|
uint totrpfs = 0;
|
|
uint totfiles = 0;
|
|
uint totfolders = 0;
|
|
uint totresfiles = 0;
|
|
uint totbinfiles = 0;
|
|
|
|
foreach (string rpfpath in allfiles)
|
|
{
|
|
if (AbortOperation)
|
|
{
|
|
UpdateStatus("Scan aborted!");
|
|
InProgress = false;
|
|
return;
|
|
}
|
|
|
|
RpfFile rf = new RpfFile(rpfpath, rpfpath.Replace(replpath, ""));
|
|
|
|
UpdateStatus("Scanning " + rf.Name + "...");
|
|
|
|
rf.ScanStructure(UpdateStatus, UpdateStatus);
|
|
|
|
totrpfs += rf.GrandTotalRpfCount;
|
|
totfiles += rf.GrandTotalFileCount;
|
|
totfolders += rf.GrandTotalFolderCount;
|
|
totresfiles += rf.GrandTotalResourceCount;
|
|
totbinfiles += rf.GrandTotalBinaryFileCount;
|
|
|
|
AddScannedFile(rf, null, true);
|
|
|
|
RootFiles.Add(rf);
|
|
}
|
|
|
|
UpdateStatus(string.Format("Scan complete. {0} RPF files, {1} total files, {2} total folders, {3} resources, {4} binary files.", totrpfs, totfiles, totfolders, totresfiles, totbinfiles));
|
|
InProgress = false;
|
|
TotalFileCount = (int)totfiles;
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UpdateStatus(string text)
|
|
{
|
|
try
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => { UpdateStatus(text); }));
|
|
}
|
|
else
|
|
{
|
|
StatusLabel.Text = text;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void ClearFiles()
|
|
{
|
|
MainTreeView.Nodes.Clear();
|
|
}
|
|
private void AddScannedFile(RpfFile file, TreeNode node, bool addToList = false)
|
|
{
|
|
try
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => { AddScannedFile(file, node, addToList); }));
|
|
}
|
|
else
|
|
{
|
|
|
|
var cnode = AddFileNode(file, node);
|
|
|
|
if (FlatStructure) cnode = null;
|
|
|
|
foreach (RpfFile cfile in file.Children)
|
|
{
|
|
AddScannedFile(cfile, cnode, addToList);
|
|
}
|
|
if (addToList)
|
|
{
|
|
ScannedFiles.Add(file);
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
private TreeNode AddFileNode(RpfFile file, TreeNode n)
|
|
{
|
|
var nodes = (n == null) ? MainTreeView.Nodes : n.Nodes;
|
|
TreeNode node = nodes.Add(file.Path);
|
|
node.Tag = file;
|
|
|
|
foreach (RpfEntry entry in file.AllEntries)
|
|
{
|
|
if (entry is RpfFileEntry)
|
|
{
|
|
bool show = !entry.NameLower.EndsWith(".rpf"); //rpf entries get their own root node..
|
|
if (show)
|
|
{
|
|
//string text = entry.Path.Substring(file.Path.Length + 1); //includes \ on the end
|
|
//TreeNode cnode = node.Nodes.Add(text);
|
|
//cnode.Tag = entry;
|
|
TreeNode cnode = AddEntryNode(entry, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//make sure it's all in jenkindex...
|
|
JenkIndex.Ensure(file.Name);
|
|
foreach (RpfEntry entry in file.AllEntries)
|
|
{
|
|
if (string.IsNullOrEmpty(entry.Name)) continue;
|
|
JenkIndex.Ensure(entry.Name);
|
|
JenkIndex.Ensure(entry.NameLower);
|
|
int ind = entry.Name.LastIndexOf('.');
|
|
if (ind > 0)
|
|
{
|
|
JenkIndex.Ensure(entry.Name.Substring(0, ind));
|
|
JenkIndex.Ensure(entry.NameLower.Substring(0, ind));
|
|
}
|
|
}
|
|
|
|
return node;
|
|
|
|
|
|
|
|
|
|
//TreeNode lastNode = null;
|
|
//string subPathAgg;
|
|
//subPathAgg = string.Empty;
|
|
//foreach (string subPath in file.Path.Split('\\'))
|
|
//{
|
|
// subPathAgg += subPath + '\\';
|
|
// TreeNode[] nodes = MainTreeView.Nodes.Find(subPathAgg, true);
|
|
// if (nodes.Length == 0)
|
|
// {
|
|
// if (lastNode == null)
|
|
// {
|
|
// lastNode = MainTreeView.Nodes.Add(subPathAgg, subPath);
|
|
// }
|
|
// else
|
|
// {
|
|
// lastNode = lastNode.Nodes.Add(subPathAgg, subPath);
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// lastNode = nodes[0];
|
|
// }
|
|
//}
|
|
//lastNode.Tag = file;
|
|
|
|
}
|
|
private TreeNode AddEntryNode(RpfEntry entry, TreeNode node)
|
|
{
|
|
string text = entry.Path.Substring(entry.File.Path.Length + 1); //includes \ on the end
|
|
TreeNode cnode = (node != null) ? node.Nodes.Add(text) : MainTreeView.Nodes.Add(text);
|
|
cnode.Tag = entry;
|
|
return cnode;
|
|
}
|
|
|
|
|
|
private void MainTreeView_AfterSelect(object sender, TreeViewEventArgs e)
|
|
{
|
|
if (e.Node == null) return;
|
|
if (!e.Node.IsSelected) return; //only do this for selected node
|
|
if (MainTreeView.SelectedNode == null) return;
|
|
|
|
SelectedEntry = MainTreeView.SelectedNode.Tag as RpfEntry;
|
|
SelectedOffset = -1;
|
|
SelectedLength = 0;
|
|
|
|
SelectFile();
|
|
}
|
|
private void DataTextRadio_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
SelectFile();
|
|
}
|
|
private void DataHexLineCombo_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
SelectFile();
|
|
}
|
|
private void DataHexRadio_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
SelectFile();
|
|
}
|
|
|
|
|
|
private void SelectFile()
|
|
{
|
|
SelectFile(SelectedEntry, SelectedOffset, SelectedLength);
|
|
}
|
|
private void SelectFile(RpfEntry entry, int offset, int length)
|
|
{
|
|
SelectedEntry = entry;
|
|
SelectedOffset = offset;
|
|
SelectedLength = length;
|
|
|
|
RpfFileEntry rfe = entry as RpfFileEntry;
|
|
if (rfe == null)
|
|
{
|
|
RpfDirectoryEntry rde = entry as RpfDirectoryEntry;
|
|
if (rde != null)
|
|
{
|
|
FileInfoLabel.Text = rde.Path + " (Directory)";
|
|
DataTextBox.Text = "[Please select a data file]";
|
|
}
|
|
else
|
|
{
|
|
FileInfoLabel.Text = "[Nothing selected]";
|
|
DataTextBox.Text = "[Please select a data file]";
|
|
}
|
|
ShowTextures(null);
|
|
return;
|
|
}
|
|
|
|
|
|
Cursor = Cursors.WaitCursor;
|
|
|
|
string typestr = "Resource";
|
|
if (rfe is RpfBinaryFileEntry)
|
|
{
|
|
typestr = "Binary";
|
|
}
|
|
|
|
byte[] data = rfe.File.ExtractFile(rfe);
|
|
|
|
int datalen = (data != null) ? data.Length : 0;
|
|
FileInfoLabel.Text = rfe.Path + " (" + typestr + " file) - " + TextUtil.GetBytesReadable(datalen);
|
|
|
|
|
|
if (ShowLargeFileContentsCheckBox.Checked || (datalen < 524287)) //512K
|
|
{
|
|
DisplayFileContentsText(rfe, data, length, offset);
|
|
}
|
|
else
|
|
{
|
|
DataTextBox.Text = "[Filesize >512KB. Select the Show large files option to view its contents]";
|
|
}
|
|
|
|
|
|
|
|
bool istexdict = false;
|
|
|
|
|
|
if (rfe.NameLower.EndsWith(".ymap"))
|
|
{
|
|
YmapFile ymap = new YmapFile(rfe);
|
|
ymap.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ymap;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ytyp"))
|
|
{
|
|
YtypFile ytyp = new YtypFile();
|
|
ytyp.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ytyp;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ymf"))
|
|
{
|
|
YmfFile ymf = new YmfFile();
|
|
ymf.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ymf;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ymt"))
|
|
{
|
|
YmtFile ymt = new YmtFile();
|
|
ymt.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ymt;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ybn"))
|
|
{
|
|
YbnFile ybn = new YbnFile();
|
|
ybn.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ybn;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".fxc"))
|
|
{
|
|
FxcFile fxc = new FxcFile();
|
|
fxc.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = fxc;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".yft"))
|
|
{
|
|
YftFile yft = new YftFile();
|
|
yft.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = yft;
|
|
|
|
if ((yft.Fragment != null) && (yft.Fragment.Drawable != null) && (yft.Fragment.Drawable.ShaderGroup != null) && (yft.Fragment.Drawable.ShaderGroup.TextureDictionary != null))
|
|
{
|
|
ShowTextures(yft.Fragment.Drawable.ShaderGroup.TextureDictionary);
|
|
istexdict = true;
|
|
}
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ydr"))
|
|
{
|
|
YdrFile ydr = new YdrFile();
|
|
ydr.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ydr;
|
|
|
|
if ((ydr.Drawable != null) && (ydr.Drawable.ShaderGroup != null) && (ydr.Drawable.ShaderGroup.TextureDictionary != null))
|
|
{
|
|
ShowTextures(ydr.Drawable.ShaderGroup.TextureDictionary);
|
|
istexdict = true;
|
|
}
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ydd"))
|
|
{
|
|
YddFile ydd = new YddFile();
|
|
ydd.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ydd;
|
|
//todo: show embedded texdicts in ydd's? is this possible?
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ytd"))
|
|
{
|
|
YtdFile ytd = new YtdFile();
|
|
ytd.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ytd;
|
|
ShowTextures(ytd.TextureDict);
|
|
istexdict = true;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ycd"))
|
|
{
|
|
YcdFile ycd = new YcdFile();
|
|
ycd.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ycd;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ynd"))
|
|
{
|
|
YndFile ynd = new YndFile();
|
|
ynd.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ynd;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".ynv"))
|
|
{
|
|
YnvFile ynv = new YnvFile();
|
|
ynv.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = ynv;
|
|
}
|
|
else if (rfe.NameLower.EndsWith("_cache_y.dat"))
|
|
{
|
|
CacheDatFile cdf = new CacheDatFile();
|
|
cdf.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = cdf;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".rel"))
|
|
{
|
|
RelFile rel = new RelFile(rfe);
|
|
rel.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = rel;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".gxt2"))
|
|
{
|
|
Gxt2File gxt2 = new Gxt2File();
|
|
gxt2.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = gxt2;
|
|
}
|
|
else if (rfe.NameLower.EndsWith(".pso"))
|
|
{
|
|
JPsoFile pso = new JPsoFile();
|
|
pso.Load(data, rfe);
|
|
DetailsPropertyGrid.SelectedObject = pso;
|
|
}
|
|
else
|
|
{
|
|
DetailsPropertyGrid.SelectedObject = null;
|
|
|
|
}
|
|
|
|
|
|
if (!istexdict)
|
|
{
|
|
ShowTextures(null);
|
|
}
|
|
|
|
|
|
Cursor = Cursors.Default;
|
|
|
|
|
|
}
|
|
|
|
private void DisplayFileContentsText(RpfFileEntry rfe, byte[] data, int length, int offset)
|
|
{
|
|
if (data == null)
|
|
{
|
|
Cursor = Cursors.Default;
|
|
DataTextBox.Text = "[Error extracting file! " + rfe.File.LastError + "]";
|
|
return;
|
|
}
|
|
|
|
int selline = -1;
|
|
int selstartc = -1;
|
|
int selendc = -1;
|
|
|
|
if (DataHexRadio.Checked)
|
|
{
|
|
int charsperln = int.Parse(DataHexLineCombo.Text);
|
|
int lines = (data.Length / charsperln) + (((data.Length % charsperln) > 0) ? 1 : 0);
|
|
StringBuilder hexb = new StringBuilder();
|
|
StringBuilder texb = new StringBuilder();
|
|
StringBuilder finb = new StringBuilder();
|
|
|
|
if (offset > 0)
|
|
{
|
|
selline = offset / charsperln;
|
|
}
|
|
for (int i = 0; i < lines; i++)
|
|
{
|
|
int pos = i * charsperln;
|
|
int poslim = pos + charsperln;
|
|
hexb.Clear();
|
|
texb.Clear();
|
|
hexb.AppendFormat("{0:X4}: ", pos);
|
|
for (int c = pos; c < poslim; c++)
|
|
{
|
|
if (c < data.Length)
|
|
{
|
|
byte b = data[c];
|
|
hexb.AppendFormat("{0:X2} ", b);
|
|
if (char.IsControl((char)b))
|
|
{
|
|
texb.Append(".");
|
|
}
|
|
else
|
|
{
|
|
texb.Append(Encoding.ASCII.GetString(data, c, 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hexb.Append(" ");
|
|
texb.Append(" ");
|
|
}
|
|
}
|
|
|
|
if (i == selline) selstartc = finb.Length;
|
|
|
|
finb.AppendLine(hexb.ToString() + "| " + texb.ToString());
|
|
|
|
if (i == selline) selendc = finb.Length - 1;
|
|
}
|
|
|
|
DataTextBox.Text = finb.ToString();
|
|
}
|
|
else
|
|
{
|
|
|
|
string text = Encoding.UTF8.GetString(data);
|
|
|
|
|
|
DataTextBox.Text = text;
|
|
|
|
if (offset > 0)
|
|
{
|
|
selstartc = offset;
|
|
selendc = offset + length;
|
|
}
|
|
}
|
|
|
|
if ((selstartc > 0) && (selendc > 0))
|
|
{
|
|
DataTextBox.SelectionStart = selstartc;
|
|
DataTextBox.SelectionLength = selendc - selstartc;
|
|
DataTextBox.ScrollToCaret();
|
|
}
|
|
|
|
}
|
|
|
|
private void ShowTextures(TextureDictionary td)
|
|
{
|
|
SelTexturesListView.Items.Clear();
|
|
SelTexturePictureBox.Image = null;
|
|
SelTextureNameTextBox.Text = string.Empty;
|
|
SelTextureDimensionsLabel.Text = "-";
|
|
SelTextureMipLabel.Text = "0";
|
|
SelTextureMipTrackBar.Value = 0;
|
|
SelTextureMipTrackBar.Maximum = 0;
|
|
|
|
if (td == null)
|
|
{
|
|
HideTexturesTab();
|
|
return;
|
|
}
|
|
if (!SelectionTabControl.TabPages.Contains(TexturesTabPage))
|
|
{
|
|
SelectionTabControl.TabPages.Add(TexturesTabPage);
|
|
}
|
|
|
|
|
|
if ((td.Textures == null) || (td.Textures.data_items == null)) return;
|
|
var texs = td.Textures.data_items;
|
|
|
|
for (int i = 0; i < texs.Length; i++)
|
|
{
|
|
var tex = texs[i];
|
|
ListViewItem lvi = SelTexturesListView.Items.Add(tex.Name);
|
|
lvi.Tag = tex;
|
|
}
|
|
|
|
}
|
|
|
|
private void HideTexturesTab()
|
|
{
|
|
if (SelectionTabControl.TabPages.Contains(TexturesTabPage))
|
|
{
|
|
SelectionTabControl.TabPages.Remove(TexturesTabPage);
|
|
}
|
|
}
|
|
|
|
private void ShowTextureMip(Texture tex, int mip, bool mipchange)
|
|
{
|
|
if (tex == null)
|
|
{
|
|
SelTexturePictureBox.Image = null;
|
|
SelTextureNameTextBox.Text = string.Empty;
|
|
SelTextureDimensionsLabel.Text = "-";
|
|
SelTextureMipLabel.Text = "0";
|
|
SelTextureMipTrackBar.Value = 0;
|
|
SelTextureMipTrackBar.Maximum = 0;
|
|
return;
|
|
}
|
|
|
|
|
|
if (mipchange)
|
|
{
|
|
if (mip >= tex.Levels) mip = tex.Levels - 1;
|
|
}
|
|
else
|
|
{
|
|
SelTextureMipTrackBar.Maximum = tex.Levels - 1;
|
|
}
|
|
|
|
SelTextureNameTextBox.Text = tex.Name;
|
|
|
|
try
|
|
{
|
|
int cmip = Math.Min(Math.Max(mip, 0), tex.Levels - 1);
|
|
byte[] pixels = DDSIO.GetPixels(tex, cmip);
|
|
int w = tex.Width >> cmip;
|
|
int h = tex.Height >> cmip;
|
|
Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb);
|
|
|
|
if (pixels != null)
|
|
{
|
|
var BoundsRect = new System.Drawing.Rectangle(0, 0, w, h);
|
|
BitmapData bmpData = bmp.LockBits(BoundsRect, ImageLockMode.WriteOnly, bmp.PixelFormat);
|
|
IntPtr ptr = bmpData.Scan0;
|
|
int bytes = bmpData.Stride * bmp.Height;
|
|
Marshal.Copy(pixels, 0, ptr, bytes);
|
|
bmp.UnlockBits(bmpData);
|
|
}
|
|
|
|
SelTexturePictureBox.Image = bmp;
|
|
SelTextureDimensionsLabel.Text = w.ToString() + " x " + h.ToString();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show("Error reading texture mip:\n" + ex.ToString());
|
|
SelTexturePictureBox.Image = null;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void AbortButton_Click(object sender, EventArgs e)
|
|
{
|
|
AbortOperation = true;
|
|
}
|
|
|
|
|
|
private void TestAllButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (InProgress) return;
|
|
if (ScannedFiles.Count == 0)
|
|
{
|
|
MessageBox.Show("Please scan the GTAV folder first.");
|
|
return;
|
|
}
|
|
|
|
AbortOperation = false;
|
|
InProgress = true;
|
|
|
|
DataTextBox.Text = string.Empty;
|
|
FileInfoLabel.Text = "[File test results]";
|
|
|
|
Task.Run(() =>
|
|
{
|
|
|
|
UpdateStatus("Starting test...");
|
|
|
|
StringBuilder sbout = new StringBuilder();
|
|
int errcount = 0;
|
|
int curfile = 1;
|
|
int totrpfs = ScannedFiles.Count;
|
|
long totbytes = 0;
|
|
|
|
foreach (RpfFile file in ScannedFiles)
|
|
{
|
|
if (AbortOperation)
|
|
{
|
|
UpdateStatus("Test aborted.");
|
|
InProgress = false;
|
|
return;
|
|
}
|
|
|
|
UpdateStatus(curfile.ToString() + "/" + totrpfs.ToString() + ": Testing " + file.FilePath + "...");
|
|
|
|
string errorstr = file.TestExtractAllFiles();
|
|
|
|
if (!string.IsNullOrEmpty(errorstr))
|
|
{
|
|
AddTestError(errorstr);
|
|
sbout.Append(errorstr);
|
|
errcount++;
|
|
}
|
|
|
|
totbytes += file.ExtractedByteCount;
|
|
curfile++;
|
|
}
|
|
|
|
|
|
UpdateStatus("Test complete. " + errcount.ToString() + " problems encountered, " + totbytes.ToString() + " total bytes extracted.");
|
|
InProgress = false;
|
|
});
|
|
}
|
|
private void AddTestError(string error)
|
|
{
|
|
try
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => { AddTestError(error); }));
|
|
}
|
|
else
|
|
{
|
|
DataTextBox.AppendText(error);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void Find()
|
|
{
|
|
if (InProgress) return;
|
|
if (ScannedFiles.Count == 0)
|
|
{
|
|
MessageBox.Show("Please scan the GTAV folder first.");
|
|
return;
|
|
}
|
|
|
|
|
|
string find = FindTextBox.Text.ToLowerInvariant();
|
|
Cursor = Cursors.WaitCursor;
|
|
if (string.IsNullOrEmpty(find))
|
|
{
|
|
ClearFiles();
|
|
foreach (RpfFile file in RootFiles)
|
|
{
|
|
AddScannedFile(file, null); //reset the file tree... slow :(
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClearFiles();
|
|
int count = 0;
|
|
int max = 500;
|
|
foreach (RpfFile file in ScannedFiles)
|
|
{
|
|
if (file.Name.ToLowerInvariant().Contains(find))
|
|
{
|
|
AddFileNode(file, null);
|
|
count++;
|
|
}
|
|
foreach (RpfEntry entry in file.AllEntries)
|
|
{
|
|
if (entry.NameLower.Contains(find))
|
|
{
|
|
if (entry is RpfDirectoryEntry)
|
|
{
|
|
RpfDirectoryEntry direntry = entry as RpfDirectoryEntry;
|
|
|
|
TreeNode node = AddEntryNode(entry, null);
|
|
|
|
foreach (RpfFileEntry cfentry in direntry.Files)
|
|
{
|
|
//if (cfentry.Name.EndsWith(".rpf", StringComparison.InvariantCultureIgnoreCase)) continue;
|
|
AddEntryNode(cfentry, node);
|
|
}
|
|
count++;
|
|
}
|
|
else if (entry is RpfBinaryFileEntry)
|
|
{
|
|
if (entry.NameLower.EndsWith(".rpf", StringComparison.InvariantCultureIgnoreCase)) continue;
|
|
AddEntryNode(entry, null);
|
|
count++;
|
|
}
|
|
else if (entry is RpfResourceFileEntry)
|
|
{
|
|
AddEntryNode(entry, null);
|
|
count++;
|
|
}
|
|
}
|
|
if (count >= max)
|
|
{
|
|
MessageBox.Show("Search results limited to " + max.ToString() + " entries.");
|
|
break;
|
|
}
|
|
}
|
|
if (count >= max)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Cursor = Cursors.Default;
|
|
}
|
|
private void FindTextBox_KeyPress(object sender, KeyPressEventArgs e)
|
|
{
|
|
if (e.KeyChar == 13)
|
|
{
|
|
Find();
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
private void FindButton_Click(object sender, EventArgs e)
|
|
{
|
|
Find();
|
|
}
|
|
|
|
|
|
|
|
private void ExportButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (InProgress) return;
|
|
if (ScannedFiles.Count == 0)
|
|
{
|
|
MessageBox.Show("Please scan the GTAV folder first.");
|
|
return;
|
|
}
|
|
TreeNode node = MainTreeView.SelectedNode;
|
|
if (node == null)
|
|
{
|
|
MessageBox.Show("Please select a file to export.");
|
|
return;
|
|
}
|
|
|
|
RpfFileEntry rfe = node.Tag as RpfFileEntry;
|
|
if (rfe == null)
|
|
{
|
|
MessageBox.Show("Please select a file to export.");
|
|
return;
|
|
}
|
|
|
|
SaveFileDialog.FileName = rfe.Name;
|
|
if (SaveFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string fpath = SaveFileDialog.FileName;
|
|
|
|
byte[] data = rfe.File.ExtractFile(rfe);
|
|
|
|
|
|
if (ExportCompressCheckBox.Checked)
|
|
{
|
|
data = ResourceBuilder.Compress(data);
|
|
}
|
|
|
|
|
|
RpfResourceFileEntry rrfe = rfe as RpfResourceFileEntry;
|
|
if (rrfe != null) //add resource header if this is a resource file.
|
|
{
|
|
data = ResourceBuilder.AddResourceHeader(rrfe, data);
|
|
}
|
|
|
|
if (data == null)
|
|
{
|
|
MessageBox.Show("Error extracting file! " + rfe.File.LastError);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
File.WriteAllBytes(fpath, data);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show("Error saving file! " + ex.ToString());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class SearchResult
|
|
{
|
|
public RpfFileEntry FileEntry { get; set; }
|
|
public int Offset { get; set; }
|
|
public int Length { get; set; }
|
|
|
|
public SearchResult(RpfFileEntry entry, int offset, int length)
|
|
{
|
|
FileEntry = entry;
|
|
Offset = offset;
|
|
Length = length;
|
|
}
|
|
}
|
|
private byte LowerCaseByte(byte b)
|
|
{
|
|
if ((b >= 65) && (b <= 90)) //upper case alphabet...
|
|
{
|
|
b += 32;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
private void AddSearchResult(SearchResult result)
|
|
{
|
|
try
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => { AddSearchResult(result); }));
|
|
}
|
|
else
|
|
{
|
|
SearchResults.Add(result);
|
|
SearchResultsListView.VirtualListSize = SearchResults.Count;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void Search()
|
|
{
|
|
if (InProgress) return;
|
|
if (ScannedFiles.Count == 0)
|
|
{
|
|
MessageBox.Show("Please scan the GTAV folder first.");
|
|
return;
|
|
}
|
|
if (SearchTextBox.Text.Length == 0)
|
|
{
|
|
MessageBox.Show("Please enter a search term.");
|
|
return;
|
|
}
|
|
|
|
string searchtxt = SearchTextBox.Text;
|
|
bool hex = SearchHexRadioButton.Checked;
|
|
bool casesen = SearchCaseSensitiveCheckBox.Checked || hex;
|
|
bool bothdirs = SearchBothDirectionsCheckBox.Checked;
|
|
string[] ignoreexts = null;
|
|
byte[] searchbytes1;
|
|
byte[] searchbytes2;
|
|
int bytelen;
|
|
|
|
if (!casesen) searchtxt = searchtxt.ToLowerInvariant(); //case sensitive search in lower case.
|
|
|
|
if (SearchIgnoreCheckBox.Checked)
|
|
{
|
|
ignoreexts = SearchIgnoreTextBox.Text.Split(',');
|
|
for (int i = 0; i < ignoreexts.Length; i++)
|
|
{
|
|
ignoreexts[i] = ignoreexts[i].Trim();
|
|
}
|
|
}
|
|
|
|
if (hex)
|
|
{
|
|
if (searchtxt.Length < 2)
|
|
{
|
|
MessageBox.Show("Please enter at least one byte of hex (2 characters).");
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
bytelen = searchtxt.Length / 2;
|
|
searchbytes1 = new byte[bytelen];
|
|
searchbytes2 = new byte[bytelen];
|
|
for (int i = 0; i < bytelen; i++)
|
|
{
|
|
searchbytes1[i] = Convert.ToByte(searchtxt.Substring(i * 2, 2), 16);
|
|
searchbytes2[bytelen - i - 1] = searchbytes1[i];
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
MessageBox.Show("Please enter a valid hex string.");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bytelen = searchtxt.Length;
|
|
searchbytes1 = new byte[bytelen];
|
|
searchbytes2 = new byte[bytelen]; //reversed text...
|
|
for (int i = 0; i < bytelen; i++)
|
|
{
|
|
searchbytes1[i] = (byte)searchtxt[i];
|
|
searchbytes2[bytelen - i - 1] = searchbytes1[i];
|
|
}
|
|
}
|
|
|
|
SearchTextBox.Enabled = false;
|
|
SearchHexRadioButton.Enabled = false;
|
|
SearchTextRadioButton.Enabled = false;
|
|
SearchCaseSensitiveCheckBox.Enabled = false;
|
|
SearchBothDirectionsCheckBox.Enabled = false;
|
|
SearchIgnoreCheckBox.Enabled = false;
|
|
SearchIgnoreTextBox.Enabled = false;
|
|
SearchButton.Enabled = false;
|
|
SearchSaveResultsButton.Enabled = false;
|
|
|
|
InProgress = true;
|
|
AbortOperation = false;
|
|
SearchResultsListView.VirtualListSize = 0;
|
|
SearchResults.Clear();
|
|
int totfiles = TotalFileCount;
|
|
int curfile = 0;
|
|
Task.Run(() =>
|
|
{
|
|
|
|
DateTime starttime = DateTime.Now;
|
|
int resultcount = 0;
|
|
|
|
for (int f = 0; f < ScannedFiles.Count; f++)
|
|
{
|
|
var rpffile = ScannedFiles[f];
|
|
|
|
//UpdateStatus(string.Format("Searching {0}/{1} : {2}", f, ScannedFiles.Count, rpffile.Path));
|
|
|
|
foreach (var entry in rpffile.AllEntries)
|
|
{
|
|
var duration = DateTime.Now - starttime;
|
|
if (AbortOperation)
|
|
{
|
|
UpdateStatus(duration.ToString(@"hh\:mm\:ss") + " - Search aborted.");
|
|
InProgress = false;
|
|
SearchComplete();
|
|
return;
|
|
}
|
|
|
|
RpfFileEntry fentry = entry as RpfFileEntry;
|
|
if (fentry == null) continue;
|
|
|
|
curfile++;
|
|
|
|
if (fentry.NameLower.EndsWith(".rpf"))
|
|
{ continue; }
|
|
|
|
if (ignoreexts != null)
|
|
{
|
|
bool ignore = false;
|
|
for (int i = 0; i < ignoreexts.Length; i++)
|
|
{
|
|
if (fentry.NameLower.EndsWith(ignoreexts[i]))
|
|
{
|
|
ignore = true;
|
|
break;
|
|
}
|
|
}
|
|
if (ignore)
|
|
{ continue; }
|
|
}
|
|
|
|
UpdateStatus(string.Format("{0} - Searching {1}/{2} : {3}", duration.ToString(@"hh\:mm\:ss"), curfile, totfiles, fentry.Path));
|
|
|
|
byte[] filebytes = fentry.File.ExtractFile(fentry);
|
|
if (filebytes == null) continue;
|
|
|
|
|
|
int hitlen1 = 0;
|
|
int hitlen2 = 0;
|
|
|
|
for (int i = 0; i < filebytes.Length; i++)
|
|
{
|
|
byte b = casesen ? filebytes[i] : LowerCaseByte(filebytes[i]);
|
|
byte b1 = searchbytes1[hitlen1]; //current test byte 1
|
|
byte b2 = searchbytes2[hitlen2];
|
|
|
|
if (b == b1) hitlen1++; else hitlen1 = 0;
|
|
if (hitlen1 == bytelen)
|
|
{
|
|
AddSearchResult(new SearchResult(fentry, (i - bytelen), bytelen));
|
|
resultcount++;
|
|
hitlen1 = 0;
|
|
}
|
|
if (bothdirs)
|
|
{
|
|
if (b == b2) hitlen2++; else hitlen2 = 0;
|
|
if (hitlen2 == bytelen)
|
|
{
|
|
AddSearchResult(new SearchResult(fentry, (i - bytelen), bytelen));
|
|
resultcount++;
|
|
hitlen2 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var totdur = DateTime.Now - starttime;
|
|
UpdateStatus(totdur.ToString(@"hh\:mm\:ss") + " - Search complete. " + resultcount.ToString() + " results found.");
|
|
InProgress = false;
|
|
SearchComplete();
|
|
});
|
|
}
|
|
private void SearchComplete()
|
|
{
|
|
try
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => { SearchComplete(); }));
|
|
}
|
|
else
|
|
{
|
|
SearchTextBox.Enabled = true;
|
|
SearchHexRadioButton.Enabled = true;
|
|
SearchTextRadioButton.Enabled = true;
|
|
SearchCaseSensitiveCheckBox.Enabled = SearchTextRadioButton.Checked;
|
|
SearchBothDirectionsCheckBox.Enabled = true;
|
|
SearchIgnoreCheckBox.Enabled = true;
|
|
SearchIgnoreTextBox.Enabled = SearchIgnoreCheckBox.Checked;
|
|
SearchButton.Enabled = true;
|
|
SearchSaveResultsButton.Enabled = true;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
private void SearchButton_Click(object sender, EventArgs e)
|
|
{
|
|
Search();
|
|
}
|
|
private void SearchAbortButton_Click(object sender, EventArgs e)
|
|
{
|
|
AbortOperation = true;
|
|
}
|
|
private void SearchSaveResultsButton_Click(object sender, EventArgs e)
|
|
{
|
|
SaveFileDialog.FileName = "SearchResults.txt";
|
|
if (SaveFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string fpath = SaveFileDialog.FileName;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine("CodeWalker Search Results for \"" + SearchTextBox.Text + "\"");
|
|
sb.AppendLine("[File path], [Byte offset]");
|
|
if (SearchResults != null)
|
|
{
|
|
foreach (var r in SearchResults)
|
|
{
|
|
sb.AppendLine(r.FileEntry.Path + ", " + r.Offset.ToString());
|
|
}
|
|
}
|
|
|
|
File.WriteAllText(fpath, sb.ToString());
|
|
|
|
}
|
|
}
|
|
private void SearchTextRadioButton_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
SearchCaseSensitiveCheckBox.Enabled = SearchTextRadioButton.Checked;
|
|
}
|
|
private void SearchHexRadioButton_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
private void SearchResultsListView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
|
|
{
|
|
var item = new ListViewItem();
|
|
if (e.ItemIndex < SearchResults.Count)
|
|
{
|
|
SearchResult r = SearchResults[e.ItemIndex];
|
|
item.Text = r.FileEntry.Name;
|
|
item.SubItems.Add(r.Offset.ToString());
|
|
item.Tag = r;
|
|
}
|
|
e.Item = item;
|
|
}
|
|
private void SearchResultsListView_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (SearchResultsListView.SelectedIndices.Count == 1)
|
|
{
|
|
var i = SearchResultsListView.SelectedIndices[0];
|
|
if ((i >= 0) && (i < SearchResults.Count))
|
|
{
|
|
var r = SearchResults[i];
|
|
SelectFile(r.FileEntry, r.Offset+1, r.Length);
|
|
}
|
|
else
|
|
{
|
|
SelectFile(null, -1, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SelectFile(null, -1, 0);
|
|
}
|
|
}
|
|
private void SearchIgnoreCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
SearchIgnoreTextBox.Enabled = SearchIgnoreCheckBox.Checked;
|
|
}
|
|
|
|
private void SelTexturesListView_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
Texture tex = null;
|
|
if (SelTexturesListView.SelectedItems.Count == 1)
|
|
{
|
|
tex = SelTexturesListView.SelectedItems[0].Tag as Texture;
|
|
}
|
|
ShowTextureMip(tex, 0, false);
|
|
}
|
|
|
|
private void SelTextureMipTrackBar_Scroll(object sender, EventArgs e)
|
|
{
|
|
Texture tex = null;
|
|
if (SelTexturesListView.SelectedItems.Count == 1)
|
|
{
|
|
tex = SelTexturesListView.SelectedItems[0].Tag as Texture;
|
|
}
|
|
SelTextureMipLabel.Text = SelTextureMipTrackBar.Value.ToString();
|
|
ShowTextureMip(tex, SelTextureMipTrackBar.Value, true);
|
|
}
|
|
|
|
private void ShowLargeFileContentsCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
SelectFile();
|
|
}
|
|
|
|
private void FlattenStructureCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
FlatStructure = FlattenStructureCheckBox.Checked;
|
|
|
|
if (InProgress) return;
|
|
if (ScannedFiles.Count == 0) return;
|
|
|
|
Cursor = Cursors.WaitCursor;
|
|
|
|
SearchTextBox.Clear();
|
|
|
|
ClearFiles();
|
|
foreach (RpfFile file in RootFiles)
|
|
{
|
|
AddScannedFile(file, null);
|
|
}
|
|
|
|
Cursor = Cursors.Default;
|
|
}
|
|
}
|
|
}
|