Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

Added support for file preprocessing using lua scripts #83

Closed
wants to merge 4 commits into from

Conversation

AXiX-official
Copy link

Added support for file preprocessing using lua scripts

Mainly Change

In AssetsManager.cs:

private void LoadFile(string fullName)
        {
            FileReader reader = null;
            if (!EnableLuaScript)
            {
                reader = new FileReader(fullName);
            }
            else
            {
                luaEnvironment["filepath"] = fullName;
                luaEnvironment["filename"] = Path.GetFileName(fullName);
                try
                {
                    var result = luaEnvironment.DoString(LuaScript);
                    Stream fs = (Stream)result[0];
                    reader = new FileReader(fullName, fs);
                }
                catch (Exception e)
                {
                    Logger.Error($"Error while reading file {fullName} with lua", e);
                }
            }
            reader = reader.PreProcessing(Game);
            LoadFile(reader);
        }

Users can write their own simple lua scripts to do simple pre-processing of files without modifying the c# code to recompile it.
In the case of crosscore, for example, the header of the asset file for this game has an extra piece of duplicate information

55 6E 69 74 79 46 53 00 00 00 00 07 35 2E 78 2E  UnityFs
78 00 32 30 31 39 2E 34 2E 34 30 66 31 00 00 00
00 00 00 00 0E CA 00 00 00 41 00 00 00 5B 00 00
00 43 00 00 00 00 00 00 00 00 00 00 00 00 00 00
1E 00 01 00 B2 01 00 00 28 C8 00 00 0E 49 00 03
0E 00 08 01 00 00 1A 00 F0 18 00 04 55 6E 69 74  ................Unit
79 46 53 00 00 00 00 07 35 2E 78 2E 78 00 32 30  yFs

prefabs_spine_badlands.zip

Although users can write their own simple Python scripts to remove the extra headers, processing a large number of files can take up extra hard disk space or lose the original files.
Now they can write the following lua script to load the corrected file in memory:

function calculate_offset(stream)
    local readr = CreateEndianBinaryReader(stream)
    local buffer = readr:ReadBytes(512)
    local bufferTable = {}
    for i = 0, buffer.Length - 1 do
        bufferTable[i + 1] = buffer[i]
    end
    local allText = string.char(table.unpack(bufferTable))
    local firstPos = string.find(allText, 'Unity')
    if firstPos == nil then
        return nil
    end

    -- find second unity head
    local secondPos = string.find(allText, 'Unity', firstPos + 1)
    return secondPos - 1
end
    
local tmp = CreateFileStream(filepath)
tmp.Position = calculate_offset(tmp)
local fs = CreateMemoryStream()
tmp:CopyTo(fs)
tmp:Close()
fs.Position = 0
return fs

Interaction between Lua and C#

It provides three ways to call C# methods in lua scripts.

First Way

The file AssetStudio/Lua/LuaMethods.cs defines some functions in lua that call c# methods.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace AssetStudio;

public class LuaMethods
{
    public Delegate GetAnyMethod(string className, string methodName, object target = null)
    {
        var type = Type.GetType(className);
        if (type == null)
        {
            throw new Exception($"Class {className} not found.");
        }

        var method = type.GetMethod(methodName);
        if (method == null)
        {
            throw new Exception($"Method {methodName} not found in class {className}.");
        }

        var delegateType = GetDelegateType(method);
        var del = Delegate.CreateDelegate(delegateType, target, method);

        return del;
    }

    private Type GetDelegateType(MethodInfo methodInfo)
    {
        var types = methodInfo.GetParameters().Select(p => p.ParameterType).ToList();
        if (methodInfo.ReturnType != typeof(void))
        {
            types.Add(methodInfo.ReturnType);
        }
        return Expression.GetDelegateType(types.ToArray());
    }
    
    public FileStream CreateFileStream(string path)
    {
        return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    }

    public MemoryStream CreateMemoryStream()
    {
        return new MemoryStream();
    }
    
    public EndianBinaryReader CreateEndianBinaryReader(Stream stream)
    {
        return new EndianBinaryReader(stream);
    }
}

The CreateFileStream function helps to generate a new FileStream object:

local tmp = CreateFileStream(filepath)

After this, you can call the FileStream class methods directly via tmp:CopyTo(fs)

GetAnyMethod()

GetAnyMethod function can get any class method

local methodDelegate = luaEnv:GetAnyMethod(className, methodName)
local result = methodDelegate(arg1, arg2, ...) 

use luanet

luanet.load_assembly("System")
local DateTime = luanet.import_type("System.DateTime")
local now = DateTime.Now
print(now:ToString())

One problem with this approach is that functions with the same name in c# will be overwritten

Interactivity

GUI

image
Click Load Lua Template to open the file browser and select the lua script to import.
And click Enable Lua Scripts to enable lua script preprocessing.

CLI

Importing lua scripts with the --lua_script parameter.

 --lua_script <lua_script>                                   Specify Lua script path.

Lua Template

local tmp = CreateFileStream(filepath)
local fs = CreateMemoryStream()

-- Write your code here 
-- tmp is input filestream
-- fs is output stream,
--  which can be parsed directly by the studio.

tmp:Close()
return fs

The lua environment provides two default variables, filepath and filename, which represent the absolute path to a file and its name, respectively.
In addition, the constructors for FileStream, MemoryStream and EndianBinaryReader are pre-registered in this version.

  • fs = CreateFileStream(filepath) <==> FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  • ms = CreateMemoryStream() <==> MemoryStream ms = new MemoryStream();
  • reader = CreateEndianBinaryReader(stream) <==> EndianBinaryReader reader = new EndianBinaryReader(stream);
    After this you can use fs:method_name(arg1,arg2...) to call FileStream's class method
    If these interfaces don't meet your needs, you can try GetAnyMethod() and luanet mentioned above

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant