New game entries.
This commit is contained in:
@@ -156,7 +156,7 @@ namespace AssetStudio
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadHeaderAndBlocksInfo(EndianBinaryReader reader)
|
private void ReadHeaderAndBlocksInfo(FileReader reader)
|
||||||
{
|
{
|
||||||
if (m_Header.version >= 4)
|
if (m_Header.version >= 4)
|
||||||
{
|
{
|
||||||
@@ -208,7 +208,7 @@ namespace AssetStudio
|
|||||||
return blocksStream;
|
return blocksStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadBlocksAndDirectory(EndianBinaryReader reader, Stream blocksStream)
|
private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream)
|
||||||
{
|
{
|
||||||
var isCompressed = m_Header.signature == "UnityWeb";
|
var isCompressed = m_Header.signature == "UnityWeb";
|
||||||
foreach (var blockInfo in m_BlocksInfo)
|
foreach (var blockInfo in m_BlocksInfo)
|
||||||
@@ -278,7 +278,7 @@ namespace AssetStudio
|
|||||||
XORShift128.Init = false;
|
XORShift128.Init = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadHeader(EndianBinaryReader reader)
|
private void ReadHeader(FileReader reader)
|
||||||
{
|
{
|
||||||
if (Game.Type.IsBH3() && XORShift128.Init)
|
if (Game.Type.IsBH3() && XORShift128.Init)
|
||||||
{
|
{
|
||||||
@@ -309,7 +309,7 @@ namespace AssetStudio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadBlocksInfoAndDirectory(EndianBinaryReader reader)
|
private void ReadBlocksInfoAndDirectory(FileReader reader)
|
||||||
{
|
{
|
||||||
if (Game.Type.IsCNUnity())
|
if (Game.Type.IsCNUnity())
|
||||||
{
|
{
|
||||||
@@ -430,7 +430,7 @@ namespace AssetStudio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadBlocks(EndianBinaryReader reader, Stream blocksStream)
|
private void ReadBlocks(FileReader reader, Stream blocksStream)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_BlocksInfo.Length; i++)
|
for (int i = 0; i < m_BlocksInfo.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -464,6 +464,10 @@ namespace AssetStudio
|
|||||||
{
|
{
|
||||||
CNUnity.DecryptBlock(compressedBytesSpan, compressedSize, i);
|
CNUnity.DecryptBlock(compressedBytesSpan, compressedSize, i);
|
||||||
}
|
}
|
||||||
|
if (Game.Type.IsOPFP())
|
||||||
|
{
|
||||||
|
OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath);
|
||||||
|
}
|
||||||
var uncompressedSize = (int)blockInfo.uncompressedSize;
|
var uncompressedSize = (int)blockInfo.uncompressedSize;
|
||||||
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize);
|
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize);
|
||||||
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
|
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
|
||||||
|
|||||||
40
AssetStudio/Crypto/OPFPUtils.cs
Normal file
40
AssetStudio/Crypto/OPFPUtils.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace AssetStudio
|
||||||
|
{
|
||||||
|
public static class OPFPUtils
|
||||||
|
{
|
||||||
|
public static readonly string[] EncrytpedFolders = { "UI/", "Atlas/", "UITexture/", "DynamicAtlas/" };
|
||||||
|
|
||||||
|
public static void Decrypt(Span<byte> data, string path)
|
||||||
|
{
|
||||||
|
if (IsEncryptionBundle(path, out var key))
|
||||||
|
{
|
||||||
|
data[0] ^= key;
|
||||||
|
for (int i = 1; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
data[i] ^= data[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static bool IsEncryptionBundle(string path, out byte key)
|
||||||
|
{
|
||||||
|
path = path.Replace("\\", "/");
|
||||||
|
foreach(var encryptedFolder in EncrytpedFolders)
|
||||||
|
{
|
||||||
|
var index = path.IndexOf(encryptedFolder, 0, path.Length, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
var assetPath = path[index..];
|
||||||
|
if (assetPath.StartsWith(encryptedFolder, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
key = (byte)assetPath.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key = 0x00;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -148,6 +148,12 @@ namespace AssetStudio
|
|||||||
case GameType.OPFP:
|
case GameType.OPFP:
|
||||||
reader = ParseOPFP(reader);
|
reader = ParseOPFP(reader);
|
||||||
break;
|
break;
|
||||||
|
case GameType.AlchemyStars:
|
||||||
|
reader = ParseAlchemyStars(reader);
|
||||||
|
break;
|
||||||
|
case GameType.FantasyOfWind:
|
||||||
|
reader = DecryptFantasyOfWind(reader);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile())
|
if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile())
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace AssetStudio
|
|||||||
Games.Add(index++, new Game(GameType.Naraka));
|
Games.Add(index++, new Game(GameType.Naraka));
|
||||||
Games.Add(index++, new Game(GameType.EnsembleStars));
|
Games.Add(index++, new Game(GameType.EnsembleStars));
|
||||||
Games.Add(index++, new Game(GameType.OPFP));
|
Games.Add(index++, new Game(GameType.OPFP));
|
||||||
|
Games.Add(index++, new Game(GameType.AlchemyStars));
|
||||||
|
Games.Add(index++, new Game(GameType.FantasyOfWind));
|
||||||
}
|
}
|
||||||
public static Game GetGame(int index)
|
public static Game GetGame(int index)
|
||||||
{
|
{
|
||||||
@@ -123,7 +125,9 @@ namespace AssetStudio
|
|||||||
Naraka,
|
Naraka,
|
||||||
CNUnity,
|
CNUnity,
|
||||||
EnsembleStars,
|
EnsembleStars,
|
||||||
OPFP
|
OPFP,
|
||||||
|
AlchemyStars,
|
||||||
|
FantasyOfWind
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GameTypes
|
public static class GameTypes
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using static AssetStudio.BundleFile;
|
using static AssetStudio.BundleFile;
|
||||||
using static AssetStudio.Crypto;
|
using static AssetStudio.Crypto;
|
||||||
|
|
||||||
@@ -273,16 +274,91 @@ namespace AssetStudio
|
|||||||
|
|
||||||
public static FileReader ParseOPFP(FileReader reader)
|
public static FileReader ParseOPFP(FileReader reader)
|
||||||
{
|
{
|
||||||
MemoryStream ms = new();
|
var stream = reader.BaseStream;
|
||||||
|
var data = reader.ReadBytes(0x1000);
|
||||||
var data = reader.ReadBytes((int)reader.BaseStream.Length);
|
|
||||||
var idx = data.Search("UnityFS");
|
var idx = data.Search("UnityFS");
|
||||||
if (idx != -1)
|
if (idx != -1)
|
||||||
{
|
{
|
||||||
var count = data.Length - idx;
|
stream = new BlockStream(stream, idx);
|
||||||
ms = new(data, idx, count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new FileReader(reader.FullPath, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileReader ParseAlchemyStars(FileReader reader)
|
||||||
|
{
|
||||||
|
var stream = reader.BaseStream;
|
||||||
|
var data = reader.ReadBytes(0x1000);
|
||||||
|
var idx = data.Search("UnityFS");
|
||||||
|
if (idx != -1)
|
||||||
|
{
|
||||||
|
var idx2 = data[(idx + 1)..].Search("UnityFS");
|
||||||
|
if (idx2 != -1)
|
||||||
|
{
|
||||||
|
stream = new BlockStream(stream, idx + idx2 + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream = new BlockStream(stream, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileReader(reader.FullPath, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileReader DecryptFantasyOfWind(FileReader reader)
|
||||||
|
{
|
||||||
|
byte[] encryptKeyName = Encoding.UTF8.GetBytes("28856");
|
||||||
|
const int MinLength = 0xC8;
|
||||||
|
const int KeyLength = 8;
|
||||||
|
const int EnLength = 0x32;
|
||||||
|
const int StartEnd = 0x14;
|
||||||
|
const int HeadLength = 5;
|
||||||
|
|
||||||
|
var signature = reader.ReadStringToNull(HeadLength);
|
||||||
|
if (string.Compare(signature, "K9999") > 0 || reader.Length <= MinLength)
|
||||||
|
{
|
||||||
|
reader.Position = 0;
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Position = reader.Length + ~StartEnd;
|
||||||
|
var keyLength = reader.ReadByte();
|
||||||
|
reader.Position = reader.Length - StartEnd - 2;
|
||||||
|
var enLength = reader.ReadByte();
|
||||||
|
|
||||||
|
var enKeyPos = (byte)((keyLength % KeyLength) + KeyLength);
|
||||||
|
var encryptedLength = (byte)((enLength % EnLength) + EnLength);
|
||||||
|
|
||||||
|
reader.Position = reader.Length - StartEnd - enKeyPos;
|
||||||
|
var encryptKey = reader.ReadBytes(KeyLength);
|
||||||
|
|
||||||
|
var subByte = (byte)(reader.Length - StartEnd - KeyLength - (keyLength % KeyLength));
|
||||||
|
for (var i = 0; i < KeyLength; i++)
|
||||||
|
{
|
||||||
|
if (encryptKey[i] == 0)
|
||||||
|
{
|
||||||
|
encryptKey[i] = (byte)(subByte + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = new byte[encryptKeyName.Length + KeyLength];
|
||||||
|
encryptKeyName.CopyTo(key.AsMemory(0));
|
||||||
|
encryptKey.CopyTo(key.AsMemory(encryptKeyName.Length));
|
||||||
|
|
||||||
|
reader.Position = HeadLength;
|
||||||
|
var data = reader.ReadBytes(encryptedLength);
|
||||||
|
for (int i = 0; i < encryptedLength; i++)
|
||||||
|
{
|
||||||
|
data[i] ^= key[i % key.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryStream ms = new();
|
||||||
|
ms.Write(Encoding.UTF8.GetBytes("Unity"));
|
||||||
|
ms.Write(data);
|
||||||
|
reader.BaseStream.CopyTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
return new FileReader(reader.FullPath, ms);
|
return new FileReader(reader.FullPath, ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user