This commit is contained in:
Razmoth
2023-08-21 21:45:57 +04:00
parent 6da2387c8c
commit 0bd3fa6db2
48 changed files with 967 additions and 510 deletions

View File

@@ -2,15 +2,15 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Version>0.90.00</Version>
<AssemblyVersion>0.90.00</AssemblyVersion>
<FileVersion>0.90.00</FileVersion>
<Version>0.90.10</Version>
<AssemblyVersion>0.90.10</AssemblyVersion>
<FileVersion>0.90.10</FileVersion>
<Copyright>Copyright © Razmoth 2022; Copyright © Perfare 2018-2022</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.6" />
<PackageReference Include="MessagePack" Version="2.6.100-alpha" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ZstdSharp.Port" Version="0.7.2" />

View File

@@ -36,7 +36,9 @@ namespace AssetStudio
{
Directory.CreateDirectory(MapName);
var files = Directory.GetFiles(MapName, "*.bin", SearchOption.TopDirectoryOnly);
return files.Select(Path.GetFileNameWithoutExtension).ToArray();
var mapNames = files.Select(Path.GetFileNameWithoutExtension).ToArray();
Logger.Verbose($"Found {mapNames.Length} CABMaps under Maps folder");
return mapNames;
}
public static void Clear()
@@ -48,34 +50,20 @@ namespace AssetStudio
tokenSource.Dispose();
tokenSource = new CancellationTokenSource();
GC.WaitForPendingFinalizers();
GC.Collect();
Logger.Verbose("Cleared AssetsHelper successfully !!");
}
public static void ClearOffsets() => Offsets.Clear();
public static void Remove(string path) => Offsets.Remove(path);
public static bool TryAdd(string name, out string path)
public static void ClearOffsets()
{
if (CABMap.TryGetValue(name, out var entry))
{
path = Path.Combine(BaseFolder, entry.Path);
if (!Offsets.ContainsKey(path))
{
Offsets.Add(path, new HashSet<long>());
}
Offsets[path].Add(entry.Offset);
return true;
}
path = string.Empty;
return false;
Offsets.Clear();
Logger.Verbose("Cleared cached offsets");
}
public static bool TryGet(string path, out long[] offsets)
{
if (Offsets.TryGetValue(path, out var list))
if (Offsets.TryGetValue(path, out var list) && list.Count > 0)
{
Logger.Verbose($"Found {list.Count} offsets for path {path}");
offsets = list.ToArray();
return true;
}
@@ -83,6 +71,67 @@ namespace AssetStudio
return false;
}
public static void AddCABOffsets(string[] paths, List<string> cabs)
{
for (int i = 0; i < cabs.Count; i++)
{
var cab = cabs[i];
if (CABMap.TryGetValue(cab, out var entry))
{
var fullPath = Path.Combine(BaseFolder, entry.Path);
Logger.Verbose($"Found {cab} in {fullPath}");
if (!paths.Contains(fullPath))
{
Offsets.TryAdd(fullPath, new HashSet<long>());
Offsets[fullPath].Add(entry.Offset);
Logger.Verbose($"Added {fullPath} to Offsets, at offset {entry.Offset}");
}
foreach (var dep in entry.Dependencies)
{
if (!cabs.Contains(dep))
cabs.Add(dep);
}
}
}
}
public static bool FindCAB(string path, out List<string> cabs)
{
var relativePath = Path.GetRelativePath(BaseFolder, path);
cabs = CABMap.AsParallel().Where(x => x.Value.Path.Equals(relativePath, StringComparison.OrdinalIgnoreCase)).Select(x => x.Key).Distinct().ToList();
Logger.Verbose($"Found {cabs.Count} that belongs to {relativePath}");
return cabs.Count != 0;
}
public static string[] ProcessFiles(string[] files)
{
foreach (var file in files)
{
Offsets.TryAdd(file, new HashSet<long>());
Logger.Verbose($"Added {file} to Offsets dictionary");
if (FindCAB(file, out var cabs))
{
AddCABOffsets(files, cabs);
}
}
Logger.Verbose($"Finished resolving dependncies, the original {files.Length} files will be loaded entirely, and the {Offsets.Count - files.Length} dependicnes will be loaded from cached offsets only");
return Offsets.Keys.ToArray();
}
public static string[] ProcessDependencies(string[] files)
{
if (CABMap.Count == 0)
{
Logger.Warning("CABMap is not build, skip resolving dependencies...");
}
else
{
Logger.Info("Resolving Dependencies...");
files = ProcessFiles(files);
}
return files;
}
public static void BuildCABMap(string[] files, string mapName, string baseFolder, Game game)
{
Logger.Info("Building CABMap...");
@@ -220,6 +269,7 @@ namespace AssetStudio
CABMap.Add(cab, entry);
}
}
Logger.Verbose($"Initialized CABMap with {CABMap.Count} entries");
Logger.Info($"Loaded {mapName} !!");
}
catch (Exception e)
@@ -342,7 +392,7 @@ namespace AssetStudio
case ClassIDType.Texture2D:
case ClassIDType.VideoClip:
case ClassIDType.AudioClip:
case ClassIDType.AnimationClip:
case ClassIDType.AnimationClip when AnimationClip.Parsable:
asset.Name = objectReader.ReadAlignedString();
break;
default:

View File

