v0.80.36
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user