Added UnityCN.

This commit is contained in:
Razmoth
2023-07-10 20:40:17 +04:00
parent 097060be89
commit 3ea9f71f00
18 changed files with 766 additions and 34 deletions

View File

@@ -16,4 +16,10 @@
<PackageReference Include="ZstdSharp.Port" Version="0.7.2" /> <PackageReference Include="ZstdSharp.Port" Version="0.7.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="Keys.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@@ -16,6 +16,7 @@ namespace AssetStudio
BlocksInfoAtTheEnd = 0x80, BlocksInfoAtTheEnd = 0x80,
OldWebPluginCompatibility = 0x100, OldWebPluginCompatibility = 0x100,
BlockInfoNeedPaddingAtStart = 0x200, BlockInfoNeedPaddingAtStart = 0x200,
UnityCNEncryption = 0x400
} }
[Flags] [Flags]
@@ -66,6 +67,7 @@ namespace AssetStudio
} }
private Game Game; private Game Game;
private UnityCN UnityCN;
public Header m_Header; public Header m_Header;
private Node[] m_DirectoryInfo; private Node[] m_DirectoryInfo;
@@ -75,6 +77,7 @@ namespace AssetStudio
private bool HasUncompressedDataHash = true; private bool HasUncompressedDataHash = true;
private bool HasBlockInfoNeedPaddingAtStart = true;
public BundleFile(FileReader reader, Game game) public BundleFile(FileReader reader, Game game)
{ {
@@ -100,6 +103,10 @@ namespace AssetStudio
case "UnityFS": case "UnityFS":
case "ENCR": case "ENCR":
ReadHeader(reader); ReadHeader(reader);
if (game.Type.IsUnityCN())
{
ReadUnityCN(reader);
}
ReadBlocksInfoAndDirectory(reader); ReadBlocksInfoAndDirectory(reader);
using (var blocksStream = CreateBlocksStream(reader.FullPath)) 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) private void ReadBlocksInfoAndDirectory(FileReader reader)
{ {
byte[] blocksInfoBytes; 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); reader.AlignStream(16);
} }
@@ -439,14 +472,18 @@ namespace AssetStudio
{ {
compressedBytesSpan = Mr0kUtils.Decrypt(compressedBytesSpan, (Mr0k)Game); 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) if (Game.Type.IsNetEase() && i == 0)
{ {
NetEaseUtils.Decrypt(compressedBytesSpan); NetEaseUtils.Decrypt(compressedBytesSpan);
} }
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);

View File

@@ -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<byte> 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<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 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})";
}
}
}

View File

@@ -12,6 +12,7 @@ namespace AssetStudio
{ {
int index = 0; int index = 0;
Games.Add(index++, new(GameType.Normal)); 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 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_Pack, PackExpansionKey, blockKey: PackBlockKey));
Games.Add(index++, new Mr0k(GameType.GI_CB1)); Games.Add(index++, new Mr0k(GameType.GI_CB1));
@@ -116,6 +117,7 @@ namespace AssetStudio
public enum GameType public enum GameType
{ {
Normal, Normal,
UnityCN,
GI, GI,
GI_Pack, GI_Pack,
GI_CB1, GI_CB1,
@@ -142,6 +144,7 @@ namespace AssetStudio
public static class GameTypes public static class GameTypes
{ {
public static bool IsNormal(this GameType type) => type == GameType.Normal; 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 IsGI(this GameType type) => type == GameType.GI;
public static bool IsGIPack(this GameType type) => type == GameType.GI_Pack; public static bool IsGIPack(this GameType type) => type == GameType.GI_Pack;
public static bool IsGICB1(this GameType type) => type == GameType.GI_CB1; public static bool IsGICB1(this GameType type) => type == GameType.GI_CB1;

86
AssetStudio/Keys.json Normal file
View File

@@ -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"
}
]

View File

@@ -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<UnityCN.Entry> Entries = new List<UnityCN.Entry>();
static UnityCNManager()
{
var str = File.ReadAllText(KeysFileName);
Entries = JsonConvert.DeserializeObject<List<UnityCN.Entry>>(str);
}
public static void SaveEntries(List<UnityCN.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 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}"));
}
}

View File

