This commit is contained in:
Razmoth
2023-03-12 22:14:22 +04:00
parent 701d76faad
commit 63f4b24b71
29 changed files with 1533 additions and 2237 deletions

View File

@@ -13,11 +13,5 @@
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.4-beta" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
<None Update="Keys.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -443,7 +443,7 @@ namespace AssetStudio
foreach (var offset in offsets)
{
stream.Offset = offset;
var dummyPath = Path.Combine("//?/block:/", reader.FileName, offset.ToString("X8"));
var dummyPath = Path.Combine(reader.FileName, offset.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
LoadBundleFile(subReader, reader.FullPath, offset);
}
@@ -454,7 +454,7 @@ namespace AssetStudio
do
{
stream.Offset = stream.RelativePosition;
var dummyPath = Path.Combine("//?/block:/", reader.FileName, stream.RelativePosition.ToString("X8"));
var dummyPath = Path.Combine(reader.FileName, stream.RelativePosition.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
LoadBundleFile(subReader, reader.FullPath, stream.RelativePosition);
} while (stream.Remaining > 0);
@@ -480,7 +480,7 @@ namespace AssetStudio
foreach (var offset in offsets)
{
stream.Offset = offset;
var dummyPath = Path.Combine("//?/blk:/", reader.FileName, offset.ToString("X8"));
var dummyPath = Path.Combine(reader.FileName, offset.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
switch (subReader.FileType)
{
@@ -499,7 +499,7 @@ namespace AssetStudio
do
{
stream.Offset = stream.RelativePosition;
var dummyPath = Path.Combine("//?/blk:/", reader.FileName, stream.RelativePosition.ToString("X8"));
var dummyPath = Path.Combine(reader.FileName, stream.RelativePosition.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
switch (subReader.FileType)
{
@@ -626,10 +626,10 @@ namespace AssetStudio
case ClassIDType.Animation:
obj = new Animation(objectReader);
break;
case ClassIDType.AnimationClip:
case ClassIDType.AnimationClip when ExportableTypes[ClassIDType.AnimationClip]:
obj = new AnimationClip(objectReader);
break;
case ClassIDType.Animator:
case ClassIDType.Animator when ExportableTypes[ClassIDType.Animator]:
obj = new Animator(objectReader);
break;
case ClassIDType.AnimatorController:
@@ -638,7 +638,7 @@ namespace AssetStudio
case ClassIDType.AnimatorOverrideController:
obj = new AnimatorOverrideController(objectReader);
break;
case ClassIDType.AssetBundle:
case ClassIDType.AssetBundle when ExportableTypes[ClassIDType.AssetBundle]:
obj = new AssetBundle(objectReader);
break;
case ClassIDType.AudioClip:
@@ -647,30 +647,29 @@ namespace AssetStudio
case ClassIDType.Avatar:
obj = new Avatar(objectReader);
break;
case ClassIDType.Font:
case ClassIDType.Font when ExportableTypes[ClassIDType.Font]:
obj = new Font(objectReader);
break;
case ClassIDType.GameObject:
case ClassIDType.GameObject when ExportableTypes[ClassIDType.GameObject]:
obj = new GameObject(objectReader);
break;
case ClassIDType.IndexObject:
case ClassIDType.IndexObject when ExportableTypes[ClassIDType.MiHoYoBinData]:
obj = new IndexObject(objectReader);
break;
case ClassIDType.Material:
case ClassIDType.Material when ExportableTypes[ClassIDType.Material]:
obj = new Material(objectReader);
break;
case ClassIDType.Mesh:
case ClassIDType.Mesh when ExportableTypes[ClassIDType.Mesh]:
obj = new Mesh(objectReader);
break;
case ClassIDType.MeshFilter:
obj = new MeshFilter(objectReader);
break;
case ClassIDType.MeshRenderer:
if (Renderer.Skipped)
goto default;
case ClassIDType.MeshRenderer when ExportableTypes[ClassIDType.Renderer]:
obj = new MeshRenderer(objectReader);
break;
case ClassIDType.MiHoYoBinData:
case ClassIDType.MiHoYoBinData when ExportableTypes[ClassIDType.MiHoYoBinData]:
obj = new MiHoYoBinData(objectReader);
break;
case ClassIDType.MonoBehaviour:
@@ -688,24 +687,22 @@ namespace AssetStudio
case ClassIDType.RectTransform:
obj = new RectTransform(objectReader);
break;
case ClassIDType.Shader:
case ClassIDType.Shader when ExportableTypes[ClassIDType.Shader]:
obj = new Shader(objectReader);
break;
case ClassIDType.SkinnedMeshRenderer:
if (Renderer.Skipped)
goto default;
case ClassIDType.SkinnedMeshRenderer when ExportableTypes[ClassIDType.Renderer]:
obj = new SkinnedMeshRenderer(objectReader);
break;
case ClassIDType.Sprite:
case ClassIDType.Sprite when ExportableTypes[ClassIDType.Sprite]:
obj = new Sprite(objectReader);
break;
case ClassIDType.SpriteAtlas:
obj = new SpriteAtlas(objectReader);
break;
case ClassIDType.TextAsset:
case ClassIDType.TextAsset when ExportableTypes[ClassIDType.TextAsset]:
obj = new TextAsset(objectReader);
break;
case ClassIDType.Texture2D:
case ClassIDType.Texture2D when ExportableTypes[ClassIDType.Texture2D]:
obj = new Texture2D(objectReader);
break;
case ClassIDType.Transform:

View File

@@ -15,7 +15,6 @@ namespace AssetStudio
BlocksInfoAtTheEnd = 0x80,
OldWebPluginCompatibility = 0x100,
BlockInfoNeedPaddingAtStart = 0x200,
CNUnityEncryption = 0x400
}
[Flags]
@@ -23,7 +22,6 @@ namespace AssetStudio
{
CompressionTypeMask = 0x3f,
Streamed = 0x40,
CNUnity = 0x100
}
public enum CompressionType
@@ -66,7 +64,6 @@ namespace AssetStudio
}
private Game Game;
private CNUnity CNUnity;
public Header m_Header;
private Node[] m_DirectoryInfo;
@@ -311,30 +308,6 @@ namespace AssetStudio
private void ReadBlocksInfoAndDirectory(FileReader reader)
{
if (Game.Type.IsCNUnity())
{
ArchiveFlags mask;
var version = ParseVersion();
//Flag changed it in these versions
if (version[0] < 2020 || //2020 and earlier
(version[0] == 2020 && version[1] == 3 && version[2] <= 34) || //2020.3.34 and earlier
(version[0] == 2021 && version[1] == 3 && version[2] <= 2) || //2021.3.2 and earlier
(version[0] == 2022 && version[1] == 3 && version[2] <= 1)) //2022.3.1 and earlier
{
mask = ArchiveFlags.BlockInfoNeedPaddingAtStart;
}
else
{
mask = ArchiveFlags.CNUnityEncryption;
}
if ((m_Header.flags & mask) != 0)
{
CNUnity = new CNUnity(reader);
}
}
byte[] blocksInfoBytes;
if (m_Header.version >= 7 && !Game.Type.IsSRGroup())
{
@@ -424,7 +397,7 @@ namespace AssetStudio
};
}
}
if (!Game.Type.IsCNUnity() && (m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
if ((m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
{
reader.AlignStream(16);
}
@@ -460,10 +433,6 @@ namespace AssetStudio
{
compressedBytesSpan = Mr0kUtils.Decrypt(compressedBytesSpan, (Mr0k)Game);
}
if (Game.Type.IsCNUnity() && (blockInfo.flags & StorageBlockFlags.CNUnity) != 0)
{
CNUnity.DecryptBlock(compressedBytesSpan, compressedSize, i);
}
if (Game.Type.IsOPFP())
{
OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath);

View File

@@ -19,8 +19,6 @@ namespace AssetStudio
public abstract class Renderer : Component
{
public static bool Skipped;
public PPtr<Material>[] m_Materials;
public StaticBatchInfo m_StaticBatchInfo;
public uint[] m_SubsetIndices;

View File

@@ -1,158 +0,0 @@
using System;
using System.Text;
using System.Security.Cryptography;
namespace AssetStudio
{
public class CNUnity
{
private const string Signature = "#$unity3dchina!@";
public static ICryptoTransform Encryptor;
public byte[] Index = new byte[0x10];
public byte[] Sub = new byte[0x10];
public CNUnity(EndianBinaryReader reader)
{
reader.ReadUInt32();
var infoBytes = reader.ReadBytes(0x10);
var infoKey = reader.ReadBytes(0x10);
reader.Position += 1;
var signatureBytes = reader.ReadBytes(0x10);
var signatureKey = reader.ReadBytes(0x10);
reader.Position += 1;
DecryptKey(signatureKey, signatureBytes);
var str = Encoding.UTF8.GetString(signatureBytes);
if (str != Signature)
throw new Exception("Invalid Signature !!");
DecryptKey(infoKey, infoBytes);
infoBytes = infoBytes.ToUInt4Array();
infoBytes.AsSpan(0, 0x10).CopyTo(Index);
var subBytes = infoBytes.AsSpan(0x10, 0x10);
for (var i = 0; i < subBytes.Length; i++)
{
var idx = (i % 4 * 4) + (i / 4);
Sub[idx] = subBytes[i];
}
}
public static bool SetKey(Entry entry)
{
try
{
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Key = entry.GenerateKey();
Encryptor = aes.CreateEncryptor();
}
catch(Exception e)
{
Logger.Error($"[CNUnity] Invalid key !!\n{e.Message}");
return false;
}
return true;
}
public void DecryptBlock(Span<byte> bytes, int size, int index)
{
var offset = 0;
while (offset < size)
{
offset += Decrypt(bytes[offset..], index++, size - offset);
}
}
private void DecryptKey(byte[] key, byte[] data)
{
if (Encryptor != null)
{
key = Encryptor.TransformFinalBlock(key, 0, key.Length);
for (int i = 0; i < 0x10; i++)
data[i] ^= key[i];
}
}
private int DecryptByte(Span<byte> bytes, ref int offset, ref int index)
{
var b = Sub[((index >> 2) & 3) + 4] + Sub[index & 3] + Sub[((index >> 4) & 3) + 8] + Sub[((byte)index >> 6) + 12];
bytes[offset] = (byte)((Index[bytes[offset] & 0xF] - b) & 0xF | 0x10 * (Index[bytes[offset] >> 4] - b));
b = bytes[offset];
offset++;
index++;
return b;
}
private int Decrypt(Span<byte> bytes, int index, int remaining)
{
var offset = 0;
var curByte = DecryptByte(bytes, ref offset, ref index);
var byteHigh = curByte >> 4;
var byteLow = curByte & 0xF;
if (byteHigh == 0xF)
{
int b;
do
{
b = DecryptByte(bytes, ref offset, ref index);
byteHigh += b;
} while (b == 0xFF);
}
offset += byteHigh;
if (offset < remaining)
{
DecryptByte(bytes, ref offset, ref index);
DecryptByte(bytes, ref offset, ref index);
if (byteLow == 0xF)
{
int b;
do
{
b = DecryptByte(bytes, ref offset, ref index);
} while(b == 0xFF);
}
}
return offset;
}
public record Entry
{
public string Name { get; private set; }
public string Key { get; private set; }
public Entry(string name, string key)
{
Name = name;
Key = key;
}
public bool Validate()
{
var bytes = GenerateKey();
if (bytes.Length != 0x10)
{
Logger.Warning($"[CNUnity] {this} has invalid key, size should be 16 bytes, skipping...");
return false;
}
return true;
}
public byte[] GenerateKey() => Convert.FromHexString(Key);
public override string ToString() => $"{Name} ({Key})";
}
}
}

View File

@@ -12,7 +12,6 @@ namespace AssetStudio
{
int index = 0;
Games.Add(index++, new(GameType.Normal));
Games.Add(index++, new Game(GameType.CNUnity));
Games.Add(index++, new Mhy0(GameType.GI, GIMhy0ShiftRow, GIMhy0Key, GIMhy0Mul, GIExpansionKey, GISBox, GIInitVector, GIInitSeed));
Games.Add(index++, new Mr0k(GameType.GI_Pack, PackExpansionKey, blockKey: PackBlockKey));
Games.Add(index++, new Mr0k(GameType.GI_CB1));
@@ -31,6 +30,7 @@ namespace AssetStudio
Games.Add(index++, new Game(GameType.FantasyOfWind));
Games.Add(index++, new Game(GameType.ShiningNikki));
}
public static Game GetGame(GameType gameType) => GetGame((int)gameType);
public static Game GetGame(int index)
{
if (!Games.TryGetValue(index, out var format))
@@ -124,7 +124,6 @@ namespace AssetStudio
SR_CB3,
TOT,
Naraka,
CNUnity,
EnsembleStars,
OPFP,
AlchemyStars,
@@ -147,7 +146,6 @@ namespace AssetStudio
public static bool IsSRCB3(this GameType type) => type == GameType.SR_CB3;
public static bool IsTOT(this GameType type) => type == GameType.TOT;
public static bool IsNaraka(this GameType type) => type == GameType.Naraka;
public static bool IsCNUnity(this GameType type) => type == GameType.CNUnity;
public static bool IsOPFP(this GameType type) => type == GameType.OPFP;
public static bool IsGIGroup(this GameType type) => type switch
{

View File

@@ -1,70 +0,0 @@
[
{
"Name": "PGR GLB/KR",
"Key": "6B75726F6B75726F6B75726F6B75726F"
},
{
"Name": "PGR CN/JP/TW",
"Key": "7935585076714C4F72436F6B57524961"
},
{
"Name": "Archeland/Kalpa of Universe",
"Key": "426C61636B4A61636B50726F6A656374"
},
{
"Name": "Archeland 1.1.14",
"Key": "50726F6A65637441726368654C616E64"
},
{
"Name": "Neural Cloud",
"Key": "31636162383436663532393031633965"
},
{
"Name": "Higan: Eruthyll",
"Key": "45317832633361346C35693662377572"
},
{
"Name": "White Chord",
"Key": "79756C6F6E6731383638676E6F6C7579"
},
{
"Name": "Mecharashi",
"Key": "33384338334631333245374637413041"
},
{
"Name": "Castlevania: Moon Night Fantasy",
"Key": "31323334353637383132333435363738"
},
{
"Name": "Huā Yì Shān Xīn Zhī Yuè",
"Key": "494E484A6E68647970716B3534377864"
},
{
"Name": "Doula Continent",
"Key": "52346366773339474644326661785756"
},
{
"Name": "Bless Global",
"Key": "6C6F6E67747567616D652E796A66623F"
},
{
"Name": "Starside",
"Key": "41394A3542384D4A50554D3539464B57"
},
{
"Name": "Resonance Soltice",
"Key": "5265736F6E616E63655265626F726E52"
},
{
"Name": "Oblivion Override",
"Key": "7179666D6F6F6E323331323433343532"
},
{
"Name": "Dawnlands",
"Key": "636F6465737339353237636F64657373"
},
{
"Name": "BB",
"Key": "5F6C4E3F3A3F233F3F3F3F663F1A3F3F"
}
]

View File

@@ -1,68 +0,0 @@
using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Reflection;
namespace AssetStudio
{
public static class CNUnityKeyManager
{
public const string KeysFileName = "Keys.json";
private static List<CNUnity.Entry> Entries = new List<CNUnity.Entry>();
static CNUnityKeyManager()
{
var str = File.ReadAllText(KeysFileName);
Entries = JsonConvert.DeserializeObject<List<CNUnity.Entry>>(str);
}
public static void SaveEntries(List<CNUnity.Entry> entries)
{
Entries.Clear();
Entries.AddRange(entries);
var str = JsonConvert.SerializeObject(Entries);
File.WriteAllText(KeysFileName, str);
}
public static void SetKey(int index)
{
if (TryGetEntry(index, out var cnunity))
{
if (CNUnity.SetKey(cnunity))
{
Logger.Info($"[CNUnity] Selected Key is {cnunity}");
}
else
{
Logger.Info($"[CNUnity] No Key is selected !!");
}
}
}
public static bool TryGetEntry(int index, out CNUnity.Entry key)
{
try
{
if (index < 0 || index > Entries.Count)
{
throw new ArgumentOutOfRangeException();
}
key = Entries[index];
}
catch(Exception e)
{
Logger.Error($"[CNUnity] Invalid Index, check if list is not empty !!\n{e.Message}");
key = null;
return false;
}
return true;
}
public static CNUnity.Entry[] GetEntries() => Entries.ToArray();
}
}