@@ -56,6 +56,8 @@ namespace AssetStudio
var path = Path.GetDirectoryName(Path.GetFullPath(files[0]));
MergeSplitAssets(path);
var toReadFile = ProcessingSplitFiles(files.ToList());
if (ResolveDependencies)
toReadFile = AssetsHelper.ProcessDependencies(toReadFile);
Load(toReadFile);
if (Silent)
@@ -89,6 +91,7 @@ namespace AssetStudio
{
foreach (var file in files)
{
Logger.Verbose($"caching {file} path and name to filter out duplicates");
importFiles.Add(file);
importFilesHash.Add(Path.GetFileName(file));
}
@@ -171,6 +174,7 @@ namespace AssetStudio
foreach (var sharedFile in assetsFile.m_Externals)
{
Logger.Verbose($"{assetsFile.fileName} needs external file {sharedFile.fileName}, attempting to look it up...");
var sharedFileName = sharedFile.fileName;
if (!importFilesHash.Contains(sharedFileName))
@@ -183,6 +187,7 @@ namespace AssetStudio
var findFiles = Directory.GetFiles(Path.GetDirectoryName(reader.FullPath), sharedFileName, SearchOption.AllDirectories);
if (findFiles.Length > 0)
{
Logger.Verbose($"Found {findFiles.Length} matching files, picking first file {findFiles[0]} !!");
sharedFilePath = findFiles[0];
}
}
@@ -193,6 +198,7 @@ namespace AssetStudio
}
else
{
Logger.Verbose("Nothing was found, caching into non existant files to avoid repeated searching !!");
noexistFiles.Add(sharedFilePath);
}
}
@@ -214,6 +220,7 @@ namespace AssetStudio
private void LoadAssetsFromMemory(FileReader reader, string originalPath, string unityVersion = null, long originalOffset = 0)
{
Logger.Verbose($"Loading asset file {reader.FileName} with version {unityVersion} from {originalPath} at offset 0x{originalOffset:X8}");
if (!assetsFileListHash.Contains(reader.FileName))
{
try
@@ -228,38 +235,6 @@ namespace AssetStudio
CheckStrippedVersion(assetsFile);
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
if (ResolveDependencies)
{
foreach (var sharedFile in assetsFile.m_Externals)
{
var sharedFileName = sharedFile.fileName;
if (!importFilesHash.Contains(sharedFileName))
{
var sharedFilePath = Path.Combine(Path.GetDirectoryName(originalPath), sharedFileName);
if (!noexistFiles.Contains(sharedFilePath))
{
if (AssetsHelper.TryAdd(sharedFileName, out var path))
{
sharedFilePath = path;
}
if (File.Exists(sharedFilePath))
{
if (!importFiles.Contains(sharedFilePath))
{
importFiles.Add(sharedFilePath);
}
importFilesHash.Add(sharedFileName);
}
else
{
noexistFiles.Add(sharedFilePath);
}
}
}
}
}
}
catch (Exception e)
{
@@ -290,6 +265,7 @@ namespace AssetStudio
}
else
{
Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = subReader; //TODO
}
}
@@ -335,6 +311,7 @@ namespace AssetStudio
LoadWebFile(subReader);
break;
case FileType.ResourceFile:
Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = subReader; //TODO
break;
}
@@ -358,8 +335,7 @@ namespace AssetStudio
using (ZipArchive archive = new ZipArchive(reader.BaseStream, ZipArchiveMode.Read))
{
List<string> splitFiles = new List<string>();
// register all files before parsing the assets so that the external references can be found
// and find split files
Logger.Verbose("Register all files before parsing the assets so that the external references can be found and find split files");
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.Name.Contains(".split"))
@@ -378,7 +354,7 @@ namespace AssetStudio
}
}
// merge split files and load the result
Logger.Verbose("Merge split files and load the result");
foreach (string basePath in splitFiles)
{
try
@@ -406,15 +382,14 @@ namespace AssetStudio
}
}
// load all entries
Logger.Verbose("Load all entries");
Logger.Verbose($"Found {archive.Entries.Count} entries");
foreach (ZipArchiveEntry entry in archive.Entries)
{
try
{
string dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), reader.FileName, entry.FullName);
// create a new stream
// - to store the deflated stream in
// - to keep the data for later extraction
Logger.Verbose("Create a new stream to store the deflated stream in and keep the data for later extraction");
Stream streamReader = new MemoryStream();
using (Stream entryStream = entry.Open())
{
@@ -429,6 +404,7 @@ namespace AssetStudio
entryReader.Position = 0;
if (!resourceFileReaders.ContainsKey(entry.Name))
{
Logger.Verbose("Caching resource file");
resourceFileReaders.Add(entry.Name, entryReader);
}
}
@@ -459,27 +435,14 @@ namespace AssetStudio
{
foreach (var offset in offsets)
{
var name = offset.ToString("X8");
Logger.Info($"Loading Block {name}");
stream.Offset = offset;
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
var subReader = new FileReader(dummyPath, stream, true);
LoadBundleFile(subReader, reader.FullPath, offset, false);
LoadBlockSubFile(reader.FullPath, stream, offset);
}
AssetsHelper.Remove(reader.FullPath);
}
else
{
do
{
var name = stream.AbsolutePosition.ToString("X8");
Logger.Info($"Loading Block {name}");
stream.Offset = stream.AbsolutePosition;
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
var subReader = new FileReader(dummyPath, stream, true);
LoadBundleFile(subReader, reader.FullPath, stream.AbsolutePosition, false);
LoadBlockSubFile(reader.FullPath, stream, stream.AbsolutePosition);
} while (stream.Remaining > 0);
}
}
@@ -492,55 +455,38 @@ namespace AssetStudio
reader.Dispose();
}
}
private void LoadBlockSubFile(string path, OffsetStream stream, long offset)
{
var name = offset.ToString("X8");
Logger.Info($"Loading Block {name}");
stream.Offset = offset;
var dummyPath = Path.Combine(Path.GetDirectoryName(path), name);
var subReader = new FileReader(dummyPath, stream, true);
LoadBundleFile(subReader, path, offset, false);
}
private void LoadBlkFile(FileReader reader)
{
Logger.Info("Loading " + reader.FullPath);
try
{
using var stream = BlkUtils.Decrypt(reader, (Blk)Game);
if (AssetsHelper.TryGet(reader.FullPath, out var offsets))
foreach (var offset in stream.GetOffsets(reader.FullPath))
{
foreach (var offset in offsets)
{
var name = offset.ToString("X8");
Logger.Info($"Loading Block {name}");
var name = offset.ToString("X8");
Logger.Info($"Loading Block {name}");
stream.Offset = offset;
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
var subReader = new FileReader(dummyPath, stream, true);
switch (subReader.FileType)
{
case FileType.BundleFile:
LoadBundleFile(subReader, reader.FullPath, offset, false);
break;
case FileType.Mhy0File:
LoadMhy0File(subReader, reader.FullPath, offset, false);
break;
}
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
var subReader = new FileReader(dummyPath, stream, true);
switch (subReader.FileType)
{
case FileType.BundleFile:
LoadBundleFile(subReader, reader.FullPath, offset, false);
break;
case FileType.Mhy0File:
LoadMhy0File(subReader, reader.FullPath, offset, false);
break;
}
AssetsHelper.Remove(reader.FullPath);
}
else
{
do
{
var name = stream.AbsolutePosition.ToString("X8");
Logger.Info($"Loading Block {name}");
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
var subReader = new FileReader(dummyPath, stream, true);
switch (subReader.FileType)
{
case FileType.BundleFile:
LoadBundleFile(subReader, reader.FullPath, stream.AbsolutePosition, false);
break;
case FileType.Mhy0File:
LoadMhy0File(subReader, reader.FullPath, stream.AbsolutePosition, false);
break;
}
stream.Offset = stream.AbsolutePosition;
} while (stream.Remaining > 0);
}
}
catch (InvalidCastException)
@@ -555,7 +501,7 @@ namespace AssetStudio
{
reader.Dispose();
}
}
}
private void LoadMhy0File(FileReader reader, string originalPath = null, long originalOffset = 0, bool log = true)
{
if (log)
@@ -565,6 +511,7 @@ namespace AssetStudio
try
{
var mhy0File = new Mhy0File(reader, reader.FullPath, (Mhy0)Game);
Logger.Verbose($"mhy0 total size: {mhy0File.TotalSize:X8}");
foreach (var file in mhy0File.fileList)
{
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
@@ -575,6 +522,7 @@ namespace AssetStudio
}
else
{
Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = cabReader; //TODO
}
}
@@ -612,6 +560,8 @@ namespace AssetStudio
public void Clear()
{
Logger.Verbose("Cleaning up...");
foreach (var assetsFile in assetsFileList)
{
assetsFile.Objects.Clear();
@@ -653,103 +603,40 @@ namespace AssetStudio
var objectReader = new ObjectReader(assetsFile.reader, assetsFile, objectInfo, Game);
try
{
Object obj;
switch (objectReader.type)
Object obj = objectReader.type switch
{
case ClassIDType.Animation:
obj = new Animation(objectReader);
break;
case ClassIDType.AnimationClip:
obj = new AnimationClip(objectReader);
break;
case ClassIDType.Animator:
obj = new Animator(objectReader);
break;
case ClassIDType.AnimatorController:
obj = new AnimatorController(objectReader);
break;
case ClassIDType.AnimatorOverrideController:
obj = new AnimatorOverrideController(objectReader);
break;
case ClassIDType.AssetBundle:
obj = new AssetBundle(objectReader);
break;
case ClassIDType.AudioClip:
obj = new AudioClip(objectReader);
break;
case ClassIDType.Avatar:
obj = new Avatar(objectReader);
break;
case ClassIDType.Font:
obj = new Font(objectReader);
break;
case ClassIDType.GameObject:
obj = new GameObject(objectReader);
break;
case ClassIDType.IndexObject:
obj = new IndexObject(objectReader);
break;
case ClassIDType.Material:
obj = new Material(objectReader);
break;
case ClassIDType.Mesh:
obj = new Mesh(objectReader);
break;
case ClassIDType.MeshFilter:
obj = new MeshFilter(objectReader);
break;
case ClassIDType.MeshRenderer when Renderer.Parsable:
obj = new MeshRenderer(objectReader);
break;
case ClassIDType.MiHoYoBinData:
obj = new MiHoYoBinData(objectReader);
break;
case ClassIDType.MonoBehaviour:
obj = new MonoBehaviour(objectReader);
break;
case ClassIDType.MonoScript:
obj = new MonoScript(objectReader);
break;
case ClassIDType.MovieTexture:
obj = new MovieTexture(objectReader);
break;
case ClassIDType.PlayerSettings:
obj = new PlayerSettings(objectReader);
break;
case ClassIDType.RectTransform:
obj = new RectTransform(objectReader);
break;
case ClassIDType.Shader when Shader.Parsable:
obj = new Shader(objectReader);
break;
case ClassIDType.SkinnedMeshRenderer when Renderer.Parsable:
obj = new SkinnedMeshRenderer(objectReader);
break;
case ClassIDType.Sprite:
obj = new Sprite(objectReader);
break;
case ClassIDType.SpriteAtlas:
obj = new SpriteAtlas(objectReader);
break;
case ClassIDType.TextAsset:
obj = new TextAsset(objectReader);
break;
case ClassIDType.Texture2D:
obj = new Texture2D(objectReader);
break;
case ClassIDType.Transform:
obj = new Transform(objectReader);
break;
case ClassIDType.VideoClip:
obj = new VideoClip(objectReader);
break;
case ClassIDType.ResourceManager:
obj = new ResourceManager(objectReader);
break;
default:
obj = new Object(objectReader);
break;
}
ClassIDType.Animation => new Animation(objectReader),
ClassIDType.AnimationClip when AnimationClip.Parsable => new AnimationClip(objectReader),
ClassIDType.Animator => new Animator(objectReader),
ClassIDType.AnimatorController => new AnimatorController(objectReader),
ClassIDType.AnimatorOverrideController => new AnimatorOverrideController(objectReader),
ClassIDType.AssetBundle => new AssetBundle(objectReader),
ClassIDType.AudioClip => new AudioClip(objectReader),
ClassIDType.Avatar => new Avatar(objectReader),
ClassIDType.Font => new Font(objectReader),
ClassIDType.GameObject => new GameObject(objectReader),
ClassIDType.IndexObject => new IndexObject(objectReader),
ClassIDType.Material => new Material(objectReader),
ClassIDType.Mesh => new Mesh(objectReader),
ClassIDType.MeshFilter => new MeshFilter(objectReader),
ClassIDType.MeshRenderer when Renderer.Parsable => new MeshRenderer(objectReader),
ClassIDType.MiHoYoBinData => new MiHoYoBinData(objectReader),
ClassIDType.MonoBehaviour => new MonoBehaviour(objectReader),
ClassIDType.MonoScript => new MonoScript(objectReader),
ClassIDType.MovieTexture => new MovieTexture(objectReader),
ClassIDType.PlayerSettings => new PlayerSettings(objectReader),
ClassIDType.RectTransform => new RectTransform(objectReader),
ClassIDType.Shader when Shader.Parsable => new Shader(objectReader),
ClassIDType.SkinnedMeshRenderer when Renderer.Parsable => new SkinnedMeshRenderer(objectReader),
ClassIDType.Sprite => new Sprite(objectReader),
ClassIDType.SpriteAtlas => new SpriteAtlas(objectReader),
ClassIDType.TextAsset => new TextAsset(objectReader),
ClassIDType.Texture2D => new Texture2D(objectReader),
ClassIDType.Transform => new Transform(objectReader),
ClassIDType.VideoClip => new VideoClip(objectReader),
ClassIDType.ResourceManager => new ResourceManager(objectReader),
_ => new Object(objectReader),
};
assetsFile.AddObject(obj);
}
catch (Exception e)
@@ -784,57 +671,67 @@ namespace AssetStudio
}
if (obj is GameObject m_GameObject)
{
foreach (var pptr in m_GameObject.m_Components)
{
Logger.Verbose($"GameObject with {m_GameObject.m_PathID} in file {m_GameObject.assetsFile.fileName} has {m_GameObject.m_Components.Length} components, Attempting to fetch them...");
foreach (var pptr in m_GameObject.m_Components)
{
if (pptr.TryGet(out var m_Component))
{
{
switch (m_Component)
{
{
case Transform m_Transform:
Logger.Verbose($"Fetched Transform component with {m_Transform.m_PathID} in file {m_Transform.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Transform = m_Transform;
break;
case MeshRenderer m_MeshRenderer:
Logger.Verbose($"Fetched MeshRenderer component with {m_MeshRenderer.m_PathID} in file {m_MeshRenderer.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_MeshRenderer = m_MeshRenderer;
break;
case MeshFilter m_MeshFilter:
Logger.Verbose($"Fetched MeshFilter component with {m_MeshFilter.m_PathID} in file {m_MeshFilter.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_MeshFilter = m_MeshFilter;
break;
case SkinnedMeshRenderer m_SkinnedMeshRenderer:
Logger.Verbose($"Fetched SkinnedMeshRenderer component with {m_SkinnedMeshRenderer.m_PathID} in file {m_SkinnedMeshRenderer.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_SkinnedMeshRenderer = m_SkinnedMeshRenderer;
break;
case Animator m_Animator:
Logger.Verbose($"Fetched Animator component with {m_Animator.m_PathID} in file {m_Animator.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Animator = m_Animator;
break;
case Animation m_Animation:
Logger.Verbose($"Fetched Animation component with {m_Animation.m_PathID} in file {m_Animation.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Animation = m_Animation;
break;
}
}
}
}
}
else if (obj is SpriteAtlas m_SpriteAtlas)
{
if (m_SpriteAtlas.m_RenderDataMap.Count > 0)
if (m_SpriteAtlas.m_RenderDataMap.Count > 0)
{
Logger.Verbose($"SpriteAtlas with {m_SpriteAtlas.m_PathID} in file {m_SpriteAtlas.assetsFile.fileName} has {m_SpriteAtlas.m_PackedSprites.Length} packed sprites, Attempting to fetch them...");
foreach (var m_PackedSprite in m_SpriteAtlas.m_PackedSprites)
{
foreach (var m_PackedSprite in m_SpriteAtlas.m_PackedSprites)
{
if (m_PackedSprite.TryGet(out var m_Sprite))
{
if (m_Sprite.m_SpriteAtlas.IsNull)
{
Logger.Verbose($"Fetched Sprite with {m_Sprite.m_PathID} in file {m_Sprite.assetsFile.fileName}, assigning to parent SpriteAtlas...");
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
else
{
if (m_Sprite.m_SpriteAtlas.IsNull)
{
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
else
{
m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld);
if (m_SpriteAtlaOld.m_IsVariant)
{
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
if (m_SpriteAtlaOld.m_IsVariant)
{
Logger.Verbose($"Fetched Sprite with {m_Sprite.m_PathID} in file {m_Sprite.assetsFile.fileName} has a variant of the origianl SpriteAtlas, disposing of the variant and assinging to the parent SpriteAtlas...");
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
}
}
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Data;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
namespace AssetStudio
{
@@ -49,6 +50,20 @@ namespace AssetStudio
public uint compressedBlocksInfoSize;
public uint uncompressedBlocksInfoSize;
public ArchiveFlags flags;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"signature: {signature} | ");
sb.Append($"version: {version} | ");
sb.Append($"unityVersion: {unityVersion} | ");
sb.Append($"unityRevision: {unityRevision} | ");
sb.Append($"size: 0x{size:X8} | ");
sb.Append($"compressedBlocksInfoSize: 0x{compressedBlocksInfoSize:X8} | ");
sb.Append($"uncompressedBlocksInfoSize: 0x{uncompressedBlocksInfoSize:X8} | ");
sb.Append($"flags: 0x{(int)flags:X8}");
return sb.ToString();
}
}
public class StorageBlock
@@ -56,6 +71,15 @@ namespace AssetStudio
public uint compressedSize;
public uint uncompressedSize;
public StorageBlockFlags flags;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"compressedSize: 0x{compressedSize:X8} | ");
sb.Append($"uncompressedSize: 0x{uncompressedSize:X8} | ");
sb.Append($"flags: 0x{(int)flags:X8}");
return sb.ToString();
}
}
public class Node
@@ -64,6 +88,16 @@ namespace AssetStudio
public long size;
public uint flags;
public string path;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"offset: 0x{offset:X8} | ");
sb.Append($"size: 0x{size:X8} | ");
sb.Append($"flags: {flags} | ");
sb.Append($"path: {path}");
return sb.ToString();
}
}
private Game Game;
@@ -121,6 +155,7 @@ namespace AssetStudio
{
Header header = new Header();
header.signature = reader.ReadStringToNull(20);
Logger.Verbose($"Parsed signature {header.signature}");
switch (header.signature)
{
case "UnityFS":
@@ -129,6 +164,7 @@ namespace AssetStudio
var version = reader.ReadUInt32();
if (version > 11)
{
Logger.Verbose($"Encrypted bundle header with key {version}");
XORShift128.InitSeed(version);
header.version = 6;
header.unityVersion = "5.x.x";
@@ -205,6 +241,7 @@ namespace AssetStudio
{
Stream blocksStream;
var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize);
Logger.Verbose($"Total size of decompressed blocks: {uncompressedSizeSum}");
if (uncompressedSizeSum >= int.MaxValue)
{
/*var memoryMappedFile = MemoryMappedFile.CreateNew(null, uncompressedSizeSum);
@@ -220,19 +257,17 @@ namespace AssetStudio
private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream)
{
Logger.Verbose($"Writing block and directory to blocks stream...");
var isCompressed = m_Header.signature == "UnityWeb";
foreach (var blockInfo in m_BlocksInfo)
{
var uncompressedBytes = reader.ReadBytes((int)blockInfo.compressedSize);
if (isCompressed)
{
using (var memoryStream = new MemoryStream(uncompressedBytes))
{
using (var decompressStream = SevenZipHelper.StreamDecompress(memoryStream))
{
uncompressedBytes = decompressStream.ToArray();
}
}
using var memoryStream = new MemoryStream(uncompressedBytes);
using var decompressStream = SevenZipHelper.StreamDecompress(memoryStream);
uncompressedBytes = decompressStream.ToArray();
}
blocksStream.Write(uncompressedBytes, 0, uncompressedBytes.Length);
}
@@ -240,6 +275,7 @@ namespace AssetStudio
var blocksReader = new EndianBinaryReader(blocksStream);
var nodesCount = blocksReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount];
Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new Node
@@ -253,6 +289,8 @@ namespace AssetStudio
public void ReadFiles(Stream blocksStream, string path)
{
Logger.Verbose($"Writing files from blocks stream...");
fileList = new StreamFile[m_DirectoryInfo.Length];
for (int i = 0; i < m_DirectoryInfo.Length; i++)
{
@@ -286,6 +324,7 @@ namespace AssetStudio
m_Header.uncompressedBlocksInfoSize ^= XORShift128.NextDecryptUInt();
m_Header.compressedBlocksInfoSize ^= XORShift128.NextDecryptUInt();
XORShift128.Init = false;
Logger.Verbose($"Bundle header decrypted");
}
private void ReadHeader(FileReader reader)
@@ -317,10 +356,13 @@ namespace AssetStudio
m_Header.compressedBlocksInfoSize -= 0xCA;
m_Header.uncompressedBlocksInfoSize -= 0xCA;
}
Logger.Verbose($"Bundle header Info: {m_Header}");
}
private void ReadUnityCN(EndianBinaryReader reader)
private void ReadUnityCN(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with UnityCN encryption");
ArchiveFlags mask;
var version = ParseVersion();
@@ -339,8 +381,11 @@ namespace AssetStudio
HasBlockInfoNeedPaddingAtStart = true;
}
Logger.Verbose($"Mask set to 0x{mask:X8}");
if ((m_Header.flags & mask) != 0)
{
Logger.Verbose($"Encryption flag exist, file is encrypted, attempting to decrypt");
UnityCN = new UnityCN(reader);
}
}
@@ -367,6 +412,7 @@ namespace AssetStudio
var blocksInfoBytesSpan = blocksInfoBytes.AsSpan();
var uncompressedSize = m_Header.uncompressedBlocksInfoSize;
var compressionType = (CompressionType)(m_Header.flags & ArchiveFlags.CompressionTypeMask);
Logger.Verbose($"BlockInfo compression type: {compressionType}");
switch (compressionType) //kArchiveCompressionTypeMask
{
case CompressionType.None: //None
@@ -399,6 +445,7 @@ namespace AssetStudio
case CompressionType.Lz4Mr0k: //Lz4Mr0k
if (Mr0kUtils.IsMr0k(blocksInfoBytesSpan))
{
Logger.Verbose($"Header encrypted with mr0k, decrypting...");
blocksInfoBytesSpan = Mr0kUtils.Decrypt(blocksInfoBytesSpan, (Mr0k)Game).ToArray();
}
goto case CompressionType.Lz4HC;
@@ -413,6 +460,7 @@ namespace AssetStudio
}
var blocksInfoCount = blocksInfoReader.ReadInt32();
m_BlocksInfo = new StorageBlock[blocksInfoCount];
Logger.Verbose($"Blocks count: {blocksInfoCount}");
for (int i = 0; i < blocksInfoCount; i++)
{
m_BlocksInfo[i] = new StorageBlock
@@ -421,10 +469,13 @@ namespace AssetStudio
compressedSize = blocksInfoReader.ReadUInt32(),
flags = (StorageBlockFlags)blocksInfoReader.ReadUInt16()
};
Logger.Verbose($"Block {i} Info: {m_BlocksInfo[i]}");
}
var nodesCount = blocksInfoReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount];
Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new Node
@@ -434,6 +485,8 @@ namespace AssetStudio
flags = blocksInfoReader.ReadUInt32(),
path = blocksInfoReader.ReadStringToNull(),
};
Logger.Verbose($"Directory {i} Info: {m_DirectoryInfo[i]}");
}
}
if (HasBlockInfoNeedPaddingAtStart && (m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
@@ -444,10 +497,14 @@ namespace AssetStudio
private void ReadBlocks(FileReader reader, Stream blocksStream)
{
Logger.Verbose($"Writing block to blocks stream...");
for (int i = 0; i < m_BlocksInfo.Length; i++)
{
Logger.Verbose($"Reading block {i}...");
var blockInfo = m_BlocksInfo[i];
var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask);
Logger.Verbose($"Block compression type {compressionType}");
switch (compressionType) //kStorageBlockCompressionTypeMask
{
case CompressionType.None: //None
@@ -470,10 +527,12 @@ namespace AssetStudio
var compressedBytesSpan = compressedBytes.AsSpan(0, compressedSize);
if (compressionType == CompressionType.Lz4Mr0k && Mr0kUtils.IsMr0k(compressedBytes))
{
Logger.Verbose($"Block encrypted with mr0k, decrypting...");
compressedBytesSpan = Mr0kUtils.Decrypt(compressedBytesSpan, (Mr0k)Game);
}
if (Game.Type.IsUnityCN() && ((int)blockInfo.flags & 0x100) != 0)
{
Logger.Verbose($"Decrypting block with UnityCN...");
UnityCN.DecryptBlock(compressedBytes, compressedSize, i);
}
if (Game.Type.IsNetEase() && i == 0)

View File

@@ -965,6 +965,8 @@ namespace AssetStudio
public sealed class AnimationClip : NamedObject
{
public static bool Parsable;
public AnimationType m_AnimationType;
public bool m_Legacy;
public bool m_Compressed;

View File

@@ -37,6 +37,8 @@ namespace AssetStudio
serializedType = reader.serializedType;
byteSize = reader.byteSize;
Logger.Verbose($"Attempting to read object {type} with {m_PathID} in file {assetsFile.fileName}, starting from offset 0x{reader.byteStart:X8} with expected size of 0x{byteSize:X8} !!");
if (platform == BuildTarget.NoTarget)
{
var m_ObjectHideFlags = reader.ReadUInt32();
@@ -81,6 +83,7 @@ namespace AssetStudio
public byte[] GetRawData()
{
Logger.Verbose($"Dumping raw bytes of the object with {m_PathID} in file {assetsFile.fileName}...");
reader.Reset();
return reader.ReadBytes((int)byteSize);
}

View File

@@ -105,7 +105,7 @@ namespace AssetStudio
var m_ReadAllowed = reader.ReadBoolean();
}
}
if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2) || reader.Game.Type.IsGI()) //2018.2 and up
if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2)) //2018.2 and up
{
var m_StreamingMipmaps = reader.ReadBoolean();
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
namespace AssetStudio
{
@@ -8,16 +10,20 @@ namespace AssetStudio
private const int DataOffset = 0x2A;
private const int KeySize = 0x1000;
private const int SeedBlockSize = 0x800;
private const int BufferSize = 0x10000;
public static XORStream Decrypt(FileReader reader, Blk blk)
{
reader.Endian = EndianType.LittleEndian;
var signature = reader.ReadStringToNull();
Logger.Verbose($"Signature: {signature}");
var count = reader.ReadInt32();
Logger.Verbose($"Key size: {count}");
var key = reader.ReadBytes(count);
reader.Position += count;
var seedSize = Math.Min(reader.ReadInt16(), blk.SBox.IsNullOrEmpty() ? SeedBlockSize : SeedBlockSize * 2);
Logger.Verbose($"Seed size: 0x{seedSize:X8}");
if (!blk.SBox.IsNullOrEmpty() && blk.Type.IsGI())
{
@@ -47,6 +53,8 @@ namespace AssetStudio
var keyHigh = BinaryPrimitives.ReadUInt64LittleEndian(key.AsSpan(8, 8));
var seed = keyLow ^ keyHigh ^ keySeed ^ blk.InitSeed;
Logger.Verbose($"Seed: 0x{seed:X8}");
var mt64 = new MT19937_64(seed);
var xorpad = new byte[KeySize];
for (int i = 0; i < KeySize; i += 8)
@@ -56,5 +64,48 @@ namespace AssetStudio
return new XORStream(reader.BaseStream, DataOffset, xorpad);
}
public static IEnumerable<long> GetOffsets(this XORStream stream, string path)
{
if (AssetsHelper.TryGet(path, out var offsets))
{
foreach(var offset in offsets)
{
stream.Offset = offset;
yield return offset;
}
}
else
{
using var reader = new FileReader(path, stream, true);
var signature = reader.FileType switch
{
FileType.BundleFile => "UnityFS\x00",
FileType.Mhy0File => "mhy0",
_ => throw new InvalidOperationException()
};
Logger.Verbose($"Prased signature: {signature}");
var signatureBytes = Encoding.UTF8.GetBytes(signature);
var buffer = BigArrayPool<byte>.Shared.Rent(BufferSize);
while (stream.Remaining > 0)
{
var index = 0;
var absOffset = stream.AbsolutePosition;
var read = stream.Read(buffer);
while (index < read)
{
index = buffer.AsSpan(0, read).Search(signatureBytes, index);
if (index == -1) break;
var offset = absOffset + index;
stream.Offset = offset;
yield return offset;
index++;
}
}
BigArrayPool<byte>.Shared.Return(buffer);
}
}
}
}

View File

@@ -21,6 +21,7 @@ namespace AssetStudio
var encryptedBlockSize = Math.Min(0x10 * ((data.Length - 0x94) >> 7), BlockSize);
Logger.Verbose($"Encrypted block size: {encryptedBlockSize}");
if (!mr0k.InitVector.IsNullOrEmpty())
{
for (int i = 0; i < mr0k.InitVector.Length; i++)
@@ -47,6 +48,8 @@ namespace AssetStudio
var seed2 = BinaryPrimitives.ReadUInt64LittleEndian(key3);
var seed = seed2 ^ seed1 ^ (seed1 + (uint)data.Length - 20);
Logger.Verbose($"Seed: 0x{seed:X8}");
var encryptedBlock = data.Slice(0x94, encryptedBlockSize);
var seedSpan = BitConverter.GetBytes(seed);
for (var i = 0; i < encryptedBlockSize; i++)

View File

@@ -11,6 +11,8 @@ namespace AssetStudio
private static readonly byte[] Signature = new byte[] { 0xEE, 0xDD };
public static void Decrypt(Span<byte> bytes)
{
Logger.Verbose($"Attempting to decrypt block with NetEase encryption...");
var (encryptedOffset, encryptedSize) = ReadHeader(bytes);
var encrypted = bytes.Slice(encryptedOffset, encryptedSize);
var encryptedInts = MemoryMarshal.Cast<byte, int>(encrypted);
@@ -77,7 +79,7 @@ namespace AssetStudio
}
private static (int, int) ReadHeader(Span<byte> bytes)
{
var index = bytes.Search(Signature, 0);
var index = bytes.Search(Signature);
if (index == -1 || index >= 0x40)
{
throw new Exception("Header not found !!");
@@ -127,6 +129,7 @@ namespace AssetStudio
throw new Exception("Unsupported version");
}
var versionString = version.ToString("X4");
Logger.Verbose($"Bundle version: {versionString}");
Encoding.UTF8.GetBytes(versionString, bytes);
}

View File

@@ -12,6 +12,7 @@ namespace AssetStudio
public static void Decrypt(Span<byte> data, string path)
{
Logger.Verbose($"Attempting to decrypt block with OPFP encryption...");
if (IsEncryptionBundle(path, out var key, out var version))
{
switch (version)
@@ -41,30 +42,39 @@ namespace AssetStudio
{
if (V1_Prefixes.Any(prefix => relativePath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
Logger.Verbose("Path matches with V1 prefixes, generatring key...");
key = (byte)Path.GetFileName(relativePath).Length;
version = 1;
Logger.Verbose($"version: {version}, key: {key}");
return true;
}
else if (V0_Prefixes.Any(prefix => relativePath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
Logger.Verbose("Path matches with V2 prefixes, generatring key...");
key = (byte)relativePath.Length;
version = 0;
Logger.Verbose($"version: {version}, key: {key}");
return true;
}
}
Logger.Verbose($"Unknown encryption type");
key = 0x00;
version = 0;
return false;
}
private static bool IsFixedPath(string path, out string fixedPath)
{
Logger.Verbose($"Fixing path before checking...");
var dirs = path.Split(Path.DirectorySeparatorChar);
if (dirs.Contains(BaseFolder))
{
var idx = Array.IndexOf(dirs, BaseFolder);
Logger.Verbose($"Seperator found at index {idx}");
fixedPath = string.Join(Path.DirectorySeparatorChar, dirs[(idx+1)..]).Replace("\\", "/");
return true;
}
Logger.Verbose($"Unknown path");
fixedPath = string.Empty;
return false;
}

View File

@@ -28,8 +28,11 @@ namespace AssetStudio
DecryptKey(signatureKey, signatureBytes);
var str = Encoding.UTF8.GetString(signatureBytes);
Logger.Verbose($"Decrypted signature is {str}");
if (str != Signature)
throw new Exception("Invalid Signature !!");
{
throw new Exception($"Invalid Signature, Expected {Signature} but found {str} instead");
}
DecryptKey(infoKey, infoBytes);
@@ -41,19 +44,20 @@ namespace AssetStudio
var idx = (i % 4 * 4) + (i / 4);
Sub[idx] = subBytes[i];
}
}
public static bool SetKey(Entry entry)
{
Logger.Verbose($"Initializing decryptor with key {entry.Key}");
try
{
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.ECB;
aes.Key = Convert.FromHexString(entry.Key);
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Key = Convert.FromHexString(entry.Key);
Encryptor = aes.CreateEncryptor();
}
Encryptor = aes.CreateEncryptor();
Logger.Verbose($"Decryptor initialized !!");
}
catch (Exception e)
{

View File

@@ -19,7 +19,7 @@ namespace AssetStudio
return buffer;
}
public static int Search(this byte[] src, string value, int offset = 0) => Search(src.AsSpan(), Encoding.UTF8.GetBytes(value), offset);
public static int Search(this Span<byte> src, byte[] pattern, int offset)
public static int Search(this Span<byte> src, byte[] pattern, int offset = 0)
{
int maxFirstCharSlot = src.Length - pattern.Length + 1;
for (int i = offset; i < maxFirstCharSlot; i++)

View File

@@ -13,5 +13,15 @@ namespace AssetStudio
//custom
public string fileName;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"Guid: {guid} | ");
sb.Append($"type: {type} | ");
sb.Append($"pathName: {pathName} | ");
sb.Append($"fileName: {fileName}");
return sb.ToString();
}
}
}

View File

@@ -27,12 +27,14 @@ namespace AssetStudio
FullPath = Path.GetFullPath(path);
FileName = Path.GetFileName(path);
FileType = CheckFileType();
Logger.Verbose($"File {path} type is {FileType}");
}
private FileType CheckFileType()
{
var signature = this.ReadStringToNull(20);
Position = 0;
Logger.Verbose($"Parsed signature is {signature}");
switch (signature)
{
case "ENCR":
@@ -47,46 +49,59 @@ namespace AssetStudio
return FileType.BlkFile;
default:
{
Logger.Verbose("signature does not match any of the supported string signatures, attempting to check bytes signatures");
byte[] magic = ReadBytes(2);
Position = 0;
Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (gzipMagic.SequenceEqual(magic))
{
return FileType.GZipFile;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(gzipMagic)}");
Position = 0x20;
magic = ReadBytes(6);
Position = 0;
Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (brotliMagic.SequenceEqual(magic))
{
return FileType.BrotliFile;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(brotliMagic)}");
if (IsSerializedFile())
{
return FileType.AssetsFile;
}
magic = ReadBytes(4);
Position = 0;
Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (zipMagic.SequenceEqual(magic) || zipSpannedMagic.SequenceEqual(magic))
{
return FileType.ZipFile;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(zipMagic)} or {Convert.ToHexString(zipSpannedMagic)}");
if (mhy0Magic.SequenceEqual(magic))
{
return FileType.Mhy0File;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(mhy0Magic)}");
magic = ReadBytes(7);
Position = 0;
Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (narakaMagic.SequenceEqual(magic))
{
return FileType.BundleFile;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(narakaMagic)}");
magic = ReadBytes(9);
Position = 0;
Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (gunfireMagic.SequenceEqual(magic))
{
Position = 0x32;
return FileType.BundleFile;
}
Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(gunfireMagic)}");
Logger.Verbose($"Parsed signature does not match any of the supported signatures, assuming resource file");
return FileType.ResourceFile;
}
}
@@ -94,9 +109,12 @@ namespace AssetStudio
private bool IsSerializedFile()
{
Logger.Verbose($"Attempting to check if the file is serialized file...");
var fileSize = BaseStream.Length;
if (fileSize < 20)
{
Logger.Verbose($"File size 0x{fileSize:X8} is too small, minimal acceptable size is 0x14, aborting...");
return false;
}
var m_MetadataSize = ReadUInt32();
@@ -109,6 +127,7 @@ namespace AssetStudio
{
if (fileSize < 48)
{
Logger.Verbose($"File size 0x{fileSize:X8} for version {m_Version} is too small, minimal acceptable size is 0x30, aborting...");
Position = 0;
return false;
}
@@ -119,12 +138,15 @@ namespace AssetStudio
Position = 0;
if (m_FileSize != fileSize)
{
Logger.Verbose($"Parsed file size 0x{m_FileSize:X8} does not match stream size {fileSize}, file might be corrupted, aborting...");
return false;
}
if (m_DataOffset > fileSize)
{
Logger.Verbose($"Parsed data offset 0x{m_DataOffset:X8} is outside the stream of the size {fileSize}, file might be corrupted, aborting...");
return false;
}
Logger.Verbose($"Valid serialized file !!");
return true;
}
}
@@ -133,8 +155,10 @@ namespace AssetStudio
{
public static FileReader PreProcessing(this FileReader reader, Game game)
{
Logger.Verbose($"Applying preprocessing to file {reader.FileName}");
if (reader.FileType == FileType.ResourceFile || !game.Type.IsNormal())
{
Logger.Verbose("File is encrypted !!");
switch (game.Type)
{
case GameType.GI_Pack:
@@ -147,16 +171,13 @@ namespace AssetStudio
reader = DecryptEnsembleStar(reader);
break;
case GameType.OPFP:
reader = ParseOPFP(reader);
break;
case GameType.FakeHeader:
case GameType.ShiningNikki:
reader = ParseFakeHeader(reader);
break;
case GameType.FantasyOfWind:
reader = DecryptFantasyOfWind(reader);
break;
case GameType.ShiningNikki:
reader = ParseShiningNikki(reader);
break;
case GameType.HelixWaltz2:
reader = ParseHelixWaltz2(reader);
@@ -174,6 +195,7 @@ namespace AssetStudio
}
if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile())
{
Logger.Verbose("File might have multiple bundles !!");
try
{
var signature = reader.ReadStringToNull();
@@ -183,6 +205,8 @@ namespace AssetStudio
var size = reader.ReadInt64();
if (!(signature == "UnityFS" && size == reader.BaseStream.Length))
{
Logger.Verbose($"Found signature UnityFS, expected bundle size is 0x{size:X8}, found 0x{reader.BaseStream.Length} instead !!");
Logger.Verbose("Loading as block file !!");
reader.FileType = FileType.BlockFile;
}
}
@@ -190,6 +214,7 @@ namespace AssetStudio
reader.Position = 0;
}
Logger.Verbose("No preprocessing is needed");
return reader;
}
}

View File

@@ -17,7 +17,7 @@ namespace AssetStudio
public class ImportedFrame
{
public string Name { get; set; }
public Vector3 LocalRotation { get; set; }
public Quaternion LocalRotation { get; set; }
public Vector3 LocalPosition { get; set; }
public Vector3 LocalScale { get; set; }
public ImportedFrame Parent { get; set; }
@@ -248,7 +248,7 @@ namespace AssetStudio
{
public string Path { get; set; }
public List<ImportedKeyframe<Vector3>> Scalings = new List<ImportedKeyframe<Vector3>>();
public List<ImportedKeyframe<Vector3>> Rotations = new List<ImportedKeyframe<Vector3>>();
public List<ImportedKeyframe<Quaternion>> Rotations = new List<ImportedKeyframe<Quaternion>>();
public List<ImportedKeyframe<Vector3>> Translations = new List<ImportedKeyframe<Vector3>>();
public ImportedBlendShape BlendShape;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -34,4 +35,39 @@ namespace AssetStudio
Console.WriteLine("[{0}] {1}", loggerEvent, message);
}
}
public sealed class FileLogger : ILogger
{
private const string LogFileName = "log.txt";
private const string PrevLogFileName = "log_prev.txt";
private readonly object LockWriter = new object();
public StreamWriter Writer;
public FileLogger()
{
var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogFileName);
var prevLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PrevLogFileName);
if (File.Exists(logPath))
{
File.Move(logPath, prevLogPath, true);
}
Writer = new StreamWriter(logPath, true) { AutoFlush = true };
}
~FileLogger()
{
Writer?.Dispose();
}
public void Log(LoggerEvent loggerEvent, string message, bool silent = false)
{
if (silent)
return;
lock (LockWriter)
{
Writer.WriteLine($"[{DateTime.Now}][{loggerEvent}] {message}");
}
}
}
}

View File

@@ -18,7 +18,9 @@ namespace AssetStudio
{
public static void MergeSplitAssets(string path, bool allDirectories = false)
{
Logger.Verbose($"Processing split assets (.splitX) prior to loading files...");
var splitFiles = Directory.GetFiles(path, "*.split0", allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Logger.Verbose($"Found {splitFiles.Length} split files, attempting to merge...");
foreach (var splitFile in splitFiles)
{
var destFile = Path.GetFileNameWithoutExtension(splitFile);
@@ -27,6 +29,7 @@ namespace AssetStudio
if (!File.Exists(destFull))
{
var splitParts = Directory.GetFiles(destPath, destFile + ".split*");
Logger.Verbose($"Creating {destFull} where split files will be combined");
using (var destStream = File.Create(destFull))
{
for (int i = 0; i < splitParts.Length; i++)
@@ -35,6 +38,7 @@ namespace AssetStudio
using (var sourceStream = File.OpenRead(splitPart))
{
sourceStream.CopyTo(destStream);
Logger.Verbose($"{splitPart} has been combined into {destFull}");
}
}
}
@@ -44,6 +48,7 @@ namespace AssetStudio
public static string[] ProcessingSplitFiles(List<string> selectFile)
{
Logger.Verbose("Filter out paths that has .split and has the same name");
var splitFiles = selectFile.Where(x => x.Contains(".split"))
.Select(x => Path.Combine(Path.GetDirectoryName(x), Path.GetFileNameWithoutExtension(x)))
.Distinct()
@@ -61,6 +66,7 @@ namespace AssetStudio
public static FileReader DecompressGZip(FileReader reader)
{
Logger.Verbose($"Decompressing GZip file {reader.FileName} into memory");
using (reader)
{
var stream = new MemoryStream();
@@ -75,6 +81,7 @@ namespace AssetStudio
public static FileReader DecompressBrotli(FileReader reader)
{
Logger.Verbose($"Decompressing Brotli file {reader.FileName} into memory");
using (reader)
{
var stream = new MemoryStream();
@@ -89,24 +96,31 @@ namespace AssetStudio
public static FileReader DecryptPack(FileReader reader, Game game)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Pack encryption");
const int PackSize = 0x880;
const string PackSignature = "pack";
const string UnityFSSignature = "UnityFS";
var data = reader.ReadBytes((int)reader.Length);
var idx = data.Search(PackSignature);
if (idx == -1)
var packIdx = data.Search(PackSignature);
if (packIdx == -1)
{
Logger.Verbose($"Signature {PackSignature} was not found, aborting...");
reader.Position = 0;
return reader;
}
idx = data.Search("mr0k", idx);
if (idx == -1)
Logger.Verbose($"Found signature {PackSignature} at offset 0x{packIdx:X8}");
var mr0kIdx = data.Search("mr0k", packIdx);
if (mr0kIdx == -1)
{
Logger.Verbose("Signature mr0k was not found, aborting...");
reader.Position = 0;
return reader;
}
Logger.Verbose($"Found signature mr0k signature at offset 0x{mr0kIdx:X8}");
Logger.Verbose("Attempting to process pack chunks...");
var ms = new MemoryStream();
try
{
@@ -121,9 +135,12 @@ namespace AssetStudio
var signature = reader.ReadStringToNull(4);
if (signature == PackSignature)
{
Logger.Verbose($"Found {PackSignature} chunk at position {reader.Position - PackSignature.Length}");
var isMr0k = reader.ReadBoolean();
Logger.Verbose("Chunk is mr0k encrypted");
var blockSize = BinaryPrimitives.ReadInt32LittleEndian(reader.ReadBytes(4));
Logger.Verbose($"Chunk size is 0x{blockSize:X8}");
Span<byte> buffer = new byte[blockSize];
reader.Read(buffer);
if (isMr0k)
@@ -134,6 +151,7 @@ namespace AssetStudio
if (bundleSize == 0)
{
Logger.Verbose("This is header chunk !! attempting to read the bundle size");
using var blockReader = new EndianBinaryReader(new MemoryStream(buffer.ToArray()));
var header = new Header()
{
@@ -144,17 +162,21 @@ namespace AssetStudio
size = blockReader.ReadInt64()
};
bundleSize = header.size;
Logger.Verbose($"Bundle size is 0x{bundleSize:X8}");
}
readSize += buffer.Length;
if (readSize % (PackSize - 0x80) == 0)
{
reader.Position += PackSize - 9 - blockSize;
var padding = PackSize - 9 - blockSize;
reader.Position += padding;
Logger.Verbose($"Skip 0x{padding:X8} padding");
}
if (readSize == bundleSize)
{
Logger.Verbose($"Bundle has been read entirely !!");
readSize = 0;
bundleSize = 0;
}
@@ -166,6 +188,7 @@ namespace AssetStudio
signature = reader.ReadStringToNull();
if (signature == UnityFSSignature)
{
Logger.Verbose($"Found {UnityFSSignature} chunk at position {reader.Position - (UnityFSSignature.Length + 1)}");
var header = new Header()
{
signature = reader.ReadStringToNull(),
@@ -175,6 +198,7 @@ namespace AssetStudio
size = reader.ReadInt64()
};
Logger.Verbose($"Bundle size is 0x{header.size:X8}");
reader.Position = pos;
reader.BaseStream.CopyTo(ms, header.size);
continue;
@@ -196,15 +220,19 @@ namespace AssetStudio
reader.Dispose();
}
Logger.Verbose("Decrypted pack file successfully !!");
ms.Position = 0;
return new FileReader(reader.FullPath, ms);
}
public static FileReader DecryptMark(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Mark encryption");
var signature = reader.ReadStringToNull(4);
if (signature != "mark")
{
Logger.Verbose($"Expected signature mark, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -240,6 +268,7 @@ namespace AssetStudio
}
}
Logger.Verbose("Decrypted mark file successfully !!");
reader.Dispose();
dataStream.Position = 0;
return new FileReader(reader.FullPath, dataStream);
@@ -247,8 +276,10 @@ namespace AssetStudio
public static FileReader DecryptEnsembleStar(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Ensemble Star encryption");
if (Path.GetExtension(reader.FileName) != ".z")
{
Logger.Verbose($"Expected file extension .z, found {Path.GetExtension(reader.FileName)} instead, aborting...");
return reader;
}
using (reader)
@@ -269,46 +300,42 @@ namespace AssetStudio
data[i] = (byte)(EnsembleStarKey1[k1] ^ ((size ^ EnsembleStarKey3[k3] ^ data[i] ^ EnsembleStarKey2[k2]) + remaining));
}
Logger.Verbose("Decrypted Ensemble Star file successfully !!");
return new FileReader(reader.FullPath, new MemoryStream(data));
}
}
public static FileReader ParseOPFP(FileReader reader)
{
var stream = reader.BaseStream;
var data = reader.ReadBytes(0x1000);
var idx = data.Search("UnityFS");
if (idx != -1)
{
stream = new OffsetStream(stream, idx);
}
return new FileReader(reader.FullPath, stream);
}
public static FileReader ParseFakeHeader(FileReader reader)
{
Logger.Verbose($"Attempting to parse file {reader.FileName} with fake header");
var stream = reader.BaseStream;
var data = reader.ReadBytes(0x1000);
var idx = data.Search("UnityFS");
if (idx != -1)
{
Logger.Verbose($"Found fake header at offset 0x{idx:X8}");
var idx2 = data[(idx + 1)..].Search("UnityFS");
if (idx2 != -1)
{
Logger.Verbose($"Found real header at offset 0x{idx + idx2 + 1:X8}");
stream = new OffsetStream(stream, idx + idx2 + 1);
}
else
{
Logger.Verbose("Real header was not found, assuming fake header is the real one");
stream = new OffsetStream(stream, idx);
}
}
Logger.Verbose("Parsed fake header file successfully !!");
return new FileReader(reader.FullPath, stream);
}
public static FileReader DecryptFantasyOfWind(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Fantasy of Wind encryption");
byte[] encryptKeyName = Encoding.UTF8.GetBytes("28856");
const int MinLength = 0xC8;
const int KeyLength = 8;
@@ -319,6 +346,7 @@ namespace AssetStudio
var signature = reader.ReadStringToNull(HeadLength);
if (string.Compare(signature, "K9999") > 0 || reader.Length <= MinLength)
{
Logger.Verbose($"Signature version {signature} is higher than K9999 or stream length {reader.Length} is less than minimum length {MinLength}, aborting...");
reader.Position = 0;
return reader;
}
@@ -360,23 +388,13 @@ namespace AssetStudio
reader.BaseStream.CopyTo(ms);
ms.Position = 0;
Logger.Verbose("Decrypted Fantasy of Wind file successfully !!");
return new FileReader(reader.FullPath, ms);
}
public static FileReader ParseShiningNikki(FileReader reader)
{
var data = reader.ReadBytes(0x1000);
var idx = data.Search("UnityFS");
if (idx == -1)
{
reader.Position = 0;
return reader;
}
var stream = new OffsetStream(reader.BaseStream, idx);
return new FileReader(reader.FullPath, stream);
}
public static FileReader ParseHelixWaltz2(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Helix Waltz 2 encryption");
var originalHeader = new byte[] { 0x55, 0x6E, 0x69, 0x74, 0x79, 0x46, 0x53, 0x00, 0x00, 0x00, 0x00, 0x07, 0x35, 0x2E, 0x78, 0x2E };
var signature = reader.ReadStringToNull();
@@ -384,6 +402,7 @@ namespace AssetStudio
if (signature != "SzxFS")
{
Logger.Verbose($"Expected signature SzxFS, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -415,6 +434,7 @@ namespace AssetStudio
data[i] = key[idx];
}
Logger.Verbose("Decrypted Helix Waltz 2 file successfully !!");
MemoryStream ms = new();
ms.Write(originalHeader);
ms.Write(data);
@@ -424,6 +444,8 @@ namespace AssetStudio
}
public static FileReader DecryptAnchorPanic(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Anchor Panic encryption");
const int BlockSize = 0x800;
var data = reader.ReadBytes(0x1000);
@@ -432,25 +454,31 @@ namespace AssetStudio
var idx = data.Search("UnityFS");
if (idx != -1)
{
Logger.Verbose("Found UnityFS signature, file might not be encrypted");
return ParseFakeHeader(reader);
}
var key = GetKey(Path.GetFileNameWithoutExtension(reader.FileName));
Logger.Verbose($"Calculated key is {key}");
var chunkIndex = 0;
MemoryStream ms = new();
while (reader.Remaining > 0)
{
var chunkSize = Math.Min((int)reader.Remaining, BlockSize);
Logger.Verbose($"Chunk {chunkIndex} size is {chunkSize}");
var chunk = reader.ReadBytes(chunkSize);
if (IsEncrypt((int)reader.Length, chunkIndex++))
{
Logger.Verbose($"Chunk {chunkIndex} is encrypted, decrypting...");
RC4(chunk, key);
}
ms.Write(chunk);
}
Logger.Verbose("Decrypted Anchor Panic file successfully !!");
ms.Position = 0;
return new FileReader(reader.FullPath, ms);
bool IsEncrypt(int fileSize, int chunkIndex)
@@ -533,11 +561,14 @@ namespace AssetStudio
public static FileReader DecryptDreamscapeAlbireo(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Dreamscape Albireo encryption");
var key = new byte[] { 0x1E, 0x1E, 0x01, 0x01, 0xFC };
var signature = reader.ReadStringToNull(4);
if (signature != "MJJ")
{
Logger.Verbose($"Expected signature MJJ, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -559,6 +590,8 @@ namespace AssetStudio
var sizeLow = (u5 >> 24 | (u2 << 8)) ^ u1;
var size = (long)(sizeHigh << 32 | sizeLow);
Logger.Verbose($"Decrypted File info: Flag 0x{flag:X8} | Compressed blockInfo size 0x{compressedBlocksInfoSize:X8} | Decompressed blockInfo size 0x{uncompressedBlocksInfoSize:X8} | Bundle size 0x{size:X8}");
var blocksInfo = reader.ReadBytes((int)compressedBlocksInfoSize);
for(int i = 0; i < blocksInfo.Length; i++)
{
@@ -587,6 +620,7 @@ namespace AssetStudio
reader.BaseStream.CopyTo(ms);
ms.Position = 0;
Logger.Verbose("Decrypted Dreamscape Albireo file successfully !!");
return new FileReader(reader.FullPath, ms);
static uint Scrample(uint value) => (value >> 5) & 0xFFE000 | (value >> 29) | (value << 14) & 0xFF000000 | (8 * value) & 0x1FF8;
@@ -594,6 +628,8 @@ namespace AssetStudio
public static FileReader DecryptImaginaryFest(FileReader reader)
{
Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Imaginary Fest encryption");
const string dataRoot = "data";
var key = new byte[] { 0xBD, 0x65, 0xF2, 0x4F, 0xBE, 0xD1, 0x36, 0xD4, 0x95, 0xFE, 0x64, 0x94, 0xCB, 0xD3, 0x7E, 0x91, 0x57, 0xB7, 0x94, 0xB7, 0x9F, 0x70, 0xB2, 0xA9, 0xA0, 0xD5, 0x4E, 0x36, 0xC6, 0x40, 0xE0, 0x2F, 0x4E, 0x6E, 0x76, 0x6D, 0xCD, 0xAE, 0xEA, 0x05, 0x13, 0x6B, 0xA7, 0x84, 0xFF, 0xED, 0x90, 0x91, 0x15, 0x7E, 0xF1, 0xF8, 0xA5, 0x9C, 0xB6, 0xDE, 0xF9, 0x56, 0x57, 0x18, 0xBF, 0x94, 0x63, 0x6F, 0x1B, 0xE2, 0x92, 0xD2, 0x7E, 0x25, 0x95, 0x23, 0x24, 0xCB, 0x93, 0xD3, 0x36, 0xD9, 0x18, 0x11, 0xF5, 0x50, 0x18, 0xE4, 0x22, 0x28, 0xD8, 0xE2, 0x1A, 0x57, 0x1E, 0x04, 0x88, 0xA5, 0x84, 0xC0, 0x6C, 0x3B, 0x46, 0x62, 0xCE, 0x85, 0x10, 0x2E, 0xA0, 0xDC, 0xD3, 0x09, 0xB2, 0xB6, 0xA4, 0x8D, 0xAF, 0x74, 0x36, 0xF7, 0x9A, 0x3F, 0x98, 0xDA, 0x62, 0x57, 0x71, 0x75, 0x92, 0x05, 0xA3, 0xB2, 0x7C, 0xCA, 0xFB, 0x1E, 0xBE, 0xC9, 0x24, 0xC1, 0xD2, 0xB9, 0xDE, 0xE4, 0x7E, 0xF3, 0x0F, 0xB4, 0xFB, 0xA2, 0xC1, 0xC2, 0x14, 0x5C, 0x78, 0x13, 0x74, 0x41, 0x8D, 0x79, 0xF4, 0x3C, 0x49, 0x92, 0x98, 0xF2, 0xCD, 0x8C, 0x09, 0xA6, 0x40, 0x34, 0x51, 0x1C, 0x11, 0x2B, 0xE0, 0x6B, 0x42, 0x9C, 0x86, 0x41, 0x06, 0xF6, 0xD2, 0x87, 0xF1, 0x10, 0x26, 0x89, 0xC2, 0x7B, 0x2A, 0x5D, 0x1C, 0xDA, 0x92, 0xC8, 0x93, 0x59, 0xF9, 0x60, 0xD0, 0xB5, 0x1E, 0xD5, 0x75, 0x56, 0xA0, 0x05, 0x83, 0x90, 0xAC, 0x72, 0xC8, 0x10, 0x09, 0xED, 0x1A, 0x46, 0xD9, 0x39, 0x6B, 0x9E, 0x19, 0x5E, 0x51, 0x44, 0x09, 0x0D, 0x74, 0xAB, 0xA8, 0xF9, 0x32, 0x43, 0xBC, 0xD2, 0xED, 0x7B, 0x6C, 0x75, 0x32, 0x24, 0x14, 0x43, 0x5D, 0x98, 0xB2, 0xFC, 0xFB, 0xF5, 0x9A, 0x19, 0x03, 0xB0, 0xB7, 0xAC, 0xAE, 0x8B };
@@ -601,12 +637,14 @@ namespace AssetStudio
var signature = Encoding.UTF8.GetString(signatureBytes[..7]);
if (signature == "UnityFS")
{
Logger.Verbose("Found UnityFS signature, file might not be encrypted");
reader.Position = 0;
return reader;
}
if (signatureBytes[7] != 0)
{
Logger.Verbose($"File might be encrypted with a byte xorkey 0x{signatureBytes[7]:X8}, attemping to decrypting...");
var xorKey = signatureBytes[7];
for (int i = 0; i < signatureBytes.Length; i++)
{
@@ -615,12 +653,14 @@ namespace AssetStudio
signature = Encoding.UTF8.GetString(signatureBytes[..7]);
if (signature == "UnityFS")
{
Logger.Verbose("Found UnityFS signature, key is valid, decrypting the rest of the stream");
var remaining = reader.ReadBytes((int)reader.Remaining);
for (int i = 0; i < remaining.Length; i++)
{
remaining[i] ^= xorKey;
}
Logger.Verbose("Decrypted Imaginary Fest file successfully !!");
var stream = new MemoryStream();
stream.Write(signatureBytes);
stream.Write(remaining);
@@ -635,25 +675,33 @@ namespace AssetStudio
var startIdx = Array.FindIndex(paths, x => x == dataRoot);
if (startIdx != -1 && startIdx != paths.Length - 1)
{
Logger.Verbose("File is in the data folder !!");
var path = string.Join(Path.AltDirectorySeparatorChar, paths[(startIdx+1)..]);
var offset = GetLoadAssetBundleOffset(path);
if (offset > 0 && offset < reader.Length)
{
Logger.Verbose($"Calculated offset is 0x{offset:X8}, attempting to read signature...");
reader.Position = offset;
signature = reader.ReadStringToNull(7);
if (signature == "UnityFS")
{
Logger.Verbose($"Found UnityFS signature, file starts at 0x{offset:X8} !!");
Logger.Verbose("Parsed Imaginary Fest file successfully !!");
reader.Position = offset;
return new FileReader(reader.FullPath, new MemoryStream(reader.ReadBytes((int)reader.Remaining)));
}
}
Logger.Verbose($"Invalid offset, attempting to generate key...");
reader.Position = 0;
var data = reader.ReadBytes((int)reader.Remaining);
var key_value = GetHashCode(path);
Logger.Verbose($"Generated key is 0x{key_value:X8}, decrypting...");
Decrypt(data, key_value);
Logger.Verbose("Decrypted Imaginary Fest file successfully !!");
return new FileReader(reader.FullPath, new MemoryStream(data));
}
Logger.Verbose("File doesn't match any of the encryption types");
reader.Position = 0;
return reader;

View File

@@ -9,5 +9,13 @@ namespace AssetStudio
{
public int localSerializedFileIndex;
public long localIdentifierInFile;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"localSerializedFileIndex: {localSerializedFileIndex} | ");
sb.Append($"localIdentifierInFile: {localIdentifierInFile}");
return sb.ToString();
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
@@ -7,21 +8,79 @@ namespace AssetStudio
{
public static class Logger
{
public static ILogger Default = new DummyLogger();
public static bool Silent = false;
private static bool _fileLogging = true;
public static void Verbose(string message) => Default.Log(LoggerEvent.Verbose, message, Silent);
public static void Debug(string message) => Default.Log(LoggerEvent.Debug, message, Silent);
public static void Info(string message) => Default.Log(LoggerEvent.Info, message, Silent);
public static void Warning(string message) => Default.Log(LoggerEvent.Warning, message, Silent);
public static void Error(string message) => Default.Log(LoggerEvent.Error, message, Silent);
public static ILogger Default = new DummyLogger();
public static ILogger File;
public static bool Silent = false;
public static bool LogVerbose = false;
public static bool FileLogging
{
get => _fileLogging;
set
{
_fileLogging = value;
if (_fileLogging)
{
File = new FileLogger();
}
else
{
((FileLogger)File)?.Writer?.Dispose();
File = null;
}
}
}
public static void Verbose(string message)
{
if (LogVerbose)
{
try
{
var callerMethod = new StackTrace().GetFrame(1).GetMethod();
var callerMethodClass = callerMethod.ReflectedType.Name;
if (!string.IsNullOrEmpty(callerMethodClass))
{
message = $"[{callerMethodClass}] {message}";
}
}
catch (Exception) { }
if (FileLogging) File.Log(LoggerEvent.Verbose, message);
Default.Log(LoggerEvent.Verbose, message, Silent);
}
}
public static void Debug(string message)
{
if (FileLogging) File.Log(LoggerEvent.Debug, message);
Default.Log(LoggerEvent.Debug, message, Silent);
}
public static void Info(string message)
{
if (FileLogging) File.Log(LoggerEvent.Info, message);
Default.Log(LoggerEvent.Info, message, Silent);
}
public static void Warning(string message)
{
if (FileLogging) File.Log(LoggerEvent.Warning, message);
Default.Log(LoggerEvent.Warning, message, Silent);
}
public static void Error(string message)
{
if (FileLogging) File.Log(LoggerEvent.Error, message);
Default.Log(LoggerEvent.Error, message, Silent);
}
public static void Error(string message, Exception e)
{
var sb = new StringBuilder();
sb.AppendLine(message);
sb.AppendLine(e.ToString());
Default.Log(LoggerEvent.Error, sb.ToString(), Silent);
message = sb.ToString();
if (FileLogging) File.Log(LoggerEvent.Error, message);
Default.Log(LoggerEvent.Error, message, Silent);
}
}
}

View File

@@ -63,6 +63,7 @@ namespace AssetStudio
return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W);
}
public static Quaternion Zero => new Quaternion(0, 0, 0, 1);
public static float Dot(Quaternion a, Quaternion b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;

View File

@@ -15,6 +15,8 @@ namespace AssetStudio
public long Offset;
public Mhy0 mhy0;
public long TotalSize => 8 + m_Header.compressedBlocksInfoSize + m_BlocksInfo.Sum(x => x.compressedSize);
public Mhy0File(FileReader reader, string path, Mhy0 mhy0)
{
this.mhy0 = mhy0;
@@ -22,6 +24,10 @@ namespace AssetStudio
reader.Endian = EndianType.LittleEndian;
var signature = reader.ReadStringToNull(4);
Logger.Verbose($"Parsed signature {signature}");
if (signature != "mhy0")
throw new Exception("not a mhy0");
m_Header = new BundleFile.Header
{
version = 6,
@@ -30,6 +36,7 @@ namespace AssetStudio
compressedBlocksInfoSize = reader.ReadUInt32(),
flags = (ArchiveFlags)0x43
};
Logger.Verbose($"Header: {m_Header}");
ReadBlocksInfoAndDirectory(reader);
using var blocksStream = CreateBlocksStream(path);
ReadBlocks(reader, blocksStream);
@@ -41,9 +48,11 @@ namespace AssetStudio
var blocksInfo = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize);
DescrambleHeader(blocksInfo);
Logger.Verbose($"Descrambled blocksInfo signature {Convert.ToHexString(blocksInfo, 0 , 4)}");
using var blocksInfoStream = new MemoryStream(blocksInfo, 0x20, (int)m_Header.compressedBlocksInfoSize - 0x20);
using var blocksInfoReader = new EndianBinaryReader(blocksInfoStream);
m_Header.uncompressedBlocksInfoSize = blocksInfoReader.ReadMhy0UInt();
Logger.Verbose($"uncompressed blocksInfo size: 0x{m_Header.uncompressedBlocksInfoSize:X8}");
var compressedBlocksInfo = blocksInfoReader.ReadBytes((int)blocksInfoReader.Remaining);
var uncompressedBlocksInfo = new byte[(int)m_Header.uncompressedBlocksInfoSize];
var numWrite = LZ4Codec.Decode(compressedBlocksInfo, uncompressedBlocksInfo);
@@ -52,10 +61,12 @@ namespace AssetStudio
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {m_Header.uncompressedBlocksInfoSize} bytes");
}
Logger.Verbose($"Writing block and directory to blocks stream...");
using var blocksInfoUncompressedStream = new MemoryStream(uncompressedBlocksInfo);
using var blocksInfoUncompressedReader = new EndianBinaryReader(blocksInfoUncompressedStream);
var nodesCount = blocksInfoUncompressedReader.ReadMhy0Int();
m_DirectoryInfo = new BundleFile.Node[nodesCount];
Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new BundleFile.Node
@@ -65,10 +76,13 @@ namespace AssetStudio
offset = blocksInfoUncompressedReader.ReadMhy0Int(),
size = blocksInfoUncompressedReader.ReadMhy0UInt()
};
Logger.Verbose($"Directory {i} Info: {m_DirectoryInfo[i]}");
}
var blocksInfoCount = blocksInfoUncompressedReader.ReadMhy0Int();
m_BlocksInfo = new BundleFile.StorageBlock[blocksInfoCount];
Logger.Verbose($"Blocks count: {blocksInfoCount}");
for (int i = 0; i < blocksInfoCount; i++)
{
m_BlocksInfo[i] = new BundleFile.StorageBlock
@@ -77,6 +91,8 @@ namespace AssetStudio
uncompressedSize = blocksInfoUncompressedReader.ReadMhy0UInt(),
flags = (StorageBlockFlags)0x43
};
Logger.Verbose($"Block {i} Info: {m_BlocksInfo[i]}");
}
}
@@ -84,6 +100,7 @@ namespace AssetStudio
{
Stream blocksStream;
var uncompressedSizeSum = (int)m_BlocksInfo.Sum(x => x.uncompressedSize);
Logger.Verbose($"Total size of decompressed blocks: 0x{uncompressedSizeSum:X8}");
if (uncompressedSizeSum >= int.MaxValue)
blocksStream = new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
else
@@ -110,6 +127,7 @@ namespace AssetStudio
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
DescrambleEntry(compressedBytesSpan);
Logger.Verbose($"Descrambled block signature {Convert.ToHexString(compressedBytes, 0, 4)}");
var numWrite = LZ4Codec.Decode(compressedBytesSpan[0xC..compressedSize], uncompressedBytesSpan);
if (numWrite != uncompressedSize)
{
@@ -124,6 +142,8 @@ namespace AssetStudio
private void ReadFiles(Stream blocksStream, string path)
{
Logger.Verbose($"Writing files from blocks stream...");
fileList = new StreamFile[m_DirectoryInfo.Length];
for (int i = 0; i < m_DirectoryInfo.Length; i++)
{

View File

@@ -1,4 +1,6 @@
namespace AssetStudio
using System.Text;
namespace AssetStudio
{
public class ObjectInfo
{
@@ -11,5 +13,18 @@
public long m_PathID;
public SerializedType serializedType;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"byteStart: 0x{byteStart:X8} | ");
sb.Append($"byteSize: 0x{byteSize:X8} | ");
sb.Append($"typeID: {typeID} | ");
sb.Append($"classID: {classID} | ");
sb.Append($"isDestroyed: {isDestroyed} | ");
sb.Append($"stripped: {stripped} | ");
sb.Append($"PathID: {m_PathID}");
return sb.ToString();
}
}
}

View File

@@ -39,10 +39,13 @@ namespace AssetStudio
serializedType = objectInfo.serializedType;
platform = assetsFile.m_TargetPlatform;
m_Version = assetsFile.header.m_Version;
Logger.Verbose($"Initialized reader for {type} object with {m_PathID} in file {assetsFile.fileName} !!");
}
public void Reset()
{
Logger.Verbose($"Resetting reader position to object offset 0x{byteStart:X8}...");
Position = byteStart;
}
}

View File

@@ -60,14 +60,7 @@ namespace AssetStudio
_baseStream.Seek(target, SeekOrigin.Begin);
return Position;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (offset > _baseStream.Length || Position + count > _baseStream.Length)
{
throw new IOException("Unable to read beyond stream bound");
}
return _baseStream.Read(buffer, offset, count);
}
public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count);
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Flush() => throw new NotImplementedException();

View File

@@ -60,23 +60,28 @@ namespace AssetStudio
reader.Position = header.m_FileSize - header.m_MetadataSize;
m_FileEndianess = reader.ReadByte();
}
if (header.m_Version >= SerializedFileFormatVersion.LargeFilesSupport)
{
header.m_MetadataSize = reader.ReadUInt32();
header.m_FileSize = reader.ReadInt64();
header.m_DataOffset = reader.ReadInt64();
reader.ReadInt64(); // unknown
}
Logger.Verbose($"File {fileName} Info: {header}");
// ReadMetadata
if (m_FileEndianess == 0)
{
reader.Endian = EndianType.LittleEndian;
Logger.Verbose($"Endianness {reader.Endian}");
}
if (header.m_Version >= SerializedFileFormatVersion.Unknown_7)
{
unityVersion = reader.ReadStringToNull();
Logger.Verbose($"Unity version {unityVersion}");
SetVersion(unityVersion);
}
if (header.m_Version >= SerializedFileFormatVersion.Unknown_8)
@@ -84,12 +89,15 @@ namespace AssetStudio
m_TargetPlatform = (BuildTarget)reader.ReadInt32();
if (!Enum.IsDefined(typeof(BuildTarget), m_TargetPlatform))
{
Logger.Verbose($"Parsed target format {m_TargetPlatform} doesn't match any of supported formats, defaulting to {BuildTarget.UnknownPlatform}");
m_TargetPlatform = BuildTarget.UnknownPlatform;
}
else if (game.Type.IsMhyGroup())
{
Logger.Verbose($"Selected game {game.Name} is a mhy game, forcing target format {BuildTarget.StandaloneWindows64}");
m_TargetPlatform = BuildTarget.StandaloneWindows64;
}
Logger.Verbose($"Target format {m_TargetPlatform}");
}
if (header.m_Version >= SerializedFileFormatVersion.HasTypeTreeHashes)
{
@@ -99,6 +107,7 @@ namespace AssetStudio
// Read Types
int typeCount = reader.ReadInt32();
m_Types = new List<SerializedType>(typeCount);
Logger.Verbose($"Found {typeCount} serialized types");
for (int i = 0; i < typeCount; i++)
{
m_Types.Add(ReadSerializedType(false));
@@ -114,6 +123,7 @@ namespace AssetStudio
m_Objects = new List<ObjectInfo>(objectCount);
Objects = new List<Object>(objectCount);
ObjectsDic = new Dictionary<long, Object>(objectCount);
Logger.Verbose($"Found {objectCount} objects");
for (int i = 0; i < objectCount; i++)
{
var objectInfo = new ObjectInfo();
@@ -164,12 +174,14 @@ namespace AssetStudio
{
objectInfo.stripped = reader.ReadByte();
}
Logger.Verbose($"Object Info: {objectInfo}");
m_Objects.Add(objectInfo);
}
if (header.m_Version >= SerializedFileFormatVersion.HasScriptTypeIndex)
{
int scriptCount = reader.ReadInt32();
Logger.Verbose($"Found {scriptCount} scripts");
m_ScriptTypes = new List<LocalSerializedObjectIdentifier>(scriptCount);
for (int i = 0; i < scriptCount; i++)
{
@@ -184,12 +196,14 @@ namespace AssetStudio
reader.AlignStream();
m_ScriptType.localIdentifierInFile = reader.ReadInt64();
}
Logger.Verbose($"Script Info: {m_ScriptType}");
m_ScriptTypes.Add(m_ScriptType);
}
}
int externalsCount = reader.ReadInt32();
m_Externals = new List<FileIdentifier>(externalsCount);
Logger.Verbose($"Found {externalsCount} externals");
for (int i = 0; i < externalsCount; i++)
{
var m_External = new FileIdentifier();
@@ -204,6 +218,7 @@ namespace AssetStudio
}
m_External.pathName = reader.ReadStringToNull();
m_External.fileName = Path.GetFileName(m_External.pathName);
Logger.Verbose($"External Info: {m_External}");
m_Externals.Add(m_External);
}
@@ -211,6 +226,7 @@ namespace AssetStudio
{
int refTypesCount = reader.ReadInt32();
m_RefTypes = new List<SerializedType>(refTypesCount);
Logger.Verbose($"Found {refTypesCount} reference types");
for (int i = 0; i < refTypesCount; i++)
{
m_RefTypes.Add(ReadSerializedType(true));
@@ -239,12 +255,14 @@ namespace AssetStudio
private SerializedType ReadSerializedType(bool isRefType)
{
Logger.Verbose($"Attempting to parse serialized" + (isRefType ? " reference" : " ") + "type");
var type = new SerializedType();
type.classID = reader.ReadInt32();
if (game.Type.IsGIGroup() && BitConverter.ToBoolean(header.m_Reserved))
{
Logger.Verbose($"Encoded class ID {type.classID}, decoding...");
type.classID = DecodeClassID(type.classID);
}
@@ -273,6 +291,7 @@ namespace AssetStudio
if (m_EnableTypeTree)
{
Logger.Verbose($"File has type tree enabled !!");
type.m_Type = new TypeTree();
type.m_Type.m_Nodes = new List<TypeTreeNode>();
if (header.m_Version >= SerializedFileFormatVersion.Unknown_12 || header.m_Version == SerializedFileFormatVersion.Unknown_10)
@@ -298,11 +317,13 @@ namespace AssetStudio
}
}
Logger.Verbose($"Serialized type info: {type}");
return type;
}
private void ReadTypeTree(TypeTree m_Type, int level = 0)
{
Logger.Verbose($"Attempting to parse type tree...");
var typeTreeNode = new TypeTreeNode();
m_Type.m_Nodes.Add(typeTreeNode);
typeTreeNode.m_Level = level;
@@ -329,12 +350,16 @@ namespace AssetStudio
{
ReadTypeTree(m_Type, level + 1);
}
Logger.Verbose($"Type Tree Info: {m_Type}");
}
private void TypeTreeBlobRead(TypeTree m_Type)
{
Logger.Verbose($"Attempting to parse blob type tree...");
int numberOfNodes = reader.ReadInt32();
int stringBufferSize = reader.ReadInt32();
Logger.Verbose($"Found {numberOfNodes} nodes and {stringBufferSize} strings");
for (int i = 0; i < numberOfNodes; i++)
{
var typeTreeNode = new TypeTreeNode();
@@ -364,6 +389,8 @@ namespace AssetStudio
}
}
Logger.Verbose($"Type Tree Info: {m_Type}");
string ReadString(EndianBinaryReader stringBufferReader, uint value)
{
var isOffset = (value & 0x80000000) == 0;
@@ -383,6 +410,7 @@ namespace AssetStudio
public void AddObject(Object obj)
{
Logger.Verbose($"Caching object with {obj.m_PathID} in file {fileName}...");
Objects.Add(obj);
ObjectsDic.Add(obj.m_PathID, obj);
}

View File

@@ -13,5 +13,16 @@ namespace AssetStudio
public long m_DataOffset;
public byte m_Endianess;
public byte[] m_Reserved;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"MetadataSize: 0x{m_MetadataSize:X8} | ");
sb.Append($"FileSize: 0x{m_FileSize:X8} | ");
sb.Append($"Version: {m_Version} | ");
sb.Append($"DataOffset: 0x{m_DataOffset:X8} | ");
sb.Append($"Endianness: {(EndianType)m_Endianess}");
return sb.ToString();
}
}
}

View File

@@ -19,5 +19,16 @@ namespace AssetStudio
public string m_AsmName;
public bool Match(string hash) => Convert.ToHexString(m_OldTypeHash) == hash;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"classID: {classID} | ");
sb.Append($"IsStrippedType: {m_IsStrippedType} | ");
sb.Append($"ScriptTypeIndex: {m_ScriptTypeIndex} | ");
sb.Append($"KlassName: {m_KlassName} | ");
sb.Append($"NameSpace: {m_NameSpace} | ");
sb.Append($"AsmName: {m_AsmName}");
return sb.ToString();
}
}
}

View File

@@ -186,6 +186,7 @@ namespace AssetStudio
{
var m_Node = m_Nodes[i];
var varTypeStr = m_Node.m_Type;
Logger.Verbose($"Reading {m_Node.m_Name} of type {varTypeStr}");
object value;
var align = (m_Node.m_MetaFlag & 0x4000) != 0;
switch (varTypeStr)

View File

@@ -28,5 +28,19 @@ namespace AssetStudio
m_Level = level;
m_MetaFlag = align ? 0x4000 : 0;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"Type: {m_Type} | ");
sb.Append($"Name: {m_Name} | ");
sb.Append($"ByteSize: 0x{m_ByteSize:X8} | ");
sb.Append($"Index: {m_Index} | ");
sb.Append($"TypeFlags: {m_TypeFlags} | ");
sb.Append($"Version: {m_Version} | ");
sb.Append($"TypeStrOffset: 0x{m_TypeStrOffset:X8} | ");
sb.Append($"NameStrOffset: 0x{m_NameStrOffset:X8}");
return sb.ToString();
}
}
}

View File

@@ -13,6 +13,15 @@ namespace AssetStudio
public int dataOffset;
public int dataLength;
public string path;
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"dataOffset: 0x{dataOffset:X8} | ");
sb.Append($"dataOffset: 0x{dataLength:X8} | ");
sb.Append($"path: {path}");
return sb.ToString();
}
}
public WebFile(EndianBinaryReader reader)
@@ -21,15 +30,19 @@ namespace AssetStudio
var signature = reader.ReadStringToNull();
var headLength = reader.ReadInt32();
var dataList = new List<WebData>();
Logger.Verbose($"Header size: 0x{headLength:X8}");
while (reader.BaseStream.Position < headLength)
{
var data = new WebData();
data.dataOffset = reader.ReadInt32();
data.dataLength = reader.ReadInt32();
var pathLength = reader.ReadInt32();
Logger.Verbose($"Path length: {pathLength}");
data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength));
Logger.Verbose($"Web data Info: {data}");
dataList.Add(data);
}
Logger.Verbose("Writing files to streams...");
fileList = new StreamFile[dataList.Count];
for (int i = 0; i < dataList.Count; i++)
{