@@ -26,6 +26,7 @@ namespace AssetStudioCLI
optionsBinder.NameFilter, optionsBinder.NameFilter,
optionsBinder.ContainerFilter, optionsBinder.ContainerFilter,
optionsBinder.GameName, optionsBinder.GameName,
optionsBinder.KeyIndex,
optionsBinder.MapOp, optionsBinder.MapOp,
optionsBinder.MapType, optionsBinder.MapType,
optionsBinder.MapName, optionsBinder.MapName,
@@ -50,6 +51,7 @@ namespace AssetStudioCLI
public Regex[] NameFilter { get; set; } public Regex[] NameFilter { get; set; }
public Regex[] ContainerFilter { get; set; } public Regex[] ContainerFilter { get; set; }
public string GameName { get; set; } public string GameName { get; set; }
public int KeyIndex { get; set; }
public MapOpType MapOp { get; set; } public MapOpType MapOp { get; set; }
public ExportListType MapType { get; set; } public ExportListType MapType { get; set; }
public string MapName { get; set; } public string MapName { get; set; }
@@ -69,6 +71,7 @@ namespace AssetStudioCLI
public readonly Option<Regex[]> NameFilter; public readonly Option<Regex[]> NameFilter;
public readonly Option<Regex[]> ContainerFilter; public readonly Option<Regex[]> ContainerFilter;
public readonly Option<string> GameName; public readonly Option<string> GameName;
public readonly Option<int> KeyIndex;
public readonly Option<MapOpType> MapOp; public readonly Option<MapOpType> MapOp;
public readonly Option<ExportListType> MapType; public readonly Option<ExportListType> MapType;
public readonly Option<string> MapName; public readonly Option<string> MapName;
@@ -88,6 +91,7 @@ namespace AssetStudioCLI
NameFilter = new Option<Regex[]>("--names", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify name regex filter(s).") { AllowMultipleArgumentsPerToken = true }; NameFilter = new Option<Regex[]>("--names", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify name regex filter(s).") { AllowMultipleArgumentsPerToken = true };
ContainerFilter = new Option<Regex[]>("--containers", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify container regex filter(s).") { AllowMultipleArgumentsPerToken = true }; ContainerFilter = new Option<Regex[]>("--containers", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify container regex filter(s).") { AllowMultipleArgumentsPerToken = true };
GameName = new Option<string>("--game", $"Specify Game.") { IsRequired = true }; GameName = new Option<string>("--game", $"Specify Game.") { IsRequired = true };
KeyIndex = new Option<int>("--key_index", "Specify key index.") { ArgumentHelpName = UnityCNManager.ToString() };
MapOp = new Option<MapOpType>("--map_op", "Specify which map to build."); MapOp = new Option<MapOpType>("--map_op", "Specify which map to build.");
MapType = new Option<ExportListType>("--map_type", "AssetMap output type."); MapType = new Option<ExportListType>("--map_type", "AssetMap output type.");
MapName = new Option<string>("--map_name", () => "assets_map", "Specify AssetMap file name."); MapName = new Option<string>("--map_name", () => "assets_map", "Specify AssetMap file name.");
@@ -141,6 +145,7 @@ namespace AssetStudioCLI
GroupAssetsType.SetDefaultValue(AssetGroupOption.ByType); GroupAssetsType.SetDefaultValue(AssetGroupOption.ByType);
MapOp.SetDefaultValue(MapOpType.None); MapOp.SetDefaultValue(MapOpType.None);
MapType.SetDefaultValue(ExportListType.XML); MapType.SetDefaultValue(ExportListType.XML);
KeyIndex.SetDefaultValue(0);
} }
public void FilterValidator(OptionResult result) public void FilterValidator(OptionResult result)
@@ -174,6 +179,7 @@ namespace AssetStudioCLI
NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter), NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter),
ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter), ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter),
GameName = bindingContext.ParseResult.GetValueForOption(GameName), GameName = bindingContext.ParseResult.GetValueForOption(GameName),
KeyIndex = bindingContext.ParseResult.GetValueForOption(KeyIndex),
MapOp = bindingContext.ParseResult.GetValueForOption(MapOp), MapOp = bindingContext.ParseResult.GetValueForOption(MapOp),
MapType = bindingContext.ParseResult.GetValueForOption(MapType), MapType = bindingContext.ParseResult.GetValueForOption(MapType),
MapName = bindingContext.ParseResult.GetValueForOption(MapName), MapName = bindingContext.ParseResult.GetValueForOption(MapName),

View File

@@ -25,6 +25,19 @@ namespace AssetStudioCLI
return; 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; Studio.Game = game;
Logger.Default = new ConsoleLogger(); Logger.Default = new ConsoleLogger();
AssetsHelper.Minimal = Settings.Default.minimalAssetMap; AssetsHelper.Minimal = Settings.Default.minimalAssetMap;

View File

@@ -109,6 +109,9 @@
<setting name="enableModelPreview" serializeAs="String"> <setting name="enableModelPreview" serializeAs="String">
<value>False</value> <value>False</value>
</setting> </setting>
<setting name="selectedUnityCNKey" serializeAs="String">
<value>0</value>
</setting>
</AssetStudioGUI.Properties.Settings> </AssetStudioGUI.Properties.Settings>
</userSettings> </userSettings>
</configuration> </configuration>

View File

