diff --git a/FGOAssetsModifyTool/CatAndMouseGame.cs b/FGOAssetsModifyTool/CatAndMouseGame.cs index 67acc7d..e9a36ca 100644 --- a/FGOAssetsModifyTool/CatAndMouseGame.cs +++ b/FGOAssetsModifyTool/CatAndMouseGame.cs @@ -36,7 +36,7 @@ public static string GetMD5String(string input) } } - private FileType fileType; + public FileType fileType; public CatAndMouseGame(FileType _) { fileType = _; @@ -256,14 +256,13 @@ public static void OtherHomeBuilding(string data, out byte[] home, out byte[] in else info[i] = bytes[i]; } - public byte[] MouseGame4(byte[] data, string key) { byte[] info; byte[] home; OtherHomeBuilding(key, out home, out info); - byte[] array = MouseHomeMain(data, baseData, baseTop, false); + byte[] array = MouseHomeMain(data, home, info, false); if (array == null) { Console.WriteLine("MouseHomeMain failed"); diff --git a/FGOAssetsModifyTool/Configuration.cs b/FGOAssetsModifyTool/Configuration.cs index de364c5..98fe069 100644 --- a/FGOAssetsModifyTool/Configuration.cs +++ b/FGOAssetsModifyTool/Configuration.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Text.Json.Nodes; namespace FGOAssetsModifyTool { @@ -9,11 +10,15 @@ public static class Configuration public static DirectoryInfo ScriptsFolder = new DirectoryInfo(NowPath + @"\Android\Scripts"); public static string GameDataFolder = new DirectoryInfo(NowPath + @"\Android\gamedata\").FullName; public static string GameDataUnpackFolder = new DirectoryInfo(NowPath + @"\Android\gamedata\unpack_master\").FullName; + public static string GameDataUnpackAssetBundleFolder = new DirectoryInfo(NowPath + @"\Android\gamedata\unpack_assetbundle\").FullName; + public static string GameDataRawPath = new DirectoryInfo(NowPath + @"\Android\gamedata\raw").FullName; + public static string GameDataMasterPath = new DirectoryInfo(NowPath + @"\Android\gamedata\master").FullName; + public static string GameDataAssetBundlePath = new DirectoryInfo(NowPath + @"\Android\gamedata\assetbundle").FullName; + public static string GameDataAssetBundleKeyPath = new DirectoryInfo(NowPath + @"\Android\gamedata\assetbundleKey").FullName; public static DirectoryInfo DecryptedFolder = new DirectoryInfo(NowPath + @"\Decrypted\"); public static string EncryptedFolder = new DirectoryInfo(NowPath + @"\Encrypted\").FullName; public static DirectoryInfo DecryptedScriptsFolder = new DirectoryInfo(NowPath + @"\DecryptedScripts\"); public static string EncryptedScriptsFolder = new DirectoryInfo(NowPath + @"\EncryptedScripts\").FullName; - static Configuration() { if (!Directory.Exists(AssetsFolder.FullName)) @@ -28,6 +33,9 @@ static Configuration() if (!Directory.Exists(GameDataUnpackFolder)) Directory.CreateDirectory(GameDataUnpackFolder); + if (!Directory.Exists(GameDataUnpackAssetBundleFolder)) + Directory.CreateDirectory(GameDataUnpackAssetBundleFolder); + if (!Directory.Exists(DecryptedFolder.FullName)) Directory.CreateDirectory(DecryptedFolder.FullName); diff --git a/FGOAssetsModifyTool/MasterDataUnpacker.cs b/FGOAssetsModifyTool/MasterDataUnpacker.cs deleted file mode 100644 index de63996..0000000 --- a/FGOAssetsModifyTool/MasterDataUnpacker.cs +++ /dev/null @@ -1,505 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace FGOAssetsModifyTool -{ - public class MasterDataUnpacker - { - public object Unpack(byte[] buf, int offset, int size) - { - object result; - using (MemoryStream memoryStream = new MemoryStream(buf, offset, size)) - { - result = this.Unpack(memoryStream); - } - return result; - } - public object Unpack(byte[] buf) - { - return this.Unpack(buf, 0, buf.Length); - } - public void UnpackByte(Stream s, Stream ws) - { - int num = s.ReadByte(); - ws.WriteByte((byte)num); - if (num < 0) - { - throw new FormatException(); - } - if (num > 127) - { - if (num <= 143) - { - this.UnpackMapByte(num, s, ws); - } - else if (num <= 159) - { - this.UnpackArrayByte(num, s, ws); - } - else if (num <= 191) - { - this.UnpackBinary(num, s, ws); - } - else if (num >= 224) - { - } - } - switch (num) - { - case 196: - case 197: - case 198: - case 217: - case 218: - case 219: - this.UnpackBinary(num, s, ws); - break; - case 202: - s.Read(this.tmp0, 0, 4); - ws.Write(this.tmp0, 0, 4); - break; - case 203: - s.Read(this.tmp0, 0, 8); - ws.Write(this.tmp0, 0, 8); - break; - case 204: - ws.WriteByte((byte)s.ReadByte()); - break; - case 205: - s.Read(this.tmp0, 0, 2); - ws.Write(this.tmp0, 0, 2); - break; - case 206: - s.Read(this.tmp0, 0, 4); - ws.Write(this.tmp0, 0, 4); - break; - case 207: - if (s.Read(this.tmp0, 0, 8) != 8) - { - throw new FormatException(); - } - ws.Write(this.tmp0, 0, 8); - break; - case 208: - ws.WriteByte((byte)s.ReadByte()); - break; - case 209: - if (s.Read(this.tmp0, 0, 2) != 2) - { - throw new FormatException(); - } - ws.Write(this.tmp0, 0, 2); - break; - case 210: - if (s.Read(this.tmp0, 0, 4) != 4) - { - throw new FormatException(); - } - ws.Write(this.tmp0, 0, 4); - break; - case 211: - if (s.Read(this.tmp0, 0, 8) != 8) - { - throw new FormatException(); - } - ws.Write(this.tmp0, 0, 8); - break; - case 220: - case 221: - this.UnpackArrayByte(num, s, ws); - break; - case 222: - case 223: - this.UnpackMapByte(num, s, ws); - break; - } - } - public void UnpackBinary(int b, Stream s, Stream ws) - { - if (b <= 191) - { - int num = b & 31; - if (num != 0) - { - byte[] array = new byte[num]; - s.Read(array, 0, num); - ws.Write(array, 0, num); - } - } - else - { - switch (b) - { - case 196: - break; - case 197: - goto IL_9D; - case 198: - goto IL_EE; - default: - switch (b) - { - case 217: - break; - case 218: - goto IL_9D; - case 219: - goto IL_EE; - default: - return; - } - break; - } - int num2 = s.ReadByte(); - byte[] array2 = new byte[num2]; - s.Read(array2, 0, num2); - ws.WriteByte((byte)num2); - ws.Write(array2, 0, num2); - return; - IL_9D: - byte[] array3 = new byte[2]; - s.Read(array3, 0, 2); - byte[] array4 = new byte[(long)((int)array3[0] << 8 | (int)array3[1])]; - s.Read(array4, 0, array4.Length); - ws.Write(array3, 0, 2); - ws.Write(array4, 0, array4.Length); - return; - IL_EE: - byte[] array5 = new byte[4]; - s.Read(array5, 0, 4); - byte[] array6 = new byte[(long)array5[0] << 24 | (long)array5[1] << 16 | (long)array5[2] << 8 | (long)array5[3]]; - s.Read(array6, 0, array6.Length); - ws.Write(array5, 0, 4); - ws.Write(array6, 0, array6.Length); - } - } - public void UnpackArrayByte(int b, Stream s, Stream ws) - { - long num = 0L; - if (b <= 159) - { - num = (long)(b & 15); - } - else if (b != 220) - { - if (b == 221) - { - s.Read(this.tmp0, 0, 4); - num = ((long)this.tmp0[0] << 24 | (long)this.tmp0[1] << 16 | (long)this.tmp0[2] << 8 | (long)this.tmp0[3]); - ws.Write(this.tmp0, 0, 4); - } - } - else - { - s.Read(this.tmp0, 0, 2); - num = (long)((int)this.tmp0[0] << 8 | (int)this.tmp0[1]); - ws.Write(this.tmp0, 0, 2); - } - int num2 = 0; - while ((long)num2 < num) - { - this.UnpackByte(s, ws); - num2++; - } - } - public void UnpackMapByte(int b, Stream s, Stream ws) - { - long num = 0L; - if (b <= 143) - { - num = (long)(b & 15); - } - else if (b != 222) - { - if (b == 223) - { - s.Read(this.tmp0, 0, 4); - num = ((long)this.tmp0[0] << 24 | (long)this.tmp0[1] << 16 | (long)this.tmp0[2] << 8 | (long)this.tmp0[3]); - ws.Write(this.tmp0, 0, 4); - } - } - else - { - s.Read(this.tmp0, 0, 2); - num = (long)((int)this.tmp0[0] << 8 | (int)this.tmp0[1]); - ws.Write(this.tmp0, 0, 2); - } - int num2 = 0; - while ((long)num2 < num) - { - this.UnpackByte(s, ws); - this.UnpackByte(s, ws); - num2++; - } - } - public object Unpack(Stream s) - { - int num = s.ReadByte(); - if (num < 0) - { - throw new FormatException(); - } - if (num <= 127) - { - return (long)num; - } - if (num <= 143) - { - return this.UnpackMap(s, (long)(num & 15)); - } - if (num <= 159) - { - return this.UnpackArray(s, (long)(num & 15)); - } - if (num <= 191) - { - return this.UnpackString(s, (long)(num & 31)); - } - if (num >= 224) - { - return (long)((sbyte)num); - } - switch (num) - { - case 192: - return null; - case 194: - return false; - case 195: - return true; - case 196: - return this.UnpackBinary(s, (long)s.ReadByte()); - case 197: - return this.UnpackBinary(s, this.UnpackUint16(s)); - case 198: - return this.UnpackBinary(s, this.UnpackUint32(s)); - case 202: - s.Read(this.tmp0, 0, 4); - if (BitConverter.IsLittleEndian) - { - this.tmp1[0] = this.tmp0[3]; - this.tmp1[1] = this.tmp0[2]; - this.tmp1[2] = this.tmp0[1]; - this.tmp1[3] = this.tmp0[0]; - return (double)BitConverter.ToSingle(this.tmp1, 0); - } - return (double)BitConverter.ToSingle(this.tmp0, 0); - case 203: - s.Read(this.tmp0, 0, 8); - if (BitConverter.IsLittleEndian) - { - this.tmp1[0] = this.tmp0[7]; - this.tmp1[1] = this.tmp0[6]; - this.tmp1[2] = this.tmp0[5]; - this.tmp1[3] = this.tmp0[4]; - this.tmp1[4] = this.tmp0[3]; - this.tmp1[5] = this.tmp0[2]; - this.tmp1[6] = this.tmp0[1]; - this.tmp1[7] = this.tmp0[0]; - return BitConverter.ToDouble(this.tmp1, 0); - } - return BitConverter.ToDouble(this.tmp0, 0); - case 204: - return (long)s.ReadByte(); - case 205: - return this.UnpackUint16(s); - case 206: - return this.UnpackUint32(s); - case 207: - if (s.Read(this.tmp0, 0, 8) != 8) - { - throw new FormatException(); - } - return (long)this.tmp0[0] << 56 | (long)this.tmp0[1] << 48 | (long)this.tmp0[2] << 40 | ((long)this.tmp0[3] << 32) + ((long)this.tmp0[4] << 24) | (long)this.tmp0[5] << 16 | (long)this.tmp0[6] << 8 | (long)this.tmp0[7]; - case 208: - return (long)((sbyte)s.ReadByte()); - case 209: - if (s.Read(this.tmp0, 0, 2) != 2) - { - throw new FormatException(); - } - return (long)((sbyte)this.tmp0[0]) << 8 | (long)this.tmp0[1]; - case 210: - if (s.Read(this.tmp0, 0, 4) != 4) - { - throw new FormatException(); - } - return (long)((sbyte)this.tmp0[0]) << 24 | (long)this.tmp0[1] << 16 | (long)this.tmp0[2] << 8 | (long)this.tmp0[3]; - case 211: - if (s.Read(this.tmp0, 0, 8) != 8) - { - throw new FormatException(); - } - return (long)((sbyte)this.tmp0[0]) << 56 | (long)this.tmp0[1] << 48 | (long)this.tmp0[2] << 40 | ((long)this.tmp0[3] << 32) + ((long)this.tmp0[4] << 24) | (long)this.tmp0[5] << 16 | (long)this.tmp0[6] << 8 | (long)this.tmp0[7]; - case 217: - return this.UnpackString(s, (long)s.ReadByte()); - case 218: - return this.UnpackString(s, this.UnpackUint16(s)); - case 219: - return this.UnpackString(s, this.UnpackUint32(s)); - case 220: - return this.UnpackArray(s, this.UnpackUint16(s)); - case 221: - return this.UnpackArray(s, this.UnpackUint32(s)); - case 222: - return this.UnpackMap(s, this.UnpackUint16(s)); - case 223: - return this.UnpackMap(s, this.UnpackUint32(s)); - } - return null; - } - private long UnpackUint16(Stream s) - { - if (s.Read(this.tmp0, 0, 2) != 2) - { - throw new FormatException(); - } - return (long)((int)this.tmp0[0] << 8 | (int)this.tmp0[1]); - } - private long UnpackUint32(Stream s) - { - if (s.Read(this.tmp0, 0, 4) != 4) - { - throw new FormatException(); - } - return (long)this.tmp0[0] << 24 | (long)this.tmp0[1] << 16 | (long)this.tmp0[2] << 8 | (long)this.tmp0[3]; - } - private string UnpackString(Stream s, long len) - { - if (MasterDataUnpacker.sb == null) - { - MasterDataUnpacker.sb = new StringBuilder((int)len); - } - else - { - MasterDataUnpacker.sb.Length = 0; - MasterDataUnpacker.sb.EnsureCapacity((int)len); - } - uint num = 0u; - uint num2 = 0u; - uint num3 = 0u; - int num4 = 0; - while ((long)num4 < len) - { - uint num5 = (uint)s.ReadByte(); - if (num2 == 0u) - { - if (num5 < 128u) - { - MasterDataUnpacker.sb.Append((char)num5); - } - else if ((num5 & 224u) == 192u) - { - num = (num5 & 31u); - num3 = 1u; - num2 = 2u; - } - else if ((num5 & 240u) == 224u) - { - num = (num5 & 15u); - num3 = 1u; - num2 = 3u; - } - else if ((num5 & 248u) == 240u) - { - num = (num5 & 7u); - num3 = 1u; - num2 = 4u; - } - else if ((num5 & 252u) == 248u) - { - num = (num5 & 3u); - num3 = 1u; - num2 = 5u; - } - else if ((num5 & 254u) == 252u) - { - num = (num5 & 3u); - num3 = 1u; - num2 = 6u; - } - } - else if ((num5 & 192u) == 128u) - { - num = (num << 6 | (num5 & 63u)); - if ((num3 += 1u) >= num2) - { - if (num < 65536u) - { - MasterDataUnpacker.sb.Append((char)num); - } - else if (num < 1114112u) - { - num -= 65536u; - MasterDataUnpacker.sb.Append((char)((num >> 10) + 55296u)); - MasterDataUnpacker.sb.Append((char)((num & 1023u) + 56320u)); - } - num2 = 0u; - } - } - num4++; - } - return MasterDataUnpacker.sb.ToString(); - } - private byte[] UnpackBinary(Stream s, long len) - { - byte[] array = new byte[len]; - s.Read(array, 0, (int)len); - return array; - } - private List UnpackArray(Stream s, long len) - { - List list = new List((int)len); - for (long num = 0L; num < len; num += 1L) - { - list.Add(this.Unpack(s)); - } - return list; - } - private Dictionary UnpackMap(Stream s, long len) - { - Dictionary dictionary = new Dictionary((int)len); - for (long num = 0L; num < len; num += 1L) - { - string text = this.Unpack(s) as string; - this.writeStream.Position = 0L; - this.writeStream.SetLength(0L); - this.UnpackByte(s, this.writeStream); - if (text != null) - { - dictionary.Add(text, this.writeStream.ToArray()); - } - } - return dictionary; - } - private static StringBuilder sb; - private byte[] tmp0 = new byte[8]; - private byte[] tmp1 = new byte[8]; - private readonly MemoryStream writeStream = new MemoryStream(2000000); - - protected static byte[] ownerTop = new byte[32]; - protected static byte[] ownerData = new byte[32]; - protected static byte[] InfoTop = new byte[32]; - protected static byte[] infoData = new byte[32]; - public static byte[] MouseHomeSub(byte[] data, byte[] home, byte[] info, bool isCompress = false) - { - return CatAndMouseGame.MouseHomeMain(data, home, info, isCompress); - } - public static object MouseHomeMaster(byte[] data, byte[] home, byte[] info, bool isCompress = false) - { - byte[] buf = MouseHomeSub(data, home, info, isCompress); - MasterDataUnpacker MasterDataUnpacker = new MasterDataUnpacker(); - return MasterDataUnpacker.Unpack(buf); - } - public static object MouseGame2Unpacker(byte[] data, bool isCompress = false) - { - Array.Copy(data, 0, ownerTop, 0, 32); - byte[] array = new byte[data.Length - 32]; - Array.Copy(data, 32, array, 0, data.Length - 32); - ownerData = Encoding.UTF8.GetBytes("pX6q6xK2UymhFKcaGHHUlfXqfTsWF0uH"); - return MouseHomeMaster(array, ownerData, ownerTop, true); - } - } -} \ No newline at end of file diff --git a/FGOAssetsModifyTool/Program.cs b/FGOAssetsModifyTool/Program.cs index f4f6da0..13a26c0 100644 --- a/FGOAssetsModifyTool/Program.cs +++ b/FGOAssetsModifyTool/Program.cs @@ -5,27 +5,37 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Text.Json; +using System.Text.Unicode; +using System.Text.Encodings.Web; namespace FGOAssetsModifyTool { class Program { static CatAndMouseGame decryptor = new(CatAndMouseGame.FileType.JP); + static Dictionary AssetBundleKeyList = new(); + static Dictionary AssetBundleWithExtraKey = new(); static async void DisplayMenuAsync() { Console.Clear(); try { Console.WriteLine( + "初始化顺序:3->7->4->6->0\n" + + "之后直接选择:0\n" + + "注意:日服的AssetStorage.txt必须选择4下载,从游戏中提取的格式不同\n" + + "0: 载入assetbundleinfo\n" + "1: 加密\t" + "2: 解密\n" + - "3: 解密AssetStorage.txt\t" + - "4: 导出资源名 - 实际文件名\n" + - "5: 加密剧情文本(scripts)\n" + - "6: 解密剧情文本(scripts)\n" + - "7: 汉化UI\n" + - "8: 从服务器下载游戏数据\n" + - "9: 解密游戏数据\n" + + "3: 从服务器下载游戏数据\n" + + "4: 下载并解密AssetStorage.txt\t" + + "5: 解密AssetStorage.txt\t" + + "6: 解析AssetStorage.txt\n" + + "7: 解析AssetBundle&Key\t" + + "8: 解析Master\n" + + "9: 加密剧情文本(scripts)\n" + + "10: 解密剧情文本(scripts)\n" + + "11: 汉化UI\n" + "67: 切换为国服密钥\n" + "69: 切换为美服密钥"); int arg = Convert.ToInt32(Console.ReadLine()); @@ -42,6 +52,38 @@ static async void DisplayMenuAsync() decryptor = new(CatAndMouseGame.FileType.EN); break; } + case 0: + { + if (File.Exists($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundleKey.json")) + { + string assetbundlekey_str = File.ReadAllText($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundleKey.json"); + JsonArray assetbundlekey = JsonNode.Parse(assetbundlekey_str).AsArray(); + + foreach(JsonObject item in assetbundlekey) + { + AssetBundleKeyList.Add(item["id"].ToString(), item["decryptKey"].ToString()); + } + } + else + { + Console.WriteLine("先解析AssetBundle&Key"); + } + + if (File.Exists($"{Configuration.AssetsFolder.FullName}AssetListWithExtraKeyType.json")) + { + string assetbundleextrakeytype_str = File.ReadAllText($"{Configuration.AssetsFolder.FullName}AssetListWithExtraKeyType.json"); + JsonArray assetbundleextrakeytype = JsonNode.Parse(assetbundleextrakeytype_str).AsArray(); + foreach(var item in assetbundleextrakeytype) + { + AssetBundleWithExtraKey.Add(item["FileName"].ToString(), item["AssetName"].ToString()); + } + } + else + { + Console.WriteLine("先解析AssetStorage.txt"); + } + break; + } case 1: { foreach (FileInfo file in Configuration.DecryptedFolder.GetFiles("*.bin")) @@ -55,38 +97,122 @@ static async void DisplayMenuAsync() } case 2: { + if (decryptor.fileType == CatAndMouseGame.FileType.JP) + { + if (AssetBundleKeyList.Count == 0 || AssetBundleWithExtraKey.Count == 0) + { + Console.WriteLine("先载入assetbundleinfo"); + break; + } + } + foreach (FileInfo file in Configuration.AssetsFolder.GetFiles("*.bin")) { Console.WriteLine("Decrypt: " + file.FullName); byte[] raw = File.ReadAllBytes(file.FullName); - byte[] output = decryptor.MouseGame4(raw); - File.WriteAllBytes(Configuration.DecryptedFolder.FullName + file.Name, output); + byte[] output = null; + string keyType; + string key; + if (AssetBundleWithExtraKey.TryGetValue(file.Name, out keyType)) + { + if(AssetBundleKeyList.TryGetValue(keyType, out key)) + { + output = decryptor.MouseGame4(raw, key); + } + else + { + Console.WriteLine($"No such value for this key type: {keyType}"); + } + } + else + { + output = decryptor.MouseGame4(raw); + } + File.WriteAllBytes($"{Configuration.DecryptedFolder.FullName}{file.Name}", output); } break; } case 3: { - string data = File.ReadAllText(Configuration.AssetsFolder.FullName + "AssetStorage.txt"); - string loadData = decryptor.MouseGame8(data); - File.WriteAllText(Configuration.AssetsFolder.FullName + "AssetStorage_dec.txt", loadData); - Console.WriteLine("Writing file to: " + Configuration.AssetsFolder.FullName + "AssetStorage_dec.txt"); + HttpClient Client = new(); + var Response = Client.GetAsync("https://game.fate-go.jp/gamedata/top?appVer=0.0"); + string Result = await Response.Result.Content.ReadAsStringAsync(); + JsonObject res = JsonNode.Parse(Result).AsObject(); + if (res["response"][0]["fail"]["action"] != null) + { + if (res["response"][0]["fail"]["action"].ToString() == "app_version_up") + { + string NewVersion = res["response"][0]["fail"]["detail"].ToString(); + NewVersion = Regex.Replace(NewVersion, @".*新ver.:(.*)、現.*", "$1", RegexOptions.Singleline); + Console.WriteLine("new version: " + NewVersion.ToString()); + Response = Client.GetAsync("https://game.fate-go.jp/gamedata/top?appVer=" + NewVersion.ToString()); + Result = await Response.Result.Content.ReadAsStringAsync(); + res = JsonNode.Parse(Result).AsObject(); + } + else + { + throw new Exception(res["response"][0]["fail"]["detail"].ToString()); + } + } + File.WriteAllText(Configuration.GameDataRawPath, Result); + Console.WriteLine($"Writing file to: {Configuration.GameDataRawPath}"); break; } case 4: { - string[] assetStore = File.ReadAllLines(Configuration.AssetsFolder.FullName + "AssetStorage_dec.txt"); - Console.WriteLine("Parsing json..."); + if (File.Exists($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundle.json")) + { + JsonObject assetbundle = JsonNode.Parse(File.ReadAllText($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundle.json")).AsObject(); + + string folderName = assetbundle["folderName"].ToString(); + string DownloadURL = $"https://cdn.data.fate-go.jp/AssetStorages/{folderName}Android/AssetStorage.txt"; + HttpClient Client = new(); + Console.WriteLine("Downloading AssetStorage..."); + var Response = Client.GetAsync(DownloadURL); + string Result = await Response.Result.Content.ReadAsStringAsync(); + + string DecryptedData = decryptor.MouseGame8(Result); + File.WriteAllText($"{Configuration.AssetsFolder.FullName}AssetStorage_dec.txt", DecryptedData); + Console.WriteLine($"Writing file to: {Configuration.AssetsFolder.FullName}AssetStorage_dec.txt"); + } + else + { + Console.WriteLine("先解析AssetBundle&Key"); + } + break; + } + case 5: + { + if (File.Exists($"{Configuration.AssetsFolder.FullName}AssetStorage.txt")) + { + string data = File.ReadAllText($"{Configuration.AssetsFolder.FullName}AssetStorage.txt"); + string DecryptedData = decryptor.MouseGame8(data); + File.WriteAllText($"{Configuration.AssetsFolder.FullName}AssetStorage_dec.txt", DecryptedData); + Console.WriteLine($"Writing file to: {Configuration.AssetsFolder.FullName}AssetStorage_dec.txt"); + } + else + { + Console.WriteLine("先下载或放置AssetStorage.txt"); + } + break; + } + case 6: + { + Console.WriteLine("Parsing AssetStorage..."); List AudioList = new(); List AssetList = new(); + List AssetListWithExtraKeyType = new(); + string[] AssetStore = File.ReadAllLines($"{Configuration.AssetsFolder.FullName}AssetStorage_dec.txt"); - for (int i = 2; i < assetStore.Length; ++i) + for (int i = 2; i < AssetStore.Length - 1; ++i) { - string[] tmp = assetStore[i].Split(','); + string[] tmp = AssetStore[i].Split(','); string assetName; string fileName; + string keyType = ""; - if (tmp.Length == 5) + if (tmp[0] == "1") { if (tmp[4].Contains("Audio")) { @@ -95,17 +221,28 @@ static async void DisplayMenuAsync() AudioList.Add(new Asset { AssetName = assetName, - FileName = fileName + FileName = fileName, }); } else if (!tmp[4].Contains("Movie")) { assetName = tmp[4].Replace('/', '@') + ".unity3d"; fileName = decryptor.getShaName(assetName); + + if (tmp.Length == 6) + { + keyType = tmp[5]; + AssetListWithExtraKeyType.Add(new Asset + { + AssetName = keyType, + FileName = fileName, + }); + } + AssetList.Add(new Asset { AssetName = assetName, - FileName = fileName + FileName = fileName, }); } } @@ -134,14 +271,116 @@ static async void DisplayMenuAsync() string result = JsonSerializer.Serialize(AudioList, options); Console.WriteLine("Writing file to: AudioName.json"); - File.WriteAllText(Configuration.AssetsFolder.FullName + "AudioName.json", result); + File.WriteAllText($"{Configuration.AssetsFolder.FullName}AudioName.json", result); result = JsonSerializer.Serialize(AssetList, options); Console.WriteLine("Writing file to: AssetName.json"); - File.WriteAllText(Configuration.AssetsFolder.FullName + "AssetName.json", result); + File.WriteAllText($"{Configuration.AssetsFolder.FullName}AssetName.json", result); + + result = JsonSerializer.Serialize(AssetListWithExtraKeyType, options); + Console.WriteLine("Writing file to: AssetListWithExtraKeyType.json"); + File.WriteAllText($"{Configuration.AssetsFolder.FullName}AssetListWithExtraKeyType.json", result); break; } - case 5: + case 7: + { + if (File.Exists($"{Configuration.GameDataRawPath}")) + { + string raw_str = File.ReadAllText($"{Configuration.GameDataRawPath}"); + JsonObject res = JsonNode.Parse(raw_str).AsObject(); + + ValueTuple[] RawData = new ValueTuple[] + { + new ("assetbundle", "", Configuration.GameDataAssetBundlePath, UniversalUnpacker.AssetBundleKey), + new ("assetbundleKey", "", Configuration.GameDataAssetBundleKeyPath, UniversalUnpacker.AssetBundleKey) + }; + + for (int i = 0; i < RawData.Length; i++) + { + if (File.Exists($"{RawData[i].Item3}")) + { + RawData[i].Item2 = File.ReadAllText(RawData[i].Item3); + } + else + { + RawData[i].Item2 = res["response"][0]["success"][RawData[i].Item1].ToString(); + File.WriteAllText(RawData[i].Item3, RawData[i].Item2); + } + } + + { + Dictionary BundleData = (Dictionary)UniversalUnpacker.Unpack(Convert.FromBase64String(RawData[0].Item2), RawData[0].Item4); + var options = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.All) + }; + string result = JsonSerializer.Serialize(BundleData, options); + File.WriteAllText($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundle.json", result); + Console.WriteLine($"Writing file to: {Configuration.GameDataUnpackAssetBundleFolder}assetbundle.json"); + } + + { + List KeyData = (List)UniversalUnpacker.Unpack(Convert.FromBase64String(RawData[1].Item2), RawData[1].Item4); + var options = new JsonSerializerOptions { WriteIndented = true }; + string result = JsonSerializer.Serialize(KeyData, options); + File.WriteAllText($"{Configuration.GameDataUnpackAssetBundleFolder}assetbundleKey.json", result); + Console.WriteLine($"Writing file to: {Configuration.GameDataUnpackAssetBundleFolder}assetbundleKey.json"); + } + } + else + { + Console.WriteLine("先从服务器下载游戏数据"); + } + break; + } + case 8: + { + if (File.Exists($"{Configuration.GameDataRawPath}")) + { + string raw_str = File.ReadAllText($"{Configuration.GameDataRawPath}"); + JsonObject res = JsonNode.Parse(raw_str).AsObject(); + + ValueTuple[] RawData = new ValueTuple[] + { + new ("master", "", Configuration.GameDataMasterPath, UniversalUnpacker.MasterKey) + }; + + for (int i = 0; i < RawData.Length; i++) + { + if (File.Exists($"{RawData[i].Item3}")) + { + RawData[i].Item2 = File.ReadAllText(RawData[i].Item3); + } + else + { + RawData[i].Item2 = res["response"][0]["success"][RawData[i].Item1].ToString(); + File.WriteAllText(RawData[i].Item3, RawData[i].Item2); + } + } + + { + Dictionary MasterData = (Dictionary)UniversalUnpacker.Unpack(Convert.FromBase64String(RawData[0].Item2), RawData[0].Item4); + foreach (var item in MasterData) + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.All) + }; + string result = JsonSerializer.Serialize(item.Value, options); + File.WriteAllText($"{Configuration.GameDataUnpackFolder}{item.Key}", result); + Console.WriteLine($"Writing file to: {Configuration.GameDataUnpackFolder}{item.Key}"); + } + } + } + else + { + Console.WriteLine("没有游戏数据,请先下载"); + } + break; + } + case 9: { foreach (FileInfo file in Configuration.DecryptedScriptsFolder.GetFiles("*.txt", SearchOption.AllDirectories)) { @@ -154,7 +393,7 @@ static async void DisplayMenuAsync() } break; } - case 6: + case 10: { foreach (FileInfo file in Configuration.ScriptsFolder.GetFiles("*.txt", SearchOption.AllDirectories)) { @@ -167,7 +406,7 @@ static async void DisplayMenuAsync() } break; } - case 7: + case 11: { string JPText = File.ReadAllText(Configuration.DecryptedFolder.FullName + "JP.txt"); string CNText = File.ReadAllText(Configuration.DecryptedFolder.FullName + "CN.txt"); @@ -192,48 +431,6 @@ static async void DisplayMenuAsync() File.WriteAllText(Configuration.DecryptedFolder.FullName + "Non-Translation.txt", NoTranslation.ToString()); break; } - case 8: - { - HttpClient Client = new(); - var Response = Client.GetAsync("https://game.fate-go.jp/gamedata/top?appVer=2.20.1"); - string Result = await Response.Result.Content.ReadAsStringAsync(); - JsonObject res = JsonNode.Parse(Result).AsObject(); - if (res["response"][0]["fail"]["action"] != null) - { - if (res["response"][0]["fail"]["action"].ToString() == "app_version_up") - { - string NewVersion = res["response"][0]["fail"]["detail"].ToString(); - NewVersion = Regex.Replace(NewVersion, @".*新ver.:(.*)、現.*", "$1", RegexOptions.Singleline); - Console.WriteLine("new version: " + NewVersion.ToString()); - Response = Client.GetAsync("https://game.fate-go.jp/gamedata/top?appVer=" + NewVersion.ToString()); - Result = await Response.Result.Content.ReadAsStringAsync(); - res = JsonNode.Parse(Result).AsObject(); - } - else - { - throw new Exception(res["response"][0]["fail"]["detail"].ToString()); - } - } - File.WriteAllText(Configuration.GameDataFolder + "raw", Result); - File.WriteAllText(Configuration.GameDataFolder + "master", res["response"][0]["success"]["master"].ToString()); - Console.WriteLine("Writing file to: " + Configuration.GameDataFolder + "master"); - break; - } - case 9: - { - string data = File.ReadAllText(Configuration.GameDataFolder + "master"); - Dictionary masterData = (Dictionary)MasterDataUnpacker.MouseGame2Unpacker(Convert.FromBase64String(data)); - MiniMessagePacker miniMessagePacker = new MiniMessagePacker(); - foreach (KeyValuePair item in masterData) - { - List unpackeditem = (List)miniMessagePacker.Unpack(item.Value); - var options = new JsonSerializerOptions { WriteIndented = true }; - string result = JsonSerializer.Serialize(unpackeditem, options); - File.WriteAllText(Configuration.GameDataUnpackFolder + item.Key, result); - Console.WriteLine("Writing file to: " + Configuration.GameDataUnpackFolder + item.Key); - } - break; - } default: { Console.WriteLine("请输入一个可接受的选项"); @@ -245,7 +442,6 @@ static async void DisplayMenuAsync() { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); - Console.ReadKey(true); } } static void Main(string[] args) diff --git a/FGOAssetsModifyTool/UniversalUnpacker.cs b/FGOAssetsModifyTool/UniversalUnpacker.cs new file mode 100644 index 0000000..526094d --- /dev/null +++ b/FGOAssetsModifyTool/UniversalUnpacker.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; + +namespace FGOAssetsModifyTool +{ + internal class UniversalUnpacker + { + public static string MasterKey = "pX6q6xK2UymhFKcaGHHUlfXqfTsWF0uH"; + public static string AssetBundleKey = "W0Juh4cFJSYPkebJB9WpswNF51oa6Gm7"; + protected static byte[] InfoTop = new byte[32]; + protected static byte[] InfoData = new byte[32]; + + public static object Unpack(byte[] data, string key) + { + var array = new byte[data.Length - 32]; + InfoData = Encoding.UTF8.GetBytes(key); + Array.Copy(data, 0, InfoTop, 0, 32); + Array.Copy(data, 32, array, 0, data.Length - 32); + + var buf = CatAndMouseGame.MouseHomeMain(array, InfoData, InfoTop, true); + return new MiniMessagePacker().Unpack(buf); + } + } +}