Skip to content

Commit

Permalink
basic decompiler implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ds5678 committed Aug 31, 2021
1 parent 1a03403 commit df10ac3
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 2 deletions.
1 change: 1 addition & 0 deletions AssetRipperCore/AssetRipperCore.csproj
Expand Up @@ -43,6 +43,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="ICSharpCode.Decompiler" Version="7.1.0.6543" />
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="Samboy063.Cpp2IL.Core" Version="2021.3.4" />
<PackageReference Include="SevenZipCsharp" Version="1.0.0" />
Expand Down
9 changes: 9 additions & 0 deletions AssetRipperCore/Classes/MonoScript.cs
Expand Up @@ -14,6 +14,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;

namespace AssetRipper.Core.Classes
{
Expand Down Expand Up @@ -127,6 +128,14 @@ public ScriptExportType GetExportType(ScriptExportManager exportManager)
return File.Collection.AssemblyManager.GetExportType(exportManager, scriptID);
}

public TypeDefinition GetTypeDefinition()
{
ScriptIdentifier scriptID = HasNamespace(File.Version) ?
File.Collection.AssemblyManager.GetScriptID(AssemblyName, Namespace, ClassName) :
File.Collection.AssemblyManager.GetScriptID(AssemblyName, ClassName);
return File.Collection.AssemblyManager.GetTypeDefinition(scriptID);
}