@@ -44,12 +44,15 @@ namespace AssetStudioGUI
abortStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); abortStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
displayAll = new System.Windows.Forms.ToolStripMenuItem(); displayAll = new System.Windows.Forms.ToolStripMenuItem();
toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator();
enablePreview = new System.Windows.Forms.ToolStripMenuItem(); enablePreview = new System.Windows.Forms.ToolStripMenuItem();
enableModelPreview = new System.Windows.Forms.ToolStripMenuItem(); enableModelPreview = new System.Windows.Forms.ToolStripMenuItem();
modelsOnly = 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(); enableResolveDependencies = new System.Windows.Forms.ToolStripMenuItem();
skipContainer = 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(); toolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem();
specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox(); specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox();
toolStripMenuItem18 = new System.Windows.Forms.ToolStripMenuItem(); toolStripMenuItem18 = new System.Windows.Forms.ToolStripMenuItem();
@@ -164,9 +167,8 @@ namespace AssetStudioGUI
exportAnimatorwithselectedAnimationClipMenuItem = new System.Windows.Forms.ToolStripMenuItem(); exportAnimatorwithselectedAnimationClipMenuItem = new System.Windows.Forms.ToolStripMenuItem();
goToSceneHierarchyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); goToSceneHierarchyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator(); specifyUnityCNKey = new System.Windows.Forms.ToolStripMenuItem();
toolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator(); toolStripSeparator13 = new System.Windows.Forms.ToolStripSeparator();
toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator();
menuStrip1.SuspendLayout(); menuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout(); splitContainer1.Panel1.SuspendLayout();
@@ -258,7 +260,7 @@ namespace AssetStudioGUI
// //
// optionsToolStripMenuItem // 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.Name = "optionsToolStripMenuItem";
optionsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); optionsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
optionsToolStripMenuItem.Text = "Options"; 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.ToolTipText = "Check this option will display all types assets. Not extractable assets can export the RAW file.";
displayAll.CheckedChanged += displayAll_CheckedChanged; displayAll.CheckedChanged += displayAll_CheckedChanged;
// //
// toolStripSeparator10
//
toolStripSeparator10.Name = "toolStripSeparator10";
toolStripSeparator10.Size = new System.Drawing.Size(222, 6);
//
// enablePreview // enablePreview
// //
enablePreview.Checked = true; enablePreview.Checked = true;
@@ -299,6 +306,22 @@ namespace AssetStudioGUI
modelsOnly.Text = "Filter models only"; modelsOnly.Text = "Filter models only";
modelsOnly.CheckedChanged += modelsOnly_CheckedChanged; 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
// //
enableResolveDependencies.Checked = true; enableResolveDependencies.Checked = true;
@@ -316,20 +339,13 @@ namespace AssetStudioGUI
skipContainer.Name = "skipContainer"; skipContainer.Name = "skipContainer";
skipContainer.Size = new System.Drawing.Size(225, 22); skipContainer.Size = new System.Drawing.Size(225, 22);
skipContainer.Text = "Skip container recovery"; skipContainer.Text = "Skip container recovery";
skipContainer.ToolTipText = "Skips the container recovery step.\nImproves loading when dealing with a large num" + skipContainer.ToolTipText = "Skips the container recovery step.\nImproves loading when dealing with a large number of files.";
"ber of files.";
skipContainer.CheckedChanged += skipContainer_CheckedChanged; skipContainer.CheckedChanged += skipContainer_CheckedChanged;
// //
// displayInfo // toolStripSeparator12
// //
displayInfo.Checked = true; toolStripSeparator12.Name = "toolStripSeparator12";
displayInfo.CheckOnClick = true; toolStripSeparator12.Size = new System.Drawing.Size(222, 6);
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;
// //
// toolStripMenuItem14 // toolStripMenuItem14
// //
@@ -1292,20 +1308,17 @@ namespace AssetStudioGUI
showOriginalFileToolStripMenuItem.Visible = false; showOriginalFileToolStripMenuItem.Visible = false;
showOriginalFileToolStripMenuItem.Click += showOriginalFileToolStripMenuItem_Click; showOriginalFileToolStripMenuItem.Click += showOriginalFileToolStripMenuItem_Click;
// //
// toolStripSeparator10 // specifyUnityCNKey
// //
toolStripSeparator10.Name = "toolStripSeparator10"; specifyUnityCNKey.Name = "specifyUnityCNKey";
toolStripSeparator10.Size = new System.Drawing.Size(222, 6); specifyUnityCNKey.Size = new System.Drawing.Size(225, 22);
specifyUnityCNKey.Text = "Specify UnityCN Key";
specifyUnityCNKey.Click += specifyUnityCNKey_Click;
// //
// toolStripSeparator11 // toolStripSeparator13
// //
toolStripSeparator11.Name = "toolStripSeparator11"; toolStripSeparator13.Name = "toolStripSeparator13";
toolStripSeparator11.Size = new System.Drawing.Size(222, 6); toolStripSeparator13.Size = new System.Drawing.Size(222, 6);
//
// toolStripSeparator12
//
toolStripSeparator12.Name = "toolStripSeparator12";
toolStripSeparator12.Size = new System.Drawing.Size(222, 6);
// //
// AssetStudioGUIForm // AssetStudioGUIForm
// //
@@ -1491,6 +1504,8 @@ namespace AssetStudioGUI
private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; private System.Windows.Forms.ToolStripSeparator toolStripSeparator10;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator11; private System.Windows.Forms.ToolStripSeparator toolStripSeparator11;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator12; private System.Windows.Forms.ToolStripSeparator toolStripSeparator12;
private System.Windows.Forms.ToolStripMenuItem specifyUnityCNKey;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator13;
} }
} }

