From 3ea9f71f00e535d7225110a359b81f428f5ff684 Mon Sep 17 00:00:00 2001
From: Razmoth <32140579+Razmoth@users.noreply.github.com>
Date: Mon, 10 Jul 2023 20:40:17 +0400
Subject: [PATCH] Added `UnityCN`.
---
AssetStudio/AssetStudio.csproj | 6 +
AssetStudio/BundleFile.cs | 43 ++++-
AssetStudio/Crypto/UnityCN.cs | 158 ++++++++++++++++++
AssetStudio/GameManager.cs | 3 +
AssetStudio/Keys.json | 86 ++++++++++
AssetStudio/UnityCNManager.cs | 74 ++++++++
AssetStudioCLI/Components/CommandLine.cs | 6 +
AssetStudioCLI/Program.cs | 13 ++
AssetStudioGUI/App.config | 3 +
AssetStudioGUI/AssetStudioGUIForm.Designer.cs | 69 +++++---
AssetStudioGUI/AssetStudioGUIForm.cs | 11 ++
AssetStudioGUI/AssetStudioGUIForm.resx | 6 +-
AssetStudioGUI/ExportOptions.resx | 9 +
.../Properties/Settings.Designer.cs | 13 +-
AssetStudioGUI/Properties/Settings.settings | 3 +
AssetStudioGUI/UnityCNForm.Designer.cs | 94 +++++++++++
AssetStudioGUI/UnityCNForm.cs | 77 +++++++++
AssetStudioGUI/UnityCNForm.resx | 126 ++++++++++++++
18 files changed, 766 insertions(+), 34 deletions(-)
create mode 100644 AssetStudio/Crypto/UnityCN.cs
create mode 100644 AssetStudio/Keys.json
create mode 100644 AssetStudio/UnityCNManager.cs
create mode 100644 AssetStudioGUI/UnityCNForm.Designer.cs
create mode 100644 AssetStudioGUI/UnityCNForm.cs
create mode 100644 AssetStudioGUI/UnityCNForm.resx
diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj
index d67e90b..c5a850f 100644
--- a/AssetStudio/AssetStudio.csproj
+++ b/AssetStudio/AssetStudio.csproj
@@ -15,5 +15,11 @@
+
+
+
+ PreserveNewest
+
+
diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs
index be2aa57..35b5d6d 100644
--- a/AssetStudio/BundleFile.cs
+++ b/AssetStudio/BundleFile.cs
@@ -16,6 +16,7 @@ namespace AssetStudio
BlocksInfoAtTheEnd = 0x80,
OldWebPluginCompatibility = 0x100,
BlockInfoNeedPaddingAtStart = 0x200,
+ UnityCNEncryption = 0x400
}
[Flags]
@@ -66,6 +67,7 @@ namespace AssetStudio
}
private Game Game;
+ private UnityCN UnityCN;
public Header m_Header;
private Node[] m_DirectoryInfo;
@@ -75,6 +77,7 @@ namespace AssetStudio
private bool HasUncompressedDataHash = true;
+ private bool HasBlockInfoNeedPaddingAtStart = true;
public BundleFile(FileReader reader, Game game)
{
@@ -100,6 +103,10 @@ namespace AssetStudio
case "UnityFS":
case "ENCR":
ReadHeader(reader);
+ if (game.Type.IsUnityCN())
+ {
+ ReadUnityCN(reader);
+ }
ReadBlocksInfoAndDirectory(reader);
using (var blocksStream = CreateBlocksStream(reader.FullPath))
{
@@ -312,6 +319,32 @@ namespace AssetStudio
}
}
+ private void ReadUnityCN(EndianBinaryReader reader)
+ {
+ 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;
+ HasBlockInfoNeedPaddingAtStart = false;
+ }
+ else
+ {
+ mask = ArchiveFlags.UnityCNEncryption;
+ HasBlockInfoNeedPaddingAtStart = true;
+ }
+
+ if ((m_Header.flags & mask) != 0)
+ {
+ UnityCN = new UnityCN(reader);
+ }
+ }
+
private void ReadBlocksInfoAndDirectory(FileReader reader)
{
byte[] blocksInfoBytes;
@@ -403,7 +436,7 @@ namespace AssetStudio
};
}
}
- if ((m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
+ if (HasBlockInfoNeedPaddingAtStart && (m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
{
reader.AlignStream(16);
}
@@ -439,14 +472,18 @@ namespace AssetStudio
{
compressedBytesSpan = Mr0kUtils.Decrypt(compressedBytesSpan, (Mr0k)Game);
}
- if (Game.Type.IsOPFP())
+ if (Game.Type.IsUnityCN() && ((int)blockInfo.flags & 0x100) != 0)
{
- OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath);
+ UnityCN.DecryptBlock(compressedBytes, compressedSize, i);
}
if (Game.Type.IsNetEase() && i == 0)
{
NetEaseUtils.Decrypt(compressedBytesSpan);
}
+ if (Game.Type.IsOPFP())
+ {
+ OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath);
+ }
var uncompressedSize = (int)blockInfo.uncompressedSize;
var uncompressedBytes = BigArrayPool.Shared.Rent(uncompressedSize);
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
diff --git a/AssetStudio/Crypto/UnityCN.cs b/AssetStudio/Crypto/UnityCN.cs
new file mode 100644
index 0000000..7f4df11
--- /dev/null
+++ b/AssetStudio/Crypto/UnityCN.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Text;
+using System.Security.Cryptography;
+
+namespace AssetStudio
+{
+ public class UnityCN
+ {
+ private const string Signature = "#$unity3dchina!@";
+
+ private static ICryptoTransform Encryptor;
+
+ public byte[] Index = new byte[0x10];
+ public byte[] Sub = new byte[0x10];
+
+ public UnityCN(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 = Convert.FromHexString(entry.Key);
+
+ Encryptor = aes.CreateEncryptor();
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error($"[UnityCN] Invalid key !!\n{e.Message}");
+ return false;
+ }
+ return true;
+ }
+
+ public void DecryptBlock(Span bytes, int size, int index)
+ {
+ var offset = 0;
+ while (offset < size)
+ {
+ offset += Decrypt(bytes.Slice(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 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 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 class 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 = Convert.FromHexString(Key);
+ if (bytes.Length != 0x10)
+ {
+ Logger.Warning($"[UnityCN] {this} has invalid key, size should be 16 bytes, skipping...");
+ return false;
+ }
+
+ return true;
+ }
+
+ public override string ToString() => $"{Name} ({Key})";
+ }
+ }
+}
\ No newline at end of file
diff --git a/AssetStudio/GameManager.cs b/AssetStudio/GameManager.cs
index 82a396c..4c695f3 100644
--- a/AssetStudio/GameManager.cs
+++ b/AssetStudio/GameManager.cs
@@ -12,6 +12,7 @@ namespace AssetStudio
{
int index = 0;
Games.Add(index++, new(GameType.Normal));
+ Games.Add(index++, new(GameType.UnityCN));
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));
@@ -116,6 +117,7 @@ namespace AssetStudio
public enum GameType
{
Normal,
+ UnityCN,
GI,
GI_Pack,
GI_CB1,
@@ -142,6 +144,7 @@ namespace AssetStudio
public static class GameTypes
{
public static bool IsNormal(this GameType type) => type == GameType.Normal;
+ public static bool IsUnityCN(this GameType type) => type == GameType.UnityCN;
public static bool IsGI(this GameType type) => type == GameType.GI;
public static bool IsGIPack(this GameType type) => type == GameType.GI_Pack;
public static bool IsGICB1(this GameType type) => type == GameType.GI_CB1;
diff --git a/AssetStudio/Keys.json b/AssetStudio/Keys.json
new file mode 100644
index 0000000..5f72d4a
--- /dev/null
+++ b/AssetStudio/Keys.json
@@ -0,0 +1,86 @@
+[
+ {
+ "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"
+ },
+ {
+ "Name": "Dynasty Legends 2",
+ "Key": "746169686567616D6573323032323032"
+ },
+ {
+ "Name": "Evernight CN",
+ "Key": "68687878747478736868787874747873"
+ },
+ {
+ "Name": "Xintianlong Babu",
+ "Key": "61323562623133346363326464333265"
+ },
+ {
+ "Name": "Frostpunk: Beyond the Ice",
+ "Key": "7368756978696E673838383838383838"
+ }
+]
diff --git a/AssetStudio/UnityCNManager.cs b/AssetStudio/UnityCNManager.cs
new file mode 100644
index 0000000..ccfb420
--- /dev/null
+++ b/AssetStudio/UnityCNManager.cs
@@ -0,0 +1,74 @@
+using System;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace AssetStudio
+{
+ public static class UnityCNManager
+ {
+ public const string KeysFileName = "Keys.json";
+
+ private static List Entries = new List();
+
+ static UnityCNManager()
+ {
+ var str = File.ReadAllText(KeysFileName);
+ Entries = JsonConvert.DeserializeObject>(str);
+ }
+
+ public static void SaveEntries(List 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 unityCN))
+ {
+ if (UnityCN.SetKey(unityCN))
+ {
+ Logger.Info($"[UnityCN] Selected Key is {unityCN}");
+ }
+ else
+ {
+ Logger.Info($"[UnityCN] No Key is selected !!");
+ }
+ }
+ else
+ {
+ Logger.Error("Invalid Key !!");
+ Logger.Warning(GetEntries().Select(x => x.ToString()).ToString());
+ }
+ }
+
+ public static bool TryGetEntry(int index, out UnityCN.Entry key)
+ {
+ try
+ {
+ if (index < 0 || index > Entries.Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ key = Entries[index];
+ }
+ catch(Exception e)
+ {
+ Logger.Error($"[UnityCN] Invalid Index, check if list is not empty !!\n{e.Message}");
+ key = null;
+ return false;
+ }
+
+ return true;
+ }
+ public static UnityCN.Entry[] GetEntries() => Entries.ToArray();
+
+ public new static string ToString() => string.Join("\n", GetEntries().Select((x, i) => $"{i}: {x.Name}"));
+ }
+}
diff --git a/AssetStudioCLI/Components/CommandLine.cs b/AssetStudioCLI/Components/CommandLine.cs
index f82f3a1..ec05cba 100644
--- a/AssetStudioCLI/Components/CommandLine.cs
+++ b/AssetStudioCLI/Components/CommandLine.cs
@@ -26,6 +26,7 @@ namespace AssetStudioCLI
optionsBinder.NameFilter,
optionsBinder.ContainerFilter,
optionsBinder.GameName,
+ optionsBinder.KeyIndex,
optionsBinder.MapOp,
optionsBinder.MapType,
optionsBinder.MapName,
@@ -50,6 +51,7 @@ namespace AssetStudioCLI
public Regex[] NameFilter { get; set; }
public Regex[] ContainerFilter { get; set; }
public string GameName { get; set; }
+ public int KeyIndex { get; set; }
public MapOpType MapOp { get; set; }
public ExportListType MapType { get; set; }
public string MapName { get; set; }
@@ -69,6 +71,7 @@ namespace AssetStudioCLI
public readonly Option NameFilter;
public readonly Option ContainerFilter;
public readonly Option GameName;
+ public readonly Option KeyIndex;
public readonly Option MapOp;
public readonly Option MapType;
public readonly Option MapName;
@@ -88,6 +91,7 @@ namespace AssetStudioCLI
NameFilter = new Option("--names", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify name regex filter(s).") { AllowMultipleArgumentsPerToken = true };
ContainerFilter = new Option("--containers", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify container regex filter(s).") { AllowMultipleArgumentsPerToken = true };
GameName = new Option("--game", $"Specify Game.") { IsRequired = true };
+ KeyIndex = new Option("--key_index", "Specify key index.") { ArgumentHelpName = UnityCNManager.ToString() };
MapOp = new Option("--map_op", "Specify which map to build.");
MapType = new Option("--map_type", "AssetMap output type.");
MapName = new Option("--map_name", () => "assets_map", "Specify AssetMap file name.");
@@ -141,6 +145,7 @@ namespace AssetStudioCLI
GroupAssetsType.SetDefaultValue(AssetGroupOption.ByType);
MapOp.SetDefaultValue(MapOpType.None);
MapType.SetDefaultValue(ExportListType.XML);
+ KeyIndex.SetDefaultValue(0);
}
public void FilterValidator(OptionResult result)
@@ -174,6 +179,7 @@ namespace AssetStudioCLI
NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter),
ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter),
GameName = bindingContext.ParseResult.GetValueForOption(GameName),
+ KeyIndex = bindingContext.ParseResult.GetValueForOption(KeyIndex),
MapOp = bindingContext.ParseResult.GetValueForOption(MapOp),
MapType = bindingContext.ParseResult.GetValueForOption(MapType),
MapName = bindingContext.ParseResult.GetValueForOption(MapName),
diff --git a/AssetStudioCLI/Program.cs b/AssetStudioCLI/Program.cs
index 5ef8360..5cf517f 100644
--- a/AssetStudioCLI/Program.cs
+++ b/AssetStudioCLI/Program.cs
@@ -25,6 +25,19 @@ namespace AssetStudioCLI
return;
}
+ if (game.Type.IsUnityCN())
+ {
+ if (!UnityCNManager.TryGetEntry(o.KeyIndex, out var unityCN))
+ {
+ Console.WriteLine("Invalid key index !!");
+ Console.WriteLine($"Available Options: \n{UnityCNManager.ToString()}");
+ return;
+ }
+
+ UnityCN.SetKey(unityCN);
+ Logger.Info($"[UnityCN] Selected Key is {unityCN}");
+ }
+
Studio.Game = game;
Logger.Default = new ConsoleLogger();
AssetsHelper.Minimal = Settings.Default.minimalAssetMap;
diff --git a/AssetStudioGUI/App.config b/AssetStudioGUI/App.config
index 1f170a6..b123ea2 100644
--- a/AssetStudioGUI/App.config
+++ b/AssetStudioGUI/App.config
@@ -109,6 +109,9 @@
False
+
+ 0
+
\ No newline at end of file
diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs
index 477543a..a2cf5ea 100644
--- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs
+++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs
@@ -44,12 +44,15 @@ namespace AssetStudioGUI
abortStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
displayAll = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator();
enablePreview = new System.Windows.Forms.ToolStripMenuItem();
enableModelPreview = new System.Windows.Forms.ToolStripMenuItem();
modelsOnly = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator();
+ displayInfo = new System.Windows.Forms.ToolStripMenuItem();
enableResolveDependencies = new System.Windows.Forms.ToolStripMenuItem();
skipContainer = new System.Windows.Forms.ToolStripMenuItem();
- displayInfo = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator();
toolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem();
specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox();
toolStripMenuItem18 = new System.Windows.Forms.ToolStripMenuItem();
@@ -164,9 +167,8 @@ namespace AssetStudioGUI
exportAnimatorwithselectedAnimationClipMenuItem = new System.Windows.Forms.ToolStripMenuItem();
goToSceneHierarchyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator();
- toolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator();
- toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator();
+ specifyUnityCNKey = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripSeparator13 = new System.Windows.Forms.ToolStripSeparator();
menuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
@@ -258,7 +260,7 @@ namespace AssetStudioGUI
//
// optionsToolStripMenuItem
//
- optionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { displayAll, toolStripSeparator10, enablePreview, enableModelPreview, modelsOnly, toolStripSeparator11, displayInfo, enableResolveDependencies, skipContainer, toolStripSeparator12, toolStripMenuItem14, toolStripMenuItem18, toolStripMenuItem19, showExpOpt });
+ optionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { displayAll, toolStripSeparator10, enablePreview, enableModelPreview, modelsOnly, toolStripSeparator11, displayInfo, enableResolveDependencies, skipContainer, toolStripSeparator12, toolStripMenuItem14, specifyUnityCNKey, toolStripSeparator13, toolStripMenuItem18, toolStripMenuItem19, showExpOpt });
optionsToolStripMenuItem.Name = "optionsToolStripMenuItem";
optionsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
optionsToolStripMenuItem.Text = "Options";
@@ -272,6 +274,11 @@ namespace AssetStudioGUI
displayAll.ToolTipText = "Check this option will display all types assets. Not extractable assets can export the RAW file.";
displayAll.CheckedChanged += displayAll_CheckedChanged;
//
+ // toolStripSeparator10
+ //
+ toolStripSeparator10.Name = "toolStripSeparator10";
+ toolStripSeparator10.Size = new System.Drawing.Size(222, 6);
+ //
// enablePreview
//
enablePreview.Checked = true;
@@ -299,6 +306,22 @@ namespace AssetStudioGUI
modelsOnly.Text = "Filter models only";
modelsOnly.CheckedChanged += modelsOnly_CheckedChanged;
//
+ // toolStripSeparator11
+ //
+ toolStripSeparator11.Name = "toolStripSeparator11";
+ toolStripSeparator11.Size = new System.Drawing.Size(222, 6);
+ //
+ // displayInfo
+ //
+ displayInfo.Checked = true;
+ displayInfo.CheckOnClick = true;
+ displayInfo.CheckState = System.Windows.Forms.CheckState.Checked;
+ displayInfo.Name = "displayInfo";
+ displayInfo.Size = new System.Drawing.Size(225, 22);
+ displayInfo.Text = "Display asset information";
+ displayInfo.ToolTipText = "Toggle the overlay that shows information about each asset, eg. image size, format, audio bitrate, etc.";
+ displayInfo.CheckedChanged += displayAssetInfo_Check;
+ //
// enableResolveDependencies
//
enableResolveDependencies.Checked = true;
@@ -316,20 +339,13 @@ namespace AssetStudioGUI
skipContainer.Name = "skipContainer";
skipContainer.Size = new System.Drawing.Size(225, 22);
skipContainer.Text = "Skip container recovery";
- skipContainer.ToolTipText = "Skips the container recovery step.\nImproves loading when dealing with a large num" +
- "ber of files.";
+ skipContainer.ToolTipText = "Skips the container recovery step.\nImproves loading when dealing with a large number of files.";
skipContainer.CheckedChanged += skipContainer_CheckedChanged;
//
- // displayInfo
+ // toolStripSeparator12
//
- displayInfo.Checked = true;
- displayInfo.CheckOnClick = true;
- displayInfo.CheckState = System.Windows.Forms.CheckState.Checked;
- displayInfo.Name = "displayInfo";
- displayInfo.Size = new System.Drawing.Size(225, 22);
- displayInfo.Text = "Display asset information";
- displayInfo.ToolTipText = "Toggle the overlay that shows information about each asset, eg. image size, format, audio bitrate, etc.";
- displayInfo.CheckedChanged += displayAssetInfo_Check;
+ toolStripSeparator12.Name = "toolStripSeparator12";
+ toolStripSeparator12.Size = new System.Drawing.Size(222, 6);
//
// toolStripMenuItem14
//
@@ -1292,20 +1308,17 @@ namespace AssetStudioGUI
showOriginalFileToolStripMenuItem.Visible = false;
showOriginalFileToolStripMenuItem.Click += showOriginalFileToolStripMenuItem_Click;
//
- // toolStripSeparator10
+ // specifyUnityCNKey
//
- toolStripSeparator10.Name = "toolStripSeparator10";
- toolStripSeparator10.Size = new System.Drawing.Size(222, 6);
+ specifyUnityCNKey.Name = "specifyUnityCNKey";
+ specifyUnityCNKey.Size = new System.Drawing.Size(225, 22);
+ specifyUnityCNKey.Text = "Specify UnityCN Key";
+ specifyUnityCNKey.Click += specifyUnityCNKey_Click;
//
- // toolStripSeparator11
+ // toolStripSeparator13
//
- toolStripSeparator11.Name = "toolStripSeparator11";
- toolStripSeparator11.Size = new System.Drawing.Size(222, 6);
- //
- // toolStripSeparator12
- //
- toolStripSeparator12.Name = "toolStripSeparator12";
- toolStripSeparator12.Size = new System.Drawing.Size(222, 6);
+ toolStripSeparator13.Name = "toolStripSeparator13";
+ toolStripSeparator13.Size = new System.Drawing.Size(222, 6);
//
// AssetStudioGUIForm
//
@@ -1491,6 +1504,8 @@ namespace AssetStudioGUI
private System.Windows.Forms.ToolStripSeparator toolStripSeparator10;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator11;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator12;
+ private System.Windows.Forms.ToolStripMenuItem specifyUnityCNKey;
+ private System.Windows.Forms.ToolStripSeparator toolStripSeparator13;
}
}
diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs
index b1db9ed..9ad095a 100644
--- a/AssetStudioGUI/AssetStudioGUIForm.cs
+++ b/AssetStudioGUI/AssetStudioGUIForm.cs
@@ -157,6 +157,7 @@ namespace AssetStudioGUI
Logger.Info($"Target Game type is {Studio.Game.Type}");
MapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
+ UnityCNManager.SetKey(Properties.Settings.Default.selectedUnityCNKey);
}
private void AssetStudioGUIForm_DragEnter(object sender, DragEventArgs e)
{
@@ -274,6 +275,10 @@ namespace AssetStudioGUI
{
productName = Studio.Game.Name;
}
+ else if (Studio.Game.Type.IsUnityCN() && UnityCNManager.TryGetEntry(Properties.Settings.Default.selectedUnityCNKey, out var unityCN))
+ {
+ productName = unityCN.Name;
+ }
else
{
productName = "no productName";
@@ -2300,6 +2305,12 @@ namespace AssetStudioGUI
assetBrowser.Show();
}
+ private void specifyUnityCNKey_Click(object sender, EventArgs e)
+ {
+ var unitycn = new UnityCNForm();
+ unitycn.Show();
+ }
+
#region FMOD
private void FMODinit()
{
diff --git a/AssetStudioGUI/AssetStudioGUIForm.resx b/AssetStudioGUI/AssetStudioGUIForm.resx
index de2aa35..500ca4f 100644
--- a/AssetStudioGUI/AssetStudioGUIForm.resx
+++ b/AssetStudioGUI/AssetStudioGUIForm.resx
@@ -120,9 +120,6 @@
312, 17
-
- 432, 17
-
abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWYZ
1234567890.:,;'\"(!?)+-*/=
@@ -141,6 +138,9 @@ The quick brown fox jumps over the lazy dog. 1234567890
The quick brown fox jumps over the lazy dog. 1234567890
+
+ 432, 17
+
553, 17
diff --git a/AssetStudioGUI/ExportOptions.resx b/AssetStudioGUI/ExportOptions.resx
index 5cb967e..ea96e60 100644
--- a/AssetStudioGUI/ExportOptions.resx
+++ b/AssetStudioGUI/ExportOptions.resx
@@ -120,6 +120,12 @@
17, 17
+
+ 17, 17
+
+
+ 162, 17
+
162, 17
@@ -129,6 +135,9 @@
273, 17
+
+ 404, 17
+
57
diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs
index 94084cb..cec55b5 100644
--- a/AssetStudioGUI/Properties/Settings.Designer.cs
+++ b/AssetStudioGUI/Properties/Settings.Designer.cs
@@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -442,5 +442,16 @@ namespace AssetStudioGUI.Properties {
this["enableModelPreview"] = value;
}
}
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("0")]
+ public int selectedUnityCNKey {
+ get {
+ return ((int)(this["selectedUnityCNKey"]));
+ }
+ set {
+ this["selectedUnityCNKey"] = value;
+ }
+ }
}
}
diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings
index a26d555..e473a34 100644
--- a/AssetStudioGUI/Properties/Settings.settings
+++ b/AssetStudioGUI/Properties/Settings.settings
@@ -107,5 +107,8 @@
False
+
+ 0
+
\ No newline at end of file
diff --git a/AssetStudioGUI/UnityCNForm.Designer.cs b/AssetStudioGUI/UnityCNForm.Designer.cs
new file mode 100644
index 0000000..00816e8
--- /dev/null
+++ b/AssetStudioGUI/UnityCNForm.Designer.cs
@@ -0,0 +1,94 @@
+namespace AssetStudioGUI
+{
+ partial class UnityCNForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.specifyUnityCNList = new System.Windows.Forms.DataGridView();
+ this.NameField = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.KeyField = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ ((System.ComponentModel.ISupportInitialize)(this.specifyUnityCNList)).BeginInit();
+ this.SuspendLayout();
+ //
+ // specifyUnityCNList
+ //
+ this.specifyUnityCNList.AllowUserToResizeColumns = false;
+ this.specifyUnityCNList.AllowUserToResizeRows = false;
+ this.specifyUnityCNList.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
+ this.specifyUnityCNList.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ this.specifyUnityCNList.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
+ this.NameField,
+ this.KeyField});
+ this.specifyUnityCNList.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.specifyUnityCNList.Location = new System.Drawing.Point(0, 0);
+ this.specifyUnityCNList.MultiSelect = false;
+ this.specifyUnityCNList.Name = "specifyUnityCNList";
+ this.specifyUnityCNList.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing;
+ this.specifyUnityCNList.RowTemplate.Height = 25;
+ this.specifyUnityCNList.Size = new System.Drawing.Size(408, 204);
+ this.specifyUnityCNList.TabIndex = 0;
+ this.specifyUnityCNList.RowHeaderMouseDoubleClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.specifyUnityCNList_RowHeaderMouseDoubleClick);
+ //
+ // NameField
+ //
+ this.NameField.HeaderText = "Name";
+ this.NameField.Name = "NameField";
+ this.NameField.Width = 140;
+ //
+ // KeyField
+ //
+ this.KeyField.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.KeyField.HeaderText = "Key";
+ this.KeyField.Name = "KeyField";
+ //
+ // UnityCNForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(408, 204);
+ this.Controls.Add(this.specifyUnityCNList);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "UnityCNForm";
+ this.ShowIcon = false;
+ this.ShowInTaskbar = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "UnityCNForm";
+ this.TopMost = true;
+ ((System.ComponentModel.ISupportInitialize)(this.specifyUnityCNList)).EndInit();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.DataGridView specifyUnityCNList;
+ private System.Windows.Forms.DataGridViewTextBoxColumn NameField;
+ private System.Windows.Forms.DataGridViewTextBoxColumn KeyField;
+ }
+}
\ No newline at end of file
diff --git a/AssetStudioGUI/UnityCNForm.cs b/AssetStudioGUI/UnityCNForm.cs
new file mode 100644
index 0000000..d8956fa
--- /dev/null
+++ b/AssetStudioGUI/UnityCNForm.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Windows.Forms;
+using System.Collections.Generic;
+using AssetStudio;
+using System.Linq;
+
+namespace AssetStudioGUI
+{
+ public partial class UnityCNForm : Form
+ {
+ public UnityCNForm()
+ {
+ InitializeComponent();
+
+ var keys = UnityCNManager.GetEntries();
+
+ for (int i = 0; i < keys.Length; i++)
+ {
+ var key = keys[i];
+ var rowIdx = specifyUnityCNList.Rows.Add();
+
+ specifyUnityCNList.Rows[rowIdx].Cells["NameField"].Value = key.Name;
+ specifyUnityCNList.Rows[rowIdx].Cells["KeyField"].Value = key.Key;
+ }
+
+ var index = Properties.Settings.Default.selectedUnityCNKey;
+ if (index >= specifyUnityCNList.RowCount)
+ {
+ index = 0;
+ }
+ specifyUnityCNList.CurrentCell = specifyUnityCNList.Rows[index].Cells[0];
+ }
+
+ private void specifyUnityCNList_RowHeaderMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
+ {
+ var keys = new List();
+ for (int i = specifyUnityCNList.Rows.Count - 1; i >= 0; i--)
+ {
+ var row = specifyUnityCNList.Rows[i];
+ var name = row.Cells["NameField"].Value as string;
+ var key = row.Cells["KeyField"].Value as string;
+
+ if (!(string.IsNullOrEmpty(name) || string.IsNullOrEmpty(key)))
+ {
+ var unityCN = new UnityCN.Entry(name, key);
+
+ if (unityCN.Validate())
+ {
+ keys.Add(unityCN);
+ continue;
+ }
+ }
+
+ if (specifyUnityCNList.CurrentCell.RowIndex == row.Index)
+ {
+ var previousRow = specifyUnityCNList.Rows.Cast().ElementAtOrDefault(i - 1);
+ if (previousRow != null)
+ {
+ specifyUnityCNList.CurrentCell = previousRow.Cells[0];
+ }
+ }
+ if (i != specifyUnityCNList.RowCount - 1)
+ {
+ specifyUnityCNList.Rows.RemoveAt(i);
+ }
+ }
+ UnityCNManager.SaveEntries(keys.Reverse().ToList());
+ UnityCNManager.SetKey(specifyUnityCNList.CurrentRow.Index);
+
+ Properties.Settings.Default.selectedUnityCNKey = specifyUnityCNList.CurrentRow.Index;
+ Properties.Settings.Default.Save();
+
+ DialogResult = DialogResult.OK;
+ Close();
+ }
+ }
+}
diff --git a/AssetStudioGUI/UnityCNForm.resx b/AssetStudioGUI/UnityCNForm.resx
new file mode 100644
index 0000000..622ff10
--- /dev/null
+++ b/AssetStudioGUI/UnityCNForm.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ True
+
+
+ True
+
+
\ No newline at end of file