- [Core] Added new entry

This commit is contained in:
Razmoth
2023-11-11 19:21:46 +04:00
parent e863310354
commit 740b8725b2
12 changed files with 524 additions and 62 deletions

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}

View File

@@ -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();

View 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);
}
}
}
}

View File

@@ -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,
};