- [Core] Added new entry
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using ACLLibs;
|
||||
using System;
|
||||
using ACLLibs;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
@@ -8,17 +9,23 @@ namespace AssetStudio
|
||||
{
|
||||
if (game.Type.IsSRGroup())
|
||||
{
|
||||
SRACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times);
|
||||
var aclClip = m_ACLClip as MHYACLClip;
|
||||
SRACL.DecompressAll(aclClip.m_ClipData, out values, out times);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_ACLClip.m_DatabaseData.IsNullOrEmpty())
|
||||
switch (m_ACLClip)
|
||||
{
|
||||
DBACL.DecompressTracks(m_ACLClip.m_ClipData, m_ACLClip.m_DatabaseData, out values, out times);
|
||||
}
|
||||
else
|
||||
{
|
||||
ACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times);
|
||||
case GIACLClip giaclClip:
|
||||
DBACL.DecompressTracks(giaclClip.m_ClipData, giaclClip.m_DatabaseData, out values, out times);
|
||||
break;
|
||||
case MHYACLClip mhyaclClip:
|
||||
ACL.DecompressAll(mhyaclClip.m_ClipData, out values, out times);
|
||||
break;
|
||||
default:
|
||||
values = Array.Empty<float>();
|
||||
times = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,6 +718,8 @@ namespace AssetStudio
|
||||
dest = 1;
|
||||
else if (Game.Type.IsSRGroup() && texEnv.Key.Contains("Pack"))
|
||||
dest = 0;
|
||||
else if (Game.Type.IsArknightsEndfield() && texEnv.Key == "_BaseMap")
|
||||
dest = 0;
|
||||
|
||||
texture.Dest = dest;
|
||||
|
||||
@@ -896,15 +898,15 @@ namespace AssetStudio
|
||||
var streamedFrames = m_Clip.m_StreamedClip.ReadData();
|
||||
var m_ClipBindingConstant = animationClip.m_ClipBindingConstant ?? m_Clip.ConvertValueArrayToGenericBinding();
|
||||
var m_ACLClip = m_Clip.m_ACLClip;
|
||||
var aclCount = m_ACLClip.m_CurveCount;
|
||||
if (!m_ACLClip.m_ClipData.IsNullOrEmpty() && !Game.Type.IsSRGroup())
|
||||
var aclCount = m_ACLClip.CurveCount;
|
||||
if (m_ACLClip.IsSet && !Game.Type.IsSRGroup())
|
||||
{
|
||||
m_ACLClip.Process(Game, out var values, out var times);
|
||||
for (int frameIndex = 0; frameIndex < times.Length; frameIndex++)
|
||||
{
|
||||
var time = times[frameIndex];
|
||||
var frameOffset = frameIndex * m_ACLClip.m_CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < m_ACLClip.m_CurveCount;)
|
||||
var frameOffset = frameIndex * m_ACLClip.CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < m_ACLClip.CurveCount;)
|
||||
{
|
||||
var index = curveIndex;
|
||||
ReadCurveData(iAnim, m_ClipBindingConstant, index, time, values, (int)frameOffset, ref curveIndex);
|
||||
@@ -938,14 +940,14 @@ namespace AssetStudio
|
||||
ReadCurveData(iAnim, m_ClipBindingConstant, (int)index, time, m_DenseClip.m_SampleArray, (int)frameOffset, ref curveIndex);
|
||||
}
|
||||
}
|
||||
if (!m_ACLClip.m_ClipData.IsNullOrEmpty() && Game.Type.IsSRGroup())
|
||||
if (m_ACLClip.IsSet && Game.Type.IsSRGroup())
|
||||
{
|
||||
m_ACLClip.Process(Game, out var values, out var times);
|
||||
for (int frameIndex = 0; frameIndex < times.Length; frameIndex++)
|
||||
{
|
||||
var time = times[frameIndex];
|
||||
var frameOffset = frameIndex * m_ACLClip.m_CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < m_ACLClip.m_CurveCount;)
|
||||
var frameOffset = frameIndex * m_ACLClip.CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < m_ACLClip.CurveCount;)
|
||||
{
|
||||
var index = (int)(curveIndex + m_DenseClip.m_CurveCount + streamCount);
|
||||
ReadCurveData(iAnim, m_ClipBindingConstant, index, time, values, (int)frameOffset, ref curveIndex);
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace AssetStudio
|
||||
var lastSampleFrame = streamedFrames.Count > 1 ? streamedFrames[streamedFrames.Count - 2].time : 0.0f;
|
||||
var lastFrame = Math.Max(lastDenseFrame, lastSampleFrame);
|
||||
|
||||
if (!m_Clip.m_ACLClip.m_ClipData.IsNullOrEmpty() && !game.Type.IsSRGroup())
|
||||
if (m_Clip.m_ACLClip.IsSet && !game.Type.IsSRGroup())
|
||||
{
|
||||
var lastACLFrame = ProcessACLClip(m_Clip, bindings, tos);
|
||||
lastFrame = Math.Max(lastFrame, lastACLFrame);
|
||||
@@ -65,7 +65,7 @@ namespace AssetStudio
|
||||
}
|
||||
ProcessStreams(streamedFrames, bindings, tos, m_Clip.m_DenseClip.m_SampleRate);
|
||||
ProcessDenses(m_Clip, bindings, tos);
|
||||
if (!m_Clip.m_ACLClip.m_ClipData.IsNullOrEmpty() && game.Type.IsSRGroup())
|
||||
if (m_Clip.m_ACLClip.IsSet && game.Type.IsSRGroup())
|
||||
{
|
||||
var lastACLFrame = ProcessACLClip(m_Clip, bindings, tos);
|
||||
lastFrame = Math.Max(lastFrame, lastACLFrame);
|
||||
@@ -112,7 +112,7 @@ namespace AssetStudio
|
||||
var curve = frame.keyList[curveIndex];
|
||||
var index = curve.index;
|
||||
if (!game.Type.IsSRGroup())
|
||||
index += (int)animationClip.m_MuscleClip.m_Clip.m_ACLClip.m_CurveCount;
|
||||
index += (int)animationClip.m_MuscleClip.m_Clip.m_ACLClip.CurveCount;
|
||||
var binding = bindings.FindBinding(index);
|
||||
|
||||
var path = GetCurvePath(tos, binding.path);
|
||||
@@ -161,7 +161,7 @@ namespace AssetStudio
|
||||
{
|
||||
var index = (int)streamCount + curveIndex;
|
||||
if (!game.Type.IsSRGroup())
|
||||
index += (int)clip.m_ACLClip.m_CurveCount;
|
||||
index += (int)clip.m_ACLClip.CurveCount;
|
||||
var binding = bindings.FindBinding(index);
|
||||
var path = GetCurvePath(tos, binding.path);
|
||||
var framePosition = frameOffset + curveIndex;
|
||||
@@ -193,8 +193,8 @@ namespace AssetStudio
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
{
|
||||
float time = times[frameIndex];
|
||||
int frameOffset = frameIndex * (int)acl.m_CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < acl.m_CurveCount;)
|
||||
int frameOffset = frameIndex * (int)acl.CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < acl.CurveCount;)
|
||||
{
|
||||
var index = curveIndex;
|
||||
if (game.Type.IsSRGroup())
|
||||
@@ -236,8 +236,8 @@ namespace AssetStudio
|
||||
for (var curveIndex = 0; curveIndex < constant.data.Length;)
|
||||
{
|
||||
var index = (int)(streamCount + denseCount + curveIndex);
|
||||
if (!clip.m_ACLClip.m_ClipData.IsNullOrEmpty())
|
||||
index += (int)clip.m_ACLClip.m_CurveCount;
|
||||
if (clip.m_ACLClip.IsSet)
|
||||
index += (int)clip.m_ACLClip.CurveCount;
|
||||
GenericBinding binding = bindings.FindBinding(index);
|
||||
string path = GetCurvePath(tos, binding.path);
|
||||
if (binding.typeID == ClassIDType.Transform)
|
||||
|
||||
@@ -634,36 +634,36 @@ namespace AssetStudio
|
||||
|
||||
public static string FindPropertyNameByCRC28(this Material material, uint crc)
|
||||
{
|
||||
foreach (var property in material.m_SavedProperties.m_TexEnvs.Keys)
|
||||
foreach (var property in material.m_SavedProperties.m_TexEnvs)
|
||||
{
|
||||
string hdrName = $"{property}_HDR";
|
||||
string hdrName = $"{property.Key}_HDR";
|
||||
if (CRC.Verify28DigestUTF8(hdrName, crc))
|
||||
{
|
||||
return hdrName;
|
||||
}
|
||||
string stName = $"{property}_ST";
|
||||
string stName = $"{property.Key}_ST";
|
||||
if (CRC.Verify28DigestUTF8(stName, crc))
|
||||
{
|
||||
return stName;
|
||||
}
|
||||
string texelName = $"{property}_TexelSize";
|
||||
string texelName = $"{property.Key}_TexelSize";
|
||||
if (CRC.Verify28DigestUTF8(texelName, crc))
|
||||
{
|
||||
return texelName;
|
||||
}
|
||||
}
|
||||
foreach (var property in material.m_SavedProperties.m_Floats.Keys)
|
||||
foreach (var property in material.m_SavedProperties.m_Floats)
|
||||
{
|
||||
if (CRC.Verify28DigestUTF8(property, crc))
|
||||
if (CRC.Verify28DigestUTF8(property.Key, crc))
|
||||
{
|
||||
return property;
|
||||
return property.Key;
|
||||
}
|
||||
}
|
||||
foreach (var property in material.m_SavedProperties.m_Colors.Keys)
|
||||
foreach (var property in material.m_SavedProperties.m_Colors)
|
||||
{
|
||||
if (CRC.Verify28DigestUTF8(property, crc))
|
||||
if (CRC.Verify28DigestUTF8(property.Key, crc))
|
||||
{
|
||||
return property;
|
||||
return property.Key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace AssetStudio
|
||||
Lz4HC,
|
||||
Lzham,
|
||||
Lz4Mr0k,
|
||||
Lz4Inv = 5,
|
||||
Zstd = 5
|
||||
}
|
||||
|
||||
@@ -542,6 +543,10 @@ namespace AssetStudio
|
||||
{
|
||||
NetEaseUtils.Decrypt(compressedBytesSpan);
|
||||
}
|
||||
if (Game.Type.IsArknightsEndfield() && i == 0)
|
||||
{
|
||||
FairGuardUtils.Decrypt(compressedBytesSpan);
|
||||
}
|
||||
if (Game.Type.IsOPFP())
|
||||
{
|
||||
OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath);
|
||||
@@ -559,6 +564,26 @@ namespace AssetStudio
|
||||
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
|
||||
break;
|
||||
}
|
||||
case CompressionType.Lz4Inv when Game.Type.IsArknightsEndfield():
|
||||
{
|
||||
var compressedSize = (int)blockInfo.compressedSize;
|
||||
var compressedBytes = BigArrayPool<byte>.Shared.Rent(compressedSize);
|
||||
reader.Read(compressedBytes, 0, compressedSize);
|
||||
var compressedBytesSpan = compressedBytes.AsSpan(0, compressedSize);
|
||||
if (i == 0)
|
||||
{
|
||||
FairGuardUtils.Decrypt(compressedBytesSpan);
|
||||
|
||||
}
|
||||
var uncompressedSize = (int)blockInfo.uncompressedSize;
|
||||
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize);
|
||||
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
|
||||
FairGuardUtils.Lz4.Decompress(compressedBytesSpan, uncompressedBytesSpan);
|
||||
blocksStream.Write(uncompressedBytes, 0, uncompressedSize);
|
||||
BigArrayPool<byte>.Shared.Return(compressedBytes);
|
||||
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
|
||||
break;
|
||||
}
|
||||
case CompressionType.Zstd when !Game.Type.IsMhyGroup(): //Zstd
|
||||
{
|
||||
var compressedSize = (int)blockInfo.compressedSize;
|
||||
|
||||
@@ -783,22 +783,71 @@ namespace AssetStudio
|
||||
}
|
||||
}
|
||||
|
||||
public class ACLClip
|
||||
public abstract class ACLClip
|
||||
{
|
||||
public byte[] m_ClipData;
|
||||
public byte[] m_DatabaseData;
|
||||
public virtual bool IsSet => false;
|
||||
public virtual uint CurveCount => 0;
|
||||
public abstract void Read(ObjectReader reader);
|
||||
}
|
||||
|
||||
public class EmptyACLClip : ACLClip
|
||||
{
|
||||
public override void Read(ObjectReader reader) { }
|
||||
}
|
||||
|
||||
public class ArkACLClip : ACLClip
|
||||
{
|
||||
public int m_ACLType;
|
||||
public byte[] m_ACLArray;
|
||||
public float m_PositionFactor;
|
||||
public float m_EulerFactor;
|
||||
public float m_ScaleFactor;
|
||||
public float m_FloatFactor;
|
||||
public float m_nPositionCurves;
|
||||
public float m_nRotationCurves;
|
||||
public float m_nEulerCurves;
|
||||
public float m_nScaleCurves;
|
||||
|
||||
public override bool IsSet => !m_ACLArray.IsNullOrEmpty();
|
||||
|
||||
public ArkACLClip()
|
||||
{
|
||||
m_ACLArray = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public override void Read(ObjectReader reader)
|
||||
{
|
||||
m_ACLType = reader.ReadInt32();
|
||||
m_ACLArray = reader.ReadUInt8Array();
|
||||
reader.AlignStream();
|
||||
m_PositionFactor = reader.ReadSingle();
|
||||
m_EulerFactor = reader.ReadSingle();
|
||||
m_ScaleFactor = reader.ReadSingle();
|
||||
m_FloatFactor = reader.ReadSingle();
|
||||
m_nPositionCurves = reader.ReadSingle();
|
||||
m_nRotationCurves = reader.ReadSingle();
|
||||
m_nEulerCurves = reader.ReadSingle();
|
||||
m_nScaleCurves = reader.ReadSingle();
|
||||
}
|
||||
}
|
||||
|
||||
public class MHYACLClip : ACLClip
|
||||
{
|
||||
public uint m_CurveCount;
|
||||
public uint m_ConstCurveCount;
|
||||
|
||||
public ACLClip()
|
||||
public byte[] m_ClipData;
|
||||
|
||||
public override bool IsSet => !m_ClipData.IsNullOrEmpty();
|
||||
public override uint CurveCount => m_CurveCount;
|
||||
|
||||
public MHYACLClip()
|
||||
{
|
||||
m_CurveCount = 0;
|
||||
m_ConstCurveCount = 0;
|
||||
m_ClipData = Array.Empty<byte>();
|
||||
m_DatabaseData = Array.Empty<byte>();
|
||||
}
|
||||
public void Read(ObjectReader reader)
|
||||
public override void Read(ObjectReader reader)
|
||||
{
|
||||
var byteCount = reader.ReadInt32();
|
||||
|
||||
@@ -817,7 +866,28 @@ namespace AssetStudio
|
||||
m_ConstCurveCount = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
public void ParseGI(ObjectReader reader)
|
||||
}
|
||||
|
||||
public class GIACLClip : ACLClip
|
||||
{
|
||||
public uint m_CurveCount;
|
||||
public uint m_ConstCurveCount;
|
||||
|
||||
public byte[] m_ClipData;
|
||||
public byte[] m_DatabaseData;
|
||||
|
||||
public override bool IsSet => !m_ClipData.IsNullOrEmpty() && !m_DatabaseData.IsNullOrEmpty();
|
||||
public override uint CurveCount => m_CurveCount;
|
||||
|
||||
public GIACLClip()
|
||||
{
|
||||
m_CurveCount = 0;
|
||||
m_ConstCurveCount = 0;
|
||||
m_ClipData = Array.Empty<byte>();
|
||||
m_DatabaseData = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public override void Read(ObjectReader reader)
|
||||
{
|
||||
var aclTracksCount = (int)reader.ReadUInt64();
|
||||
var aclTracksOffset = reader.Position + reader.ReadInt64();
|
||||
@@ -1099,7 +1169,7 @@ namespace AssetStudio
|
||||
|
||||
public class Clip
|
||||
{
|
||||
public ACLClip m_ACLClip = new();
|
||||
public ACLClip m_ACLClip = new EmptyACLClip();
|
||||
public StreamedClip m_StreamedClip;
|
||||
public DenseClip m_DenseClip;
|
||||
public ConstantClip m_ConstantClip;
|
||||
@@ -1113,6 +1183,12 @@ namespace AssetStudio
|
||||
m_DenseClip = new DenseClip(reader);
|
||||
if (reader.Game.Type.IsSRGroup())
|
||||
{
|
||||
m_ACLClip = new MHYACLClip();
|
||||
m_ACLClip.Read(reader);
|
||||
}
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
m_ACLClip = new ArkACLClip();
|
||||
m_ACLClip.Read(reader);
|
||||
}
|
||||
if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) //4.3 and up
|
||||
@@ -1121,6 +1197,7 @@ namespace AssetStudio
|
||||
}
|
||||
if (reader.Game.Type.IsGIGroup() || reader.Game.Type.IsBH3Group() || reader.Game.Type.IsZZZCB1())
|
||||
{
|
||||
m_ACLClip = new MHYACLClip();
|
||||
m_ACLClip.Read(reader);
|
||||
}
|
||||
if (version[0] < 2018 || (version[0] == 2018 && version[1] < 3)) //2018.3 down
|
||||
@@ -1143,7 +1220,8 @@ namespace AssetStudio
|
||||
clip.m_StreamedClip = StreamedClip.ParseGI(reader);
|
||||
clip.m_DenseClip = DenseClip.ParseGI(reader);
|
||||
clip.m_ConstantClip = ConstantClip.ParseGI(reader);
|
||||
clip.m_ACLClip.ParseGI(reader);
|
||||
clip.m_ACLClip = new GIACLClip();
|
||||
clip.m_ACLClip.Read(reader);
|
||||
|
||||
reader.Position = pos;
|
||||
|
||||
@@ -1683,6 +1761,10 @@ namespace AssetStudio
|
||||
|
||||
m_SampleRate = reader.ReadSingle();
|
||||
m_WrapMode = reader.ReadInt32();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_aclType = reader.ReadInt32();
|
||||
}
|
||||
if (version[0] > 3 || (version[0] == 3 && version[1] >= 4)) //3.4 and up
|
||||
{
|
||||
m_Bounds = new AABB(reader);
|
||||
@@ -1746,14 +1828,16 @@ namespace AssetStudio
|
||||
m_StreamData = new StreamingInfo(reader);
|
||||
if (!string.IsNullOrEmpty(m_StreamData?.path))
|
||||
{
|
||||
var aclClip = m_MuscleClip.m_Clip.m_ACLClip as GIACLClip;
|
||||
|
||||
var resourceReader = new ResourceReader(m_StreamData.path, assetsFile, m_StreamData.offset, m_StreamData.size);
|
||||
var ms = new MemoryStream();
|
||||
ms.Write(m_MuscleClip.m_Clip.m_ACLClip.m_DatabaseData);
|
||||
ms.Write(aclClip.m_DatabaseData);
|
||||
|
||||
ms.Write(resourceReader.GetData());
|
||||
ms.AlignStream();
|
||||
|
||||
m_MuscleClip.m_Clip.m_ACLClip.m_DatabaseData = ms.ToArray();
|
||||
aclClip.m_DatabaseData = ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,10 @@ namespace AssetStudio
|
||||
|| (version[0] == 4 && version[1] == 1 && version[2] >= 3)) //4.1.3 and up
|
||||
{
|
||||
m_CycleOffset = reader.ReadSingle();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_StateNameHash = reader.ReadUInt32();
|
||||
}
|
||||
m_Mirror = reader.ReadBoolean();
|
||||
reader.AlignStream();
|
||||
}
|
||||
@@ -411,6 +415,12 @@ namespace AssetStudio
|
||||
m_Mirror = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_SyncGroupID = reader.ReadUInt32();
|
||||
var m_SyncGroupRole = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
reader.AlignStream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,49 +13,53 @@ namespace AssetStudio
|
||||
m_Texture = new PPtr<Texture>(reader);
|
||||
m_Scale = reader.ReadVector2();
|
||||
m_Offset = reader.ReadVector2();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_UVSetIndex = reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UnityPropertySheet
|
||||
{
|
||||
public Dictionary<string, UnityTexEnv> m_TexEnvs;
|
||||
public Dictionary<string, int> m_Ints;
|
||||
public Dictionary<string, float> m_Floats;
|
||||
public Dictionary<string, Color> m_Colors;
|
||||
public KeyValuePair<string, UnityTexEnv>[] m_TexEnvs;
|
||||
public KeyValuePair<string, int>[] m_Ints;
|
||||
public KeyValuePair<string, float>[] m_Floats;
|
||||
public KeyValuePair<string, Color>[] m_Colors;
|
||||
|
||||
public UnityPropertySheet(ObjectReader reader)
|
||||
{
|
||||
var version = reader.version;
|
||||
|
||||
int m_TexEnvsSize = reader.ReadInt32();
|
||||
m_TexEnvs = new Dictionary<string, UnityTexEnv>(m_TexEnvsSize);
|
||||
m_TexEnvs = new KeyValuePair<string, UnityTexEnv>[m_TexEnvsSize];
|
||||
for (int i = 0; i < m_TexEnvsSize; i++)
|
||||
{
|
||||
m_TexEnvs.Add(reader.ReadAlignedString(), new UnityTexEnv(reader));
|
||||
m_TexEnvs[i] = new(reader.ReadAlignedString(), new UnityTexEnv(reader));
|
||||
}
|
||||
|
||||
if (version[0] >= 2021) //2021.1 and up
|
||||
{
|
||||
int m_IntsSize = reader.ReadInt32();
|
||||
m_Ints = new Dictionary<string, int>(m_IntsSize);
|
||||
m_Ints = new KeyValuePair<string, int>[m_IntsSize];
|
||||
for (int i = 0; i < m_IntsSize; i++)
|
||||
{
|
||||
m_Ints.Add(reader.ReadAlignedString(), reader.ReadInt32());
|
||||
m_Ints[i] = new(reader.ReadAlignedString(), reader.ReadInt32());
|
||||
}
|
||||
}
|
||||
|
||||
int m_FloatsSize = reader.ReadInt32();
|
||||
m_Floats = new Dictionary<string, float>(m_FloatsSize);
|
||||
m_Floats = new KeyValuePair<string, float>[m_FloatsSize];
|
||||
for (int i = 0; i < m_FloatsSize; i++)
|
||||
{
|
||||
m_Floats.Add(reader.ReadAlignedString(), reader.ReadSingle());
|
||||
m_Floats[i] = new(reader.ReadAlignedString(), reader.ReadSingle());
|
||||
}
|
||||
|
||||
int m_ColorsSize = reader.ReadInt32();
|
||||
m_Colors = new Dictionary<string, Color>(m_ColorsSize);
|
||||
m_Colors = new KeyValuePair<string, Color>[m_ColorsSize];
|
||||
for (int i = 0; i < m_ColorsSize; i++)
|
||||
{
|
||||
m_Colors.Add(reader.ReadAlignedString(), reader.ReadColor4());
|
||||
m_Colors[i] = new(reader.ReadAlignedString(), reader.ReadColor4());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +471,7 @@ namespace AssetStudio
|
||||
private VertexData m_VertexData;
|
||||
private CompressedMesh m_CompressedMesh;
|
||||
private StreamingInfo m_StreamData;
|
||||
private bool m_CollisionMeshBaked = false;
|
||||
|
||||
public List<uint> m_Indices = new List<uint>();
|
||||
|
||||
@@ -549,6 +550,12 @@ namespace AssetStudio
|
||||
}
|
||||
var m_KeepVertices = reader.ReadBoolean();
|
||||
var m_KeepIndices = reader.ReadBoolean();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_CollisionMeshOnly = reader.ReadBoolean();
|
||||
m_CollisionMeshBaked = reader.ReadBoolean();
|
||||
var m_CollisionMeshConvex = reader.ReadBoolean();
|
||||
}
|
||||
}
|
||||
reader.AlignStream();
|
||||
if (reader.Game.Type.IsGISubGroup())
|
||||
@@ -641,7 +648,7 @@ namespace AssetStudio
|
||||
m_VertexData = new VertexData(reader);
|
||||
}
|
||||
|
||||
if (version[0] > 2 || (version[0] == 2 && version[1] >= 6)) //2.6.0 and later
|
||||
if ((version[0] > 2 || (version[0] == 2 && version[1] >= 6)) && !m_CollisionMeshBaked) //2.6.0 and later
|
||||
{
|
||||
m_CompressedMesh = new CompressedMesh(reader);
|
||||
}
|
||||
@@ -696,6 +703,10 @@ namespace AssetStudio
|
||||
var m_MeshMetrics = new float[2];
|
||||
m_MeshMetrics[0] = reader.ReadSingle();
|
||||
m_MeshMetrics[1] = reader.ReadSingle();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_MeshMetrics2 = reader.ReadSingle();
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.Game.Type.IsGIGroup())
|
||||
@@ -735,7 +746,12 @@ namespace AssetStudio
|
||||
ReadVertexData();
|
||||
}
|
||||
|
||||
if (version[0] > 2 || (version[0] == 2 && version[1] >= 6)) //2.6.0 and later
|
||||
if (m_CollisionMeshBaked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((version[0] > 2 || (version[0] == 2 && version[1] >= 6))) //2.6.0 and later
|
||||
{
|
||||
DecompressCompressedMesh();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,12 @@ namespace AssetStudio
|
||||
if (version[0] >= 2021) //2021.1 and up
|
||||
{
|
||||
var m_StaticShadowCaster = reader.ReadByte();
|
||||
if (reader.Game.Type.IsArknightsEndfield())
|
||||
{
|
||||
var m_RealtimeShadowCaster = reader.ReadByte();
|
||||
var m_SubMeshRenderMode = reader.ReadByte();
|
||||
var m_CharacterIndex = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
var m_MotionVectors = reader.ReadByte();
|
||||
var m_LightProbeUsage = reader.ReadByte();
|
||||
|
||||
305
AssetStudio/Crypto/FairGuardUtils.cs
Normal file
305
AssetStudio/Crypto/FairGuardUtils.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
//Special thanks to LukeFZ#4035.
|
||||
public static class FairGuardUtils
|
||||
{
|
||||
public static void Decrypt(Span<byte> bytes)
|
||||
{
|
||||
Logger.Verbose($"Attempting to decrypt block with FairGuard encryption...");
|
||||
|
||||
var encryptedOffset = 0;
|
||||
var encryptedSize = bytes.Length > 0x500 ? 0x500 : bytes.Length;
|
||||
|
||||
if (encryptedSize < 0x20)
|
||||
{
|
||||
Logger.Verbose($"block size is less that minimum, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
var encrypted = bytes.Slice(encryptedOffset, encryptedSize);
|
||||
var encryptedInts = MemoryMarshal.Cast<byte, int>(encrypted);
|
||||
|
||||
for (int i = 0; i < 0x20; i++)
|
||||
{
|
||||
encrypted[i] ^= 0xA6;
|
||||
}
|
||||
|
||||
var seedPart0 = (uint)(encryptedInts[2] ^ 0x1274CBEC ^ encryptedInts[6] ^ 0x3F72EAF3);
|
||||
var seedPart1 = (uint)(encryptedInts[3] ^ 0xBE482704 ^ encryptedInts[0] ^ encryptedSize);
|
||||
var seedPart2 = (uint)(encryptedInts[1] ^ encryptedSize ^ encryptedInts[5] ^ 0x753BDCAA);
|
||||
var seedPart3 = (uint)(encryptedInts[0] ^ 0x82C57E3C ^ encryptedInts[7] ^ 0xE3D947D3);
|
||||
var seedPart4 = (uint)(encryptedInts[4] ^ 0x6F2A7347 ^ encryptedInts[7] ^ 0x4736C714);
|
||||
|
||||
var seedInts = new uint[] { seedPart0, seedPart1, seedPart2, seedPart3, seedPart4 };
|
||||
var seedBytes = MemoryMarshal.AsBytes<uint>(seedInts);
|
||||
|
||||
var seed = GenerateSeed(seedBytes);
|
||||
var seedBuffer = BitConverter.GetBytes(seed);
|
||||
seed = CRC.CalculateDigest(seedBuffer, 0, (uint)seedBuffer.Length);
|
||||
|
||||
var key = seedInts[0] ^ seedInts[1] ^ seedInts[2] ^ seedInts[3] ^ seedInts[4] ^ (uint)encryptedSize;
|
||||
|
||||
RC4(seedBytes, key);
|
||||
var keySeed = CRC.CalculateDigest(seedBytes.ToArray(), 0, (uint)seedBytes.Length);
|
||||
var keySeedBytes = BitConverter.GetBytes(keySeed);
|
||||
keySeed = GenerateSeed(keySeedBytes);
|
||||
|
||||
var keyPart0 = (seedInts[3] - 0x1C26B82D) ^ keySeed;
|
||||
var keyPart1 = (seedInts[2] + 0x3F72EAF3) ^ seed;
|
||||
var keyPart2 = seedInts[0] ^ 0x82C57E3C ^ keySeed;
|
||||
var keyPart3 = (seedInts[1] + 0x6F2A7347) ^ seed;
|
||||
var keyVector = new uint[] { keyPart0, keyPart1, keyPart2, keyPart3 };
|
||||
|
||||
var block = encrypted[0x20..];
|
||||
if (block.Length >= 0x80)
|
||||
{
|
||||
RC4(block[..0x60], seed);
|
||||
for (int i = 0; i < 0x60; i++)
|
||||
{
|
||||
block[i] ^= (byte)(seed ^ 0x6E);
|
||||
}
|
||||
|
||||
block = block[0x60..];
|
||||
var blockSize = (encryptedSize - 0x80) / 4;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var blockOffset = i * blockSize;
|
||||
var blockKey = i switch
|
||||
{
|
||||
0 => 0x6142756Eu,
|
||||
1 => 0x62496E66u,
|
||||
2 => 0x1304B000u,
|
||||
3 => 0x6E8E30ECu,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
RC4(block.Slice(blockOffset, blockSize), seed);
|
||||
var blockInts = MemoryMarshal.Cast<byte, uint>(block[blockOffset..]);
|
||||
for (int j = 0; j < blockSize / 4; j++)
|
||||
{
|
||||
blockInts[j] ^= seed ^ keyVector[i] ^ blockKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RC4(block, seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint GenerateSeed(Span<byte> bytes)
|
||||
{
|
||||
var state = new uint[] { 0xC1646153, 0x78DA0550, 0x2947E56B };
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
state[0] = 0x21 * state[0] + bytes[i];
|
||||
if ((state[0] & 0xF) >= 0xB)
|
||||
{
|
||||
state[0] = (state[0] ^ RotateIsSet(state[2], 6)) - 0x2CD86315;
|
||||
}
|
||||
else if ((state[0] & 0xF0) >> 4 > 0xE)
|
||||
{
|
||||
state[0] = (state[1] ^ 0xAB4A010B) + (state[0] ^ RotateIsSet(state[2], 9));
|
||||
}
|
||||
else if ((state[0] & 0xF00) >> 8 < 2)
|
||||
{
|
||||
state[1] = ((state[2] >> 3) - 0x55EEAB7B) ^ state[0];
|
||||
}
|
||||
else if (state[1] + 0x567A >= 0xAB5489E4)
|
||||
{
|
||||
state[1] = (state[1] >> 16) ^ state[0];
|
||||
}
|
||||
else if ((state[1] ^ 0x738766FA) <= state[2])
|
||||
{
|
||||
state[1] = (state[1] >> 8) ^ state[2];
|
||||
}
|
||||
else if (state[1] == 0x68F53AA6)
|
||||
{
|
||||
if ((state[1] ^ (state[0] + state[2])) > 0x594AF86E)
|
||||
{
|
||||
state[1] -= 0x8CA292E;
|
||||
}
|
||||
else
|
||||
{
|
||||
state[2] -= 0x760A1649;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state[0] > 0x865703AF)
|
||||
{
|
||||
state[1] = state[2] ^ (state[0] - 0x564389D7);
|
||||
}
|
||||
else
|
||||
{
|
||||
state[1] = (state[1] - 0x12B9DD92) ^ state[0];
|
||||
}
|
||||
|
||||
state[0] ^= RotateIsSet(state[1], 8);
|
||||
}
|
||||
}
|
||||
|
||||
return state[0];
|
||||
}
|
||||
|
||||
private static uint RotateIsSet(uint value, int count) => (((value >> count) != 0) || ((value << (32 - count))) != 0) ? 1u : 0u;
|
||||
|
||||
public class CRC
|
||||
{
|
||||
private static readonly uint[] Table;
|
||||
|
||||
static CRC()
|
||||
{
|
||||
Table = new uint[256];
|
||||
const uint kPoly = 0xD35E417E;
|
||||
for (uint i = 0; i < 256; i++)
|
||||
{
|
||||
uint r = i;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
if ((r & 1) != 0)
|
||||
r = (r >> 1) ^ kPoly;
|
||||
else
|
||||
r >>= 1;
|
||||
}
|
||||
Table[i] = r;
|
||||
}
|
||||
}
|
||||
|
||||
uint _value = 0xFFFFFFFF;
|
||||
|
||||
public void Update(byte[] data, uint offset, uint size)
|
||||
{
|
||||
for (uint i = 0; i < size; i++)
|
||||
_value = (Table[(byte)_value ^ data[offset + i]] ^ (_value >> 9)) + 0x5B;
|
||||
}
|
||||
|
||||
public uint GetDigest() { return ~_value - 0x41607A3D; }
|
||||
|
||||
public static uint CalculateDigest(byte[] data, uint offset, uint size)
|
||||
{
|
||||
var crc = new CRC();
|
||||
crc.Update(data, offset, size);
|
||||
return crc.GetDigest();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RC4(Span<byte> data, uint key) => RC4(data, BitConverter.GetBytes(key));
|
||||
|
||||
public static void RC4(Span<byte> data, byte[] key)
|
||||
{
|
||||
int[] S = new int[0x100];
|
||||
for (int _ = 0; _ < 0x100; _++)
|
||||
{
|
||||
S[_] = _;
|
||||
}
|
||||
|
||||
int[] T = new int[0x100];
|
||||
|
||||
if (key.Length == 0x100)
|
||||
{
|
||||
Buffer.BlockCopy(key, 0, T, 0, key.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int _ = 0; _ < 0x100; _++)
|
||||
{
|
||||
T[_] = key[_ % key.Length];
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
for (i = 0; i < 0x100; i++)
|
||||
{
|
||||
j = (j + S[i] + T[i]) % 0x100;
|
||||
|
||||
(S[j], S[i]) = (S[i], S[j]);
|
||||
}
|
||||
|
||||
i = j = 0;
|
||||
for (int iteration = 0; iteration < data.Length; iteration++)
|
||||
{
|
||||
i = (i + 1) % 0x100;
|
||||
j = (j + S[i]) % 0x100;
|
||||
|
||||
(S[j], S[i]) = (S[i], S[j]);
|
||||
var K = (uint)S[(S[j] + S[i]) % 0x100];
|
||||
|
||||
var k = (byte)(K << 1) | (K >> 7);
|
||||
data[iteration] ^= (byte)(k - 0x61);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Lz4
|
||||
{
|
||||
public static void Decompress(ReadOnlySpan<byte> cmp, Span<byte> dec)
|
||||
{
|
||||
int cmpPos = 0;
|
||||
int decPos = 0;
|
||||
|
||||
// ReSharper disable once VariableHidesOuterVariable
|
||||
int GetLength(int length, ReadOnlySpan<byte> cmp)
|
||||
{
|
||||
byte sum;
|
||||
|
||||
if (length == 0xf)
|
||||
{
|
||||
do
|
||||
{
|
||||
length += sum = cmp[cmpPos++];
|
||||
} while (sum == 0xff);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
byte token = cmp[cmpPos++];
|
||||
|
||||
int encCount = (token >> 4) & 0xf;
|
||||
int litCount = (token >> 0) & 0xf;
|
||||
|
||||
//Copy literal chunk
|
||||
litCount = GetLength(litCount, cmp);
|
||||
|
||||
cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos));
|
||||
|
||||
cmpPos += litCount;
|
||||
decPos += litCount;
|
||||
|
||||
if (cmpPos >= cmp.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//Copy compressed chunk
|
||||
int back = cmp[cmpPos++] << 8 |
|
||||
cmp[cmpPos++] << 0;
|
||||
|
||||
encCount = GetLength(encCount, cmp) + 4;
|
||||
|
||||
int encPos = decPos - back;
|
||||
|
||||
if (encCount <= back)
|
||||
{
|
||||
dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos));
|
||||
|
||||
decPos += encCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (encCount-- > 0)
|
||||
{
|
||||
dec[decPos++] = dec[encPos++];
|
||||
}
|
||||
}
|
||||
} while (cmpPos < cmp.Length &&
|
||||
decPos < dec.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ namespace AssetStudio
|
||||
Games.Add(index++, new Game(GameType.CodenameJump));
|
||||
Games.Add(index++, new Game(GameType.GirlsFrontline));
|
||||
Games.Add(index++, new Game(GameType.Reverse1999));
|
||||
Games.Add(index++, new Game(GameType.ArknightsEndfield));
|
||||
}
|
||||
public static Game GetGame(GameType gameType) => GetGame((int)gameType);
|
||||
public static Game GetGame(int index)
|
||||
@@ -152,7 +153,8 @@ namespace AssetStudio
|
||||
ProjectSekai,
|
||||
CodenameJump,
|
||||
GirlsFrontline,
|
||||
Reverse1999
|
||||
Reverse1999,
|
||||
ArknightsEndfield
|
||||
}
|
||||
|
||||
public static class GameTypes
|
||||
@@ -174,6 +176,7 @@ namespace AssetStudio
|
||||
public static bool IsNaraka(this GameType type) => type == GameType.Naraka;
|
||||
public static bool IsOPFP(this GameType type) => type == GameType.OPFP;
|
||||
public static bool IsNetEase(this GameType type) => type == GameType.NetEase;
|
||||
public static bool IsArknightsEndfield(this GameType type) => type == GameType.ArknightsEndfield;
|
||||
public static bool IsGIGroup(this GameType type) => type switch
|
||||
{
|
||||
GameType.GI or GameType.GI_Pack or GameType.GI_CB1 or GameType.GI_CB2 or GameType.GI_CB3 or GameType.GI_CB3Pre => true,
|
||||
@@ -200,7 +203,7 @@ namespace AssetStudio
|
||||
|
||||
public static bool IsBlockFile(this GameType type) => type switch
|
||||
{
|
||||
GameType.BH3 or GameType.BH3Pre or GameType.SR or GameType.GI_Pack or GameType.TOT => true,
|
||||
GameType.BH3 or GameType.BH3Pre or GameType.SR or GameType.GI_Pack or GameType.TOT or GameType.ArknightsEndfield => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user