View File

@@ -157,6 +157,7 @@ namespace AssetStudioGUI
Logger.Info($"Target Game type is {Studio.Game.Type}"); Logger.Info($"Target Game type is {Studio.Game.Type}");
MapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged); MapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
UnityCNManager.SetKey(Properties.Settings.Default.selectedUnityCNKey);
} }
private void AssetStudioGUIForm_DragEnter(object sender, DragEventArgs e) private void AssetStudioGUIForm_DragEnter(object sender, DragEventArgs e)
{ {
@@ -274,6 +275,10 @@ namespace AssetStudioGUI
{ {
productName = Studio.Game.Name; productName = Studio.Game.Name;
} }
else if (Studio.Game.Type.IsUnityCN() && UnityCNManager.TryGetEntry(Properties.Settings.Default.selectedUnityCNKey, out var unityCN))
{
productName = unityCN.Name;
}
else else
{ {
productName = "no productName"; productName = "no productName";
@@ -2300,6 +2305,12 @@ namespace AssetStudioGUI
assetBrowser.Show(); assetBrowser.Show();
} }
private void specifyUnityCNKey_Click(object sender, EventArgs e)
{
var unitycn = new UnityCNForm();
unitycn.Show();
}
#region FMOD #region FMOD
private void FMODinit() private void FMODinit()
{ {

View File

@@ -120,9 +120,6 @@
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>312, 17</value> <value>312, 17</value>
</metadata> </metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>432, 17</value>
</metadata>
<data name="fontPreviewBox.Text" xml:space="preserve"> <data name="fontPreviewBox.Text" xml:space="preserve">
<value>abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWYZ <value>abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWYZ
1234567890.:,;'\"(!?)+-*/= 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</value> The quick brown fox jumps over the lazy dog. 1234567890</value>
</data> </data>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>432, 17</value>
</metadata>
<metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>553, 17</value> <value>553, 17</value>
</metadata> </metadata>

View File

@@ -120,6 +120,12 @@
<metadata name="exportUvsTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="exportUvsTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<metadata name="exportUvsTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="keyToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>162, 17</value>
</metadata>
<metadata name="keyToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="keyToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>162, 17</value> <value>162, 17</value>
</metadata> </metadata>
@@ -129,6 +135,9 @@
<metadata name="resolveToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="resolveToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>273, 17</value> <value>273, 17</value>
</metadata> </metadata>
<metadata name="skipToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>404, 17</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>57</value> <value>57</value>
</metadata> </metadata>

View File

@@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [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 { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -442,5 +442,16 @@ namespace AssetStudioGUI.Properties {
this["enableModelPreview"] = value; 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;
}
}
} }
} }

View File

@@ -107,5 +107,8 @@
<Setting Name="enableModelPreview" Type="System.Boolean" Scope="User"> <Setting Name="enableModelPreview" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value> <Value Profile="(Default)">False</Value>
</Setting> </Setting>
<Setting Name="selectedUnityCNKey" Type="System.Int32" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

94
AssetStudioGUI/UnityCNForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,94 @@
namespace AssetStudioGUI
{
partial class UnityCNForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@@ -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<UnityCN.Entry>();
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<DataGridViewRow>().ElementAtOrDefault(i - 1);
if (previousRow != null)
{
specifyUnityCNList.CurrentCell = previousRow.Cells[0];
}
}
if (i != specifyUnityCNList.RowCount - 1)
{
specifyUnityCNList.Rows.RemoveAt(i);
}
}
UnityCNManager.SaveEntries(keys.Reverse<UnityCN.Entry>().ToList());
UnityCNManager.SetKey(specifyUnityCNList.CurrentRow.Index);
Properties.Settings.Default.selectedUnityCNKey = specifyUnityCNList.CurrentRow.Index;
Properties.Settings.Default.Save();
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="NameField.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="KeyField.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>