public ScriptIdentifier GetScriptID()
{
return File.Collection.AssemblyManager.GetScriptID(AssemblyName, ClassName);
Expand Down
81 changes: 81 additions & 0 deletions AssetRipperCore/Project/Exporters/Script/AltScriptExporter.cs
@@ -0,0 +1,81 @@
using AssetRipper.Core.Classes;
using AssetRipper.Core.Configuration;
using AssetRipper.Core.Parser.Asset;
using AssetRipper.Core.Parser.Files.SerializedFiles;
using AssetRipper.Core.Structure.Assembly.Managers;
using AssetRipper.Core.Structure.Collections;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using Object = AssetRipper.Core.Classes.Object.Object;

namespace AssetRipper.Core.Project.Exporters.Script
{
public class AltScriptExporter : IAssetExporter
{
private IAssemblyManager AssemblyManager { get; }

public AltScriptExporter(IAssemblyManager assemblyManager)
{
AssemblyManager = assemblyManager;
}

public bool IsHandle(Object asset, CoreConfiguration options)
{
return true;
}

public IExportCollection CreateCollection(VirtualSerializedFile virtualFile, Object asset)
{
return new ScriptExportCollection(this, (MonoScript)asset);
}

public bool Export(IExportContainer container, Object asset, string path)
{
throw new NotSupportedException("Need to export all scripts at once");
}

public void Export(IExportContainer container, Object asset, string path, Action<IExportContainer, Object, string> callback)
{
throw new NotSupportedException("Need to export all scripts at once");
}

public bool Export(IExportContainer container, IEnumerable<Object> assets, string dirPath)
{
Export(container, assets, dirPath, null);
return true;
}

public void Export(IExportContainer container, IEnumerable<Object> assets, string dirPath, Action<IExportContainer, Object, string> callback)
{
AltScriptManager scriptManager = new AltScriptManager(AssemblyManager, dirPath);
Dictionary<Object, TypeDefinition> exportTypes = new Dictionary<Object, TypeDefinition>();
foreach (Object asset in assets)
{
MonoScript script = (MonoScript)asset;
TypeDefinition exportType = script.GetTypeDefinition();
exportTypes.Add(asset, exportType);
}
foreach (KeyValuePair<Object, TypeDefinition> exportType in exportTypes)
{
string path = scriptManager.Export(exportType.Value);
if (path != null)
{
callback?.Invoke(container, exportType.Key, path);
}
}
scriptManager.ExportRest();
}

public AssetType ToExportType(Object asset)
{
return AssetType.Meta;
}

public bool ToUnknownExportType(ClassIDType classID, out AssetType assetType)
{
assetType = AssetType.Meta;
return true;
}
}
}
146 changes: 146 additions & 0 deletions AssetRipperCore/Project/Exporters/Script/AltScriptManager.cs
@@ -0,0 +1,146 @@
using AssetRipper.Core.IO;
using AssetRipper.Core.Logging;
using AssetRipper.Core.Structure.Assembly.Managers;
using AssetRipper.Core.Structure.Assembly.Mono;
using AssetRipper.Core.Utils;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace AssetRipper.Core.Project.Exporters.Script
{
public sealed class AltScriptManager
{
ScriptDecompiler Decompiler { get; }

public IEnumerable<TypeDefinition> Types => m_types.Values;

private readonly Dictionary<string, TypeDefinition> m_types = new Dictionary<string, TypeDefinition>();

private readonly HashSet<string> m_exported = new HashSet<string>();

private readonly string m_exportPath;

public AltScriptManager(IAssemblyManager assemblyManager, string exportPath)
{
if (string.IsNullOrEmpty(exportPath))
{
throw new ArgumentNullException(nameof(exportPath));
}
Decompiler = new ScriptDecompiler(assemblyManager);
m_exportPath = exportPath;
}

private static string GetExportSubPath(string assembly, string @namespace, string @class)
{
string assemblyFolder = BaseManager.ToAssemblyName(assembly);
string namespaceFolder = @namespace.Replace('.', Path.DirectorySeparatorChar);
string folderPath = Path.Combine(assemblyFolder, namespaceFolder);
string filePath = Path.Combine(folderPath, @class);
return $"{DirectoryUtils.FixInvalidPathCharacters(filePath)}.cs";
}

private static string GetExportSubPath(TypeDefinition type)
{
string typeName = type.Name;
int index = typeName.IndexOf('<');
if (index >= 0)
{
string normalName = typeName.Substring(0, index);
typeName = normalName + $".{typeName.Count(t => t == ',') + 1}";
}
return GetExportSubPath(type.Module.Name, type.Namespace, typeName);
}

public string Export(TypeDefinition exportType)
{
if (exportType.DeclaringType != null)
{
throw new NotSupportedException("You can export only topmost types");
}

if (IsBuiltInType(exportType))
{
return null;
}

string subPath = GetExportSubPath(exportType);
string filePath = Path.Combine(m_exportPath, subPath);
string uniqueFilePath = ToUniqueFileName(filePath);
string directory = Path.GetDirectoryName(uniqueFilePath);
if (!DirectoryUtils.Exists(directory))
{
DirectoryUtils.CreateVirtualDirectory(directory);
}

using (Stream fileStream = FileUtils.CreateVirtualFile(uniqueFilePath))
{
using (StreamWriter writer = new InvariantStreamWriter(fileStream, new UTF8Encoding(false)))
{
writer.Write(Decompiler.Decompile(exportType));
}
}
AddExportedType(exportType);
return uniqueFilePath;
}

public void ExportRest()
{
foreach (TypeDefinition type in m_types.Values)
{
if (type.DeclaringType != null)
{
continue;
}
if (m_exported.Contains(type.FullName))
{
continue;
}

Export(type);
}
}

private void AddExportedType(TypeDefinition exportType)
{
m_exported.Add(exportType.FullName);

foreach (TypeDefinition nestedType in exportType.NestedTypes)
{
AddExportedType(nestedType);
}
}

private static string ToUniqueFileName(string filePath)
{
if (FileUtils.Exists(filePath))
{
string directory = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileNameWithoutExtension(filePath);
string fileExtension = Path.GetExtension(filePath);
for (int i = 2; i < int.MaxValue; i++)
{
string newFilePath = Path.Combine(directory, $"{fileName}.{i}{fileExtension}");
if (!FileUtils.Exists(newFilePath))
{
Logger.Log(LogType.Warning, LogCategory.Export, $"Found duplicate script file at {filePath}. Renamed to {newFilePath}");
return newFilePath;
}
}
throw new Exception($"Can't create unit file at {filePath}");
}
else
{
return filePath;
}
}

private static bool IsBuiltInType(TypeDefinition type)
{
return MonoUtils.IsBuiltinLibrary(type.Module.Name);
}
}
}
71 changes: 71 additions & 0 deletions AssetRipperCore/Project/Exporters/Script/AssemblyResolver.cs
@@ -0,0 +1,71 @@
using AssetRipper.Core.Structure.Assembly.Managers;
using ICSharpCode.Decompiler.Metadata;
using Mono.Cecil;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace AssetRipper.Core.Project.Exporters.Script
{
internal class AssemblyResolver : ICSharpCode.Decompiler.Metadata.IAssemblyResolver
{
private readonly ConcurrentDictionary<string, PEFile> peAssemblies = new ConcurrentDictionary<string, PEFile>();
public AssemblyResolver(IAssemblyManager manager) : this(manager.GetAssemblies()) { }
public AssemblyResolver(ReadOnlySpan<AssemblyDefinition> assemblies)
{
foreach (var assembly in assemblies)
{
PEFile peFile = CreatePEFile(assembly);
if (!peAssemblies.TryAdd(assembly.FullName, peFile))
{
throw new Exception("Could not add pe assembly to name dictionary!");
}
}
}

private static PEFile CreatePEFile(AssemblyDefinition assembly)
{
if (assembly == null)
throw new ArgumentNullException(nameof(assembly));

MemoryStream memoryStream = new MemoryStream();
assembly.Write(memoryStream);
memoryStream.Position = 0;

return new PEFile(assembly.Name.Name, memoryStream);
}

public PEFile Resolve(IAssemblyReference reference) => Resolve(reference.FullName);

public PEFile Resolve(string fullName)
{
if (peAssemblies.TryGetValue(fullName, out PEFile result))
return result;
else
return null;
}

public Task<PEFile> ResolveAsync(IAssemblyReference reference)
{
return Task.Run(() => Resolve(reference));
}

/// <summary>
/// Finds a module in the same directory as another
/// </summary>
/// <param name="mainModule"></param>
/// <param name="moduleName"></param>
/// <returns></returns>
public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
return peAssemblies.Values.Where(x => x.Name == moduleName).Single();
}

public Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
return Task.Run(() => ResolveModule(mainModule, moduleName));
}
}
}

0 comments on commit df10ac3

Please sign in to comment.