416 lines
15 KiB
C#
416 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using Microsoft.Win32;
|
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
|
using RageLib.Archives;
|
|
using RageLib.GTA5.Archives;
|
|
using RageLib.GTA5.ArchiveWrappers;
|
|
using RageLib.GTA5.Cryptography;
|
|
using RageLib.GTA5.Utilities;
|
|
|
|
namespace CarConverter {
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class MainWindow {
|
|
private DirectoryInfo root;
|
|
|
|
public MainWindow() {
|
|
InitializeComponent();
|
|
|
|
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CarConverter\";
|
|
if (Directory.Exists(path))
|
|
OutputDir.Content = File.ReadAllText(path + "DefaultOutput.penis");
|
|
|
|
//ConsoleManager.Show();
|
|
}
|
|
|
|
private void OnRPFDrop(object sender, DragEventArgs e) {
|
|
Clear(null, null);
|
|
|
|
var files = e.Data.GetData(DataFormats.FileDrop) as string[];
|
|
if (files.Length != 1) {
|
|
MessageBox.Show("Du kannst nur eine Datei gleichzeitig Decompilen");
|
|
}
|
|
else {
|
|
root = new DirectoryInfo(DecompileRpfFile(new FileInfo(files[0])));
|
|
|
|
TreeViewItem item = new TreeViewItem();
|
|
item.Header = new FileInfo(files[0]).Name;
|
|
item.Tag = root;
|
|
item.FontWeight = FontWeights.Normal;
|
|
FoldersItem.Items.Add(item);
|
|
|
|
PlaceFilesInTree(item, root);
|
|
LoadFiles(root);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void PlaceFilesInTree(TreeViewItem parent, DirectoryInfo info) {
|
|
foreach (DirectoryInfo directoryInfo in info.GetDirectories()) {
|
|
TreeViewItem p = new TreeViewItem();
|
|
p.Header = directoryInfo.Name;
|
|
p.Tag = directoryInfo;
|
|
p.FontWeight = FontWeights.Normal;
|
|
parent.Items.Add(p);
|
|
PlaceFilesInTree(p, directoryInfo);
|
|
}
|
|
|
|
foreach (FileInfo file in info.GetFiles()) {
|
|
TreeViewItem item = new TreeViewItem();
|
|
item.Header = file.Name;
|
|
item.Tag = file;
|
|
item.FontWeight = FontWeights.Normal;
|
|
parent.Items.Add(item);
|
|
}
|
|
}
|
|
|
|
private string DecompileRpfFile(FileInfo fileInfo) {
|
|
try {
|
|
var outFolder = Path.GetTempPath() + @"CarConverter\Decompiled\";
|
|
if (Directory.Exists(outFolder))
|
|
Directory.Delete(outFolder, true);
|
|
|
|
var fileStream = new FileStream(fileInfo.FullName, FileMode.Open);
|
|
|
|
var inputArchive = RageArchiveWrapper7.Open(fileStream, fileInfo.Name);
|
|
var queue = new List<Tuple<string, RageArchiveWrapper7, bool>>()
|
|
{ new Tuple<string, RageArchiveWrapper7, bool>(fileInfo.FullName, inputArchive, false) };
|
|
|
|
while (queue.Count > 0) {
|
|
var fullPath = queue[0].Item1;
|
|
var rpf = queue[0].Item2;
|
|
var isTmpStream = queue[0].Item3;
|
|
|
|
queue.RemoveAt(0);
|
|
|
|
ArchiveUtilities.ForEachFile(fullPath.Replace(fileInfo.FullName, ""), rpf.Root, rpf.archive_.Encryption, (fullFileName, file, encryption) => {
|
|
string path = outFolder + fullFileName;
|
|
string dir = Path.GetDirectoryName(path);
|
|
|
|
if (!Directory.Exists(dir))
|
|
Directory.CreateDirectory(dir);
|
|
|
|
Console.WriteLine(fullFileName);
|
|
|
|
if (file.Name.EndsWith(".rpf")) {
|
|
try {
|
|
var tmpStream = new FileStream(Path.GetTempFileName(), FileMode.Open);
|
|
|
|
file.Export(tmpStream);
|
|
RageArchiveWrapper7 archive = RageArchiveWrapper7.Open(tmpStream, file.Name);
|
|
queue.Add(new Tuple<string, RageArchiveWrapper7, bool>(fullFileName, archive, true));
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine(e);
|
|
}
|
|
}
|
|
else {
|
|
if (file.Name.EndsWith(".xml") || file.Name.EndsWith(".meta")) {
|
|
byte[] data = GetBinaryFileData((IArchiveBinaryFile)file, encryption);
|
|
string xml;
|
|
|
|
if (data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) // Detect BOM
|
|
{
|
|
xml = Encoding.UTF8.GetString(data, 3, data.Length - 3);
|
|
}
|
|
else {
|
|
xml = Encoding.UTF8.GetString(data);
|
|
}
|
|
|
|
File.WriteAllText(path, xml, Encoding.UTF8);
|
|
}
|
|
else {
|
|
file.Export(path);
|
|
}
|
|
}
|
|
});
|
|
|
|
var stream = (FileStream)rpf.archive_.BaseStream;
|
|
string fileName = stream.Name;
|
|
|
|
rpf.Dispose();
|
|
|
|
if (isTmpStream) {
|
|
File.Delete(fileName);
|
|
}
|
|
}
|
|
|
|
return outFolder;
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine(e);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
private static byte[] GetBinaryFileData(IArchiveBinaryFile file, RageArchiveEncryption7 encryption)
|
|
{
|
|
using (var ms = new MemoryStream())
|
|
{
|
|
file.Export(ms);
|
|
|
|
byte[] data = ms.ToArray();
|
|
|
|
if(file.IsEncrypted)
|
|
{
|
|
if (encryption == RageArchiveEncryption7.AES)
|
|
{
|
|
data = GTA5Crypto.DecryptAES(data);
|
|
}
|
|
else // if(encryption == RageArchiveEncryption7.NG)
|
|
{
|
|
data = GTA5Crypto.DecryptNG(data, file.Name, (uint)file.UncompressedSize);
|
|
}
|
|
}
|
|
|
|
if (file.IsCompressed)
|
|
{
|
|
using (var dfls = new DeflateStream(new MemoryStream(data), CompressionMode.Decompress))
|
|
{
|
|
using (var outstr = new MemoryStream())
|
|
{
|
|
dfls.CopyTo(outstr);
|
|
data = outstr.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
private void LoadFiles(DirectoryInfo directory) {
|
|
DirectoryInfo metaFolder = new DirectoryInfo(directory.FullName + @"data\");
|
|
foreach (FileInfo file in metaFolder.GetFiles()) {
|
|
AddMetaFile(file);
|
|
}
|
|
|
|
DirectoryInfo streamFolder = new DirectoryInfo(directory.FullName + @"x64\vehicles.rpf");
|
|
foreach (FileInfo file in streamFolder.GetFiles()) {
|
|
AddStreamFile(file);
|
|
}
|
|
|
|
DirectoryInfo modsFolder = new DirectoryInfo(directory.FullName + @"x64\vehiclemods");
|
|
if (modsFolder.Exists) {
|
|
foreach (DirectoryInfo folder in modsFolder.GetDirectories()) {
|
|
foreach (FileInfo file in folder.GetFiles()) {
|
|
AddStreamFile(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (StreamFiles.Items.Count > 0)
|
|
CarName.Text = ((StreamFiles.Items[0] as TreeViewItem).Tag as FileInfo).Name.Split('.')[0];
|
|
}
|
|
|
|
private void AddMetaFile(FileInfo file) {
|
|
TreeViewItem item = new TreeViewItem();
|
|
item.Header = file.Name;
|
|
item.Tag = file;
|
|
item.FontWeight = FontWeights.Normal;
|
|
MetaFiles.Items.Add(item);
|
|
}
|
|
|
|
private void AddStreamFile(FileInfo file) {
|
|
TreeViewItem item = new TreeViewItem();
|
|
item.Header = file.Name;
|
|
item.Tag = file;
|
|
item.FontWeight = FontWeights.Normal;
|
|
StreamFiles.Items.Add(item);
|
|
}
|
|
|
|
private void OnImplement(object sender, MouseButtonEventArgs e) {
|
|
TreeViewItem item = FoldersItem.SelectedItem as TreeViewItem;
|
|
if (item == null) return;
|
|
if (item.Tag is FileInfo) {
|
|
FileInfo file = item.Tag as FileInfo;
|
|
if (file is null) return;
|
|
|
|
if (file.Name.EndsWith(".meta")) {
|
|
AddMetaFile(file);
|
|
}
|
|
else {
|
|
AddStreamFile(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnRemove(object sender, MouseButtonEventArgs e) {
|
|
TreeView view = sender as TreeView;
|
|
if (view is null) return;
|
|
view.Items.Remove(view.SelectedItem);
|
|
}
|
|
|
|
private void Clear(object sender, RoutedEventArgs e) {
|
|
FoldersItem.Items.Clear();
|
|
MetaFiles.Items.Clear();
|
|
StreamFiles.Items.Clear();
|
|
root = null;
|
|
}
|
|
|
|
private void Build(object sender, RoutedEventArgs e) {
|
|
try {
|
|
var outputPath = OutputDir.Content + @"\" + CarName.Text;
|
|
if (Directory.Exists(outputPath))
|
|
Directory.Delete(outputPath, true);
|
|
|
|
DirectoryInfo info = Directory.CreateDirectory(outputPath);
|
|
File.WriteAllBytes(info.FullName + @"\__resource.lua", GetResourceBytes());
|
|
DirectoryInfo streamFolder = info.CreateSubdirectory("stream");
|
|
|
|
foreach (var item in MetaFiles.Items) {
|
|
FileInfo file = (item as TreeViewItem).Tag as FileInfo;
|
|
file.CopyTo(info.FullName + @"\" + file.Name);
|
|
}
|
|
|
|
foreach (var item in StreamFiles.Items) {
|
|
FileInfo file = (item as TreeViewItem).Tag as FileInfo;
|
|
file.CopyTo(streamFolder.FullName + @"\" + file.Name);
|
|
}
|
|
|
|
MessageBox.Show("Fertig du Pimmelberger");
|
|
}
|
|
catch (Exception exception) {
|
|
Console.WriteLine(exception);
|
|
}
|
|
}
|
|
|
|
private byte[] GetResourceBytes() {
|
|
return Encoding.ASCII.GetBytes("resource_manifest_version '77731fab-63ca-442c-a67b-abc70f28dfa5'\n\n" +
|
|
|
|
"files {\n" +
|
|
" 'carcols.meta',\n" +
|
|
" 'vehicles.meta',\n" +
|
|
" 'carvariations.meta',\n" +
|
|
" 'handling.meta',\n" +
|
|
"}\n\n" +
|
|
|
|
"data_file 'CARCOLS_FILE' 'carcols.meta'\n" +
|
|
"data_file 'HANDLING_FILE' 'handling.meta'\n" +
|
|
"data_file 'VEHICLE_METADATA_FILE' 'vehicles.meta'\n" +
|
|
"data_file 'VEHICLE_VARIATION_FILE' 'carvariations.meta'\n");
|
|
}
|
|
|
|
private void ChooseOutput(object sender, RoutedEventArgs e) {
|
|
var dialog = new CommonOpenFileDialog();
|
|
dialog.Multiselect = false;
|
|
dialog.IsFolderPicker = true;
|
|
if (dialog.ShowDialog(this) == CommonFileDialogResult.Ok) {
|
|
OutputDir.Content = dialog.FileName;
|
|
|
|
//Save Output dir
|
|
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CarConverter\";
|
|
if (!Directory.Exists(path))
|
|
Directory.CreateDirectory(path);
|
|
|
|
File.WriteAllText(path + "DefaultOutput.penis", OutputDir.Content as string);
|
|
}
|
|
}
|
|
}
|
|
|
|
[SuppressUnmanagedCodeSecurity]
|
|
public static class ConsoleManager
|
|
{
|
|
private const string Kernel32DllName = "kernel32.dll";
|
|
|
|
[DllImport(Kernel32DllName)]
|
|
private static extern bool AllocConsole();
|
|
|
|
[DllImport(Kernel32DllName)]
|
|
private static extern bool FreeConsole();
|
|
|
|
[DllImport(Kernel32DllName)]
|
|
private static extern IntPtr GetConsoleWindow();
|
|
|
|
[DllImport(Kernel32DllName)]
|
|
private static extern int GetConsoleOutputCP();
|
|
|
|
public static bool HasConsole
|
|
{
|
|
get { return GetConsoleWindow() != IntPtr.Zero; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new console instance if the process is not attached to a console already.
|
|
/// </summary>
|
|
public static void Show()
|
|
{
|
|
//#if DEBUG
|
|
if (!HasConsole)
|
|
{
|
|
AllocConsole();
|
|
InvalidateOutAndError();
|
|
}
|
|
//#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the process has a console attached to it, it will be detached and no longer visible. Writing to the System.Console is still possible, but no output will be shown.
|
|
/// </summary>
|
|
public static void Hide()
|
|
{
|
|
//#if DEBUG
|
|
if (HasConsole)
|
|
{
|
|
SetOutAndErrorNull();
|
|
FreeConsole();
|
|
}
|
|
//#endif
|
|
}
|
|
|
|
public static void Toggle()
|
|
{
|
|
if (HasConsole)
|
|
{
|
|
Hide();
|
|
}
|
|
else
|
|
{
|
|
Show();
|
|
}
|
|
}
|
|
|
|
static void InvalidateOutAndError()
|
|
{
|
|
Type type = typeof(System.Console);
|
|
|
|
System.Reflection.FieldInfo _out = type.GetField("_out",
|
|
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
|
|
|
System.Reflection.FieldInfo _error = type.GetField("_error",
|
|
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
|
|
|
System.Reflection.MethodInfo _InitializeStdOutError = type.GetMethod("InitializeStdOutError",
|
|
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
|
|
|
Debug.Assert(_out != null);
|
|
Debug.Assert(_error != null);
|
|
|
|
Debug.Assert(_InitializeStdOutError != null);
|
|
|
|
_out.SetValue(null, null);
|
|
_error.SetValue(null, null);
|
|
|
|
_InitializeStdOutError.Invoke(null, new object[] { true });
|
|
}
|
|
|
|
static void SetOutAndErrorNull()
|
|
{
|
|
Console.SetOut(TextWriter.Null);
|
|
Console.SetError(TextWriter.Null);
|
|
}
|
|
}
|
|
} |