This commit is contained in:
2020-07-09 08:50:24 +08:00
parent 13d25f4707
commit c523462b82
1818 changed files with 174940 additions and 582 deletions

View File

@@ -0,0 +1,277 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
namespace Org.BouncyCastle.Crypto.Encodings
{
/**
* ISO 9796-1 padding. Note in the light of recent results you should
* only use this with RSA (rather than the "simpler" Rabin keys) and you
* should never use it with anything other than a hash (ie. even if the
* message is small don't sign the message, sign it's hash) or some "random"
* value. See your favorite search engine for details.
*/
public class ISO9796d1Encoding
: IAsymmetricBlockCipher
{
private static readonly BigInteger Sixteen = BigInteger.ValueOf(16);
private static readonly BigInteger Six = BigInteger.ValueOf(6);
private static readonly byte[] shadows = { 0xe, 0x3, 0x5, 0x8, 0x9, 0x4, 0x2, 0xf,
0x0, 0xd, 0xb, 0x6, 0x7, 0xa, 0xc, 0x1 };
private static readonly byte[] inverse = { 0x8, 0xf, 0x6, 0x1, 0x5, 0x2, 0xb, 0xc,
0x3, 0x4, 0xd, 0xa, 0xe, 0x9, 0x0, 0x7 };
private readonly IAsymmetricBlockCipher engine;
private bool forEncryption;
private int bitSize;
private int padBits = 0;
private BigInteger modulus;
public ISO9796d1Encoding(
IAsymmetricBlockCipher cipher)
{
this.engine = cipher;
}
public string AlgorithmName
{
get { return engine.AlgorithmName + "/ISO9796-1Padding"; }
}
public IAsymmetricBlockCipher GetUnderlyingCipher()
{
return engine;
}
public void Init(
bool forEncryption,
ICipherParameters parameters)
{
RsaKeyParameters kParam;
if (parameters is ParametersWithRandom)
{
ParametersWithRandom rParam = (ParametersWithRandom)parameters;
kParam = (RsaKeyParameters)rParam.Parameters;
}
else
{
kParam = (RsaKeyParameters)parameters;
}
engine.Init(forEncryption, parameters);
modulus = kParam.Modulus;
bitSize = modulus.BitLength;
this.forEncryption = forEncryption;
}
/**
* return the input block size. The largest message we can process
* is (key_size_in_bits + 3)/16, which in our world comes to
* key_size_in_bytes / 2.
*/
public int GetInputBlockSize()
{
int baseBlockSize = engine.GetInputBlockSize();
if (forEncryption)
{
return (baseBlockSize + 1) / 2;
}
else
{
return baseBlockSize;
}
}
/**
* return the maximum possible size for the output.
*/
public int GetOutputBlockSize()
{
int baseBlockSize = engine.GetOutputBlockSize();
if (forEncryption)
{
return baseBlockSize;
}
else
{
return (baseBlockSize + 1) / 2;
}
}
/**
* set the number of bits in the next message to be treated as
* pad bits.
*/
public void SetPadBits(
int padBits)
{
if (padBits > 7)
{
throw new ArgumentException("padBits > 7");
}
this.padBits = padBits;
}
/**
* retrieve the number of pad bits in the last decoded message.
*/
public int GetPadBits()
{
return padBits;
}
public byte[] ProcessBlock(
byte[] input,
int inOff,
int length)
{
if (forEncryption)
{
return EncodeBlock(input, inOff, length);
}
else
{
return DecodeBlock(input, inOff, length);
}
}
private byte[] EncodeBlock(
byte[] input,
int inOff,
int inLen)
{
byte[] block = new byte[(bitSize + 7) / 8];
int r = padBits + 1;
int z = inLen;
int t = (bitSize + 13) / 16;
for (int i = 0; i < t; i += z)
{
if (i > t - z)
{
Array.Copy(input, inOff + inLen - (t - i),
block, block.Length - t, t - i);
}
else
{
Array.Copy(input, inOff, block, block.Length - (i + z), z);
}
}
for (int i = block.Length - 2 * t; i != block.Length; i += 2)
{
byte val = block[block.Length - t + i / 2];
block[i] = (byte)((shadows[(uint) (val & 0xff) >> 4] << 4)
| shadows[val & 0x0f]);
block[i + 1] = val;
}
block[block.Length - 2 * z] ^= (byte) r;
block[block.Length - 1] = (byte)((block[block.Length - 1] << 4) | 0x06);
int maxBit = (8 - (bitSize - 1) % 8);
int offSet = 0;
if (maxBit != 8)
{
block[0] &= (byte) ((ushort) 0xff >> maxBit);
block[0] |= (byte) ((ushort) 0x80 >> maxBit);
}
else
{
block[0] = 0x00;
block[1] |= 0x80;
offSet = 1;
}
return engine.ProcessBlock(block, offSet, block.Length - offSet);
}
/**
* @exception InvalidCipherTextException if the decrypted block is not a valid ISO 9796 bit string
*/
private byte[] DecodeBlock(
byte[] input,
int inOff,
int inLen)
{
byte[] block = engine.ProcessBlock(input, inOff, inLen);
int r = 1;
int t = (bitSize + 13) / 16;
BigInteger iS = new BigInteger(1, block);
BigInteger iR;
if (iS.Mod(Sixteen).Equals(Six))
{
iR = iS;
}
else
{
iR = modulus.Subtract(iS);
if (!iR.Mod(Sixteen).Equals(Six))
throw new InvalidCipherTextException("resulting integer iS or (modulus - iS) is not congruent to 6 mod 16");
}
block = iR.ToByteArrayUnsigned();
if ((block[block.Length - 1] & 0x0f) != 0x6)
throw new InvalidCipherTextException("invalid forcing byte in block");
block[block.Length - 1] =
(byte)(((ushort)(block[block.Length - 1] & 0xff) >> 4)
| ((inverse[(block[block.Length - 2] & 0xff) >> 4]) << 4));
block[0] = (byte)((shadows[(uint) (block[1] & 0xff) >> 4] << 4)
| shadows[block[1] & 0x0f]);
bool boundaryFound = false;
int boundary = 0;
for (int i = block.Length - 1; i >= block.Length - 2 * t; i -= 2)
{
int val = ((shadows[(uint) (block[i] & 0xff) >> 4] << 4)
| shadows[block[i] & 0x0f]);
if (((block[i - 1] ^ val) & 0xff) != 0)
{
if (!boundaryFound)
{
boundaryFound = true;
r = (block[i - 1] ^ val) & 0xff;
boundary = i - 1;
}
else
{
throw new InvalidCipherTextException("invalid tsums in block");
}
}
}
block[boundary] = 0;
byte[] nblock = new byte[(block.Length - boundary) / 2];
for (int i = 0; i < nblock.Length; i++)
{
nblock[i] = block[2 * i + boundary + 1];
}
padBits = r - 1;
return nblock;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0002084b4e9fb467f8e2dc83cebc1784
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,353 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace Org.BouncyCastle.Crypto.Encodings
{
/**
* Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2.
*/
public class OaepEncoding
: IAsymmetricBlockCipher
{
private byte[] defHash;
private IDigest hash;
private IDigest mgf1Hash;
private IAsymmetricBlockCipher engine;
private SecureRandom random;
private bool forEncryption;
public OaepEncoding(
IAsymmetricBlockCipher cipher)
: this(cipher, new Sha1Digest(), null)
{
}
public OaepEncoding(
IAsymmetricBlockCipher cipher,
IDigest hash)
: this(cipher, hash, null)
{
}
public OaepEncoding(
IAsymmetricBlockCipher cipher,
IDigest hash,
byte[] encodingParams)
: this(cipher, hash, hash, encodingParams)
{
}
public OaepEncoding(
IAsymmetricBlockCipher cipher,
IDigest hash,
IDigest mgf1Hash,
byte[] encodingParams)
{
this.engine = cipher;
this.hash = hash;
this.mgf1Hash = mgf1Hash;
this.defHash = new byte[hash.GetDigestSize()];
if (encodingParams != null)
{
hash.BlockUpdate(encodingParams, 0, encodingParams.Length);
}
hash.DoFinal(defHash, 0);
}
public IAsymmetricBlockCipher GetUnderlyingCipher()
{
return engine;
}
public string AlgorithmName
{
get { return engine.AlgorithmName + "/OAEPPadding"; }
}
public void Init(
bool forEncryption,
ICipherParameters param)
{
if (param is ParametersWithRandom)
{
ParametersWithRandom rParam = (ParametersWithRandom)param;
this.random = rParam.Random;
}
else
{
this.random = new SecureRandom();
}
engine.Init(forEncryption, param);
this.forEncryption = forEncryption;
}
public int GetInputBlockSize()
{
int baseBlockSize = engine.GetInputBlockSize();
if (forEncryption)
{
return baseBlockSize - 1 - 2 * defHash.Length;
}
else
{
return baseBlockSize;
}
}
public int GetOutputBlockSize()
{
int baseBlockSize = engine.GetOutputBlockSize();
if (forEncryption)
{
return baseBlockSize;
}
else
{
return baseBlockSize - 1 - 2 * defHash.Length;
}
}
public byte[] ProcessBlock(
byte[] inBytes,
int inOff,
int inLen)
{
if (forEncryption)
{
return EncodeBlock(inBytes, inOff, inLen);
}
else
{
return DecodeBlock(inBytes, inOff, inLen);
}
}
private byte[] EncodeBlock(
byte[] inBytes,
int inOff,
int inLen)
{
byte[] block = new byte[GetInputBlockSize() + 1 + 2 * defHash.Length];
//
// copy in the message
//
Array.Copy(inBytes, inOff, block, block.Length - inLen, inLen);
//
// add sentinel
//
block[block.Length - inLen - 1] = 0x01;
//
// as the block is already zeroed - there's no need to add PS (the >= 0 pad of 0)
//
//
// add the hash of the encoding params.
//
Array.Copy(defHash, 0, block, defHash.Length, defHash.Length);
//
// generate the seed.
//
byte[] seed = SecureRandom.GetNextBytes(random, defHash.Length);
//
// mask the message block.
//
byte[] mask = maskGeneratorFunction1(seed, 0, seed.Length, block.Length - defHash.Length);
for (int i = defHash.Length; i != block.Length; i++)
{
block[i] ^= mask[i - defHash.Length];
}
//
// add in the seed
//
Array.Copy(seed, 0, block, 0, defHash.Length);
//
// mask the seed.
//
mask = maskGeneratorFunction1(
block, defHash.Length, block.Length - defHash.Length, defHash.Length);
for (int i = 0; i != defHash.Length; i++)
{
block[i] ^= mask[i];
}
return engine.ProcessBlock(block, 0, block.Length);
}
/**
* @exception InvalidCipherTextException if the decrypted block turns out to
* be badly formatted.
*/
private byte[] DecodeBlock(
byte[] inBytes,
int inOff,
int inLen)
{
byte[] data = engine.ProcessBlock(inBytes, inOff, inLen);
byte[] block;
//
// as we may have zeros in our leading bytes for the block we produced
// on encryption, we need to make sure our decrypted block comes back
// the same size.
//
if (data.Length < engine.GetOutputBlockSize())
{
block = new byte[engine.GetOutputBlockSize()];
Array.Copy(data, 0, block, block.Length - data.Length, data.Length);
}
else
{
block = data;
}
if (block.Length < (2 * defHash.Length) + 1)
{
throw new InvalidCipherTextException("data too short");
}
//
// unmask the seed.
//
byte[] mask = maskGeneratorFunction1(
block, defHash.Length, block.Length - defHash.Length, defHash.Length);
for (int i = 0; i != defHash.Length; i++)
{
block[i] ^= mask[i];
}
//
// unmask the message block.
//
mask = maskGeneratorFunction1(block, 0, defHash.Length, block.Length - defHash.Length);
for (int i = defHash.Length; i != block.Length; i++)
{
block[i] ^= mask[i - defHash.Length];
}
//
// check the hash of the encoding params.
// long check to try to avoid this been a source of a timing attack.
//
{
int diff = 0;
for (int i = 0; i < defHash.Length; ++i)
{
diff |= (byte)(defHash[i] ^ block[defHash.Length + i]);
}
if (diff != 0)
throw new InvalidCipherTextException("data hash wrong");
}
//
// find the data block
//
int start;
for (start = 2 * defHash.Length; start != block.Length; start++)
{
if (block[start] != 0)
{
break;
}
}
if (start > (block.Length - 1) || block[start] != 1)
{
throw new InvalidCipherTextException("data start wrong " + start);
}
start++;
//
// extract the data block
//
byte[] output = new byte[block.Length - start];
Array.Copy(block, start, output, 0, output.Length);
return output;
}
/**
* int to octet string.
*/
private void ItoOSP(
int i,
byte[] sp)
{
sp[0] = (byte)((uint)i >> 24);
sp[1] = (byte)((uint)i >> 16);
sp[2] = (byte)((uint)i >> 8);
sp[3] = (byte)((uint)i >> 0);
}
/**
* mask generator function, as described in PKCS1v2.
*/
private byte[] maskGeneratorFunction1(
byte[] Z,
int zOff,
int zLen,
int length)
{
byte[] mask = new byte[length];
byte[] hashBuf = new byte[mgf1Hash.GetDigestSize()];
byte[] C = new byte[4];
int counter = 0;
hash.Reset();
do
{
ItoOSP(counter, C);
mgf1Hash.BlockUpdate(Z, zOff, zLen);
mgf1Hash.BlockUpdate(C, 0, C.Length);
mgf1Hash.DoFinal(hashBuf, 0);
Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, hashBuf.Length);
}
while (++counter < (length / hashBuf.Length));
if ((counter * hashBuf.Length) < length)
{
ItoOSP(counter, C);
mgf1Hash.BlockUpdate(Z, zOff, zLen);
mgf1Hash.BlockUpdate(C, 0, C.Length);
mgf1Hash.DoFinal(hashBuf, 0);
Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, mask.Length - (counter * hashBuf.Length));
}
return mask;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 352f2205615d94d02914115fe9414b2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,386 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Crypto.Encodings
{
/**
* this does your basic Pkcs 1 v1.5 padding - whether or not you should be using this
* depends on your application - see Pkcs1 Version 2 for details.
*/
public class Pkcs1Encoding
: IAsymmetricBlockCipher
{
/**
* some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to
* work with one of these set the system property Org.BouncyCastle.Pkcs1.Strict to false.
*/
public const string StrictLengthEnabledProperty = "Org.BouncyCastle.Pkcs1.Strict";
private const int HeaderLength = 10;
/**
* The same effect can be achieved by setting the static property directly
* <p>
* The static property is checked during construction of the encoding object, it is set to
* true by default.
* </p>
*/
public static bool StrictLengthEnabled
{
get { return strictLengthEnabled[0]; }
set { strictLengthEnabled[0] = value; }
}
private static readonly bool[] strictLengthEnabled;
static Pkcs1Encoding()
{
string strictProperty = Platform.GetEnvironmentVariable(StrictLengthEnabledProperty);
strictLengthEnabled = new bool[]{ strictProperty == null || strictProperty.Equals("true")};
}
private SecureRandom random;
private IAsymmetricBlockCipher engine;
private bool forEncryption;
private bool forPrivateKey;
private bool useStrictLength;
private int pLen = -1;
private byte[] fallback = null;
/**
* Basic constructor.
* @param cipher
*/
public Pkcs1Encoding(
IAsymmetricBlockCipher cipher)
{
this.engine = cipher;
this.useStrictLength = StrictLengthEnabled;
}
/**
* Constructor for decryption with a fixed plaintext length.
*
* @param cipher The cipher to use for cryptographic operation.
* @param pLen Length of the expected plaintext.
*/
public Pkcs1Encoding(IAsymmetricBlockCipher cipher, int pLen)
{
this.engine = cipher;
this.useStrictLength = StrictLengthEnabled;
this.pLen = pLen;
}
/**
* Constructor for decryption with a fixed plaintext length and a fallback
* value that is returned, if the padding is incorrect.
*
* @param cipher
* The cipher to use for cryptographic operation.
* @param fallback
* The fallback value, we don't to a arraycopy here.
*/
public Pkcs1Encoding(IAsymmetricBlockCipher cipher, byte[] fallback)
{
this.engine = cipher;
this.useStrictLength = StrictLengthEnabled;
this.fallback = fallback;
this.pLen = fallback.Length;
}
public IAsymmetricBlockCipher GetUnderlyingCipher()
{
return engine;
}
public string AlgorithmName
{
get { return engine.AlgorithmName + "/PKCS1Padding"; }
}
public void Init(
bool forEncryption,
ICipherParameters parameters)
{
AsymmetricKeyParameter kParam;
if (parameters is ParametersWithRandom)
{
ParametersWithRandom rParam = (ParametersWithRandom)parameters;
this.random = rParam.Random;
kParam = (AsymmetricKeyParameter)rParam.Parameters;
}
else
{
this.random = new SecureRandom();
kParam = (AsymmetricKeyParameter)parameters;
}
engine.Init(forEncryption, parameters);
this.forPrivateKey = kParam.IsPrivate;
this.forEncryption = forEncryption;
}
public int GetInputBlockSize()
{
int baseBlockSize = engine.GetInputBlockSize();
return forEncryption
? baseBlockSize - HeaderLength
: baseBlockSize;
}
public int GetOutputBlockSize()
{
int baseBlockSize = engine.GetOutputBlockSize();
return forEncryption
? baseBlockSize
: baseBlockSize - HeaderLength;
}
public byte[] ProcessBlock(
byte[] input,
int inOff,
int length)
{
return forEncryption
? EncodeBlock(input, inOff, length)
: DecodeBlock(input, inOff, length);
}
private byte[] EncodeBlock(
byte[] input,
int inOff,
int inLen)
{
if (inLen > GetInputBlockSize())
throw new ArgumentException("input data too large", "inLen");
byte[] block = new byte[engine.GetInputBlockSize()];
if (forPrivateKey)
{
block[0] = 0x01; // type code 1
for (int i = 1; i != block.Length - inLen - 1; i++)
{
block[i] = (byte)0xFF;
}
}
else
{
random.NextBytes(block); // random fill
block[0] = 0x02; // type code 2
//
// a zero byte marks the end of the padding, so all
// the pad bytes must be non-zero.
//
for (int i = 1; i != block.Length - inLen - 1; i++)
{
while (block[i] == 0)
{
block[i] = (byte)random.NextInt();
}
}
}
block[block.Length - inLen - 1] = 0x00; // mark the end of the padding
Array.Copy(input, inOff, block, block.Length - inLen, inLen);
return engine.ProcessBlock(block, 0, block.Length);
}
/**
* Checks if the argument is a correctly PKCS#1.5 encoded Plaintext
* for encryption.
*
* @param encoded The Plaintext.
* @param pLen Expected length of the plaintext.
* @return Either 0, if the encoding is correct, or -1, if it is incorrect.
*/
private static int CheckPkcs1Encoding(byte[] encoded, int pLen)
{
int correct = 0;
/*
* Check if the first two bytes are 0 2
*/
correct |= (encoded[0] ^ 2);
/*
* Now the padding check, check for no 0 byte in the padding
*/
int plen = encoded.Length - (
pLen /* Lenght of the PMS */
+ 1 /* Final 0-byte before PMS */
);
for (int i = 1; i < plen; i++)
{
int tmp = encoded[i];
tmp |= tmp >> 1;
tmp |= tmp >> 2;
tmp |= tmp >> 4;
correct |= (tmp & 1) - 1;
}
/*
* Make sure the padding ends with a 0 byte.
*/
correct |= encoded[encoded.Length - (pLen + 1)];
/*
* Return 0 or 1, depending on the result.
*/
correct |= correct >> 1;
correct |= correct >> 2;
correct |= correct >> 4;
return ~((correct & 1) - 1);
}
/**
* Decode PKCS#1.5 encoding, and return a random value if the padding is not correct.
*
* @param in The encrypted block.
* @param inOff Offset in the encrypted block.
* @param inLen Length of the encrypted block.
* @param pLen Length of the desired output.
* @return The plaintext without padding, or a random value if the padding was incorrect.
*
* @throws InvalidCipherTextException
*/
private byte[] DecodeBlockOrRandom(byte[] input, int inOff, int inLen)
{
if (!forPrivateKey)
throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing");
byte[] block = engine.ProcessBlock(input, inOff, inLen);
byte[] random = null;
if (this.fallback == null)
{
random = new byte[this.pLen];
this.random.NextBytes(random);
}
else
{
random = fallback;
}
/*
* TODO: This is a potential dangerous side channel. However, you can
* fix this by changing the RSA engine in a way, that it will always
* return blocks of the same length and prepend them with 0 bytes if
* needed.
*/
if (block.Length < GetOutputBlockSize())
throw new InvalidCipherTextException("block truncated");
/*
* TODO: Potential side channel. Fix it by making the engine always
* return blocks of the correct length.
*/
if (useStrictLength && block.Length != engine.GetOutputBlockSize())
throw new InvalidCipherTextException("block incorrect size");
/*
* Check the padding.
*/
int correct = Pkcs1Encoding.CheckPkcs1Encoding(block, this.pLen);
/*
* Now, to a constant time constant memory copy of the decrypted value
* or the random value, depending on the validity of the padding.
*/
byte[] result = new byte[this.pLen];
for (int i = 0; i < this.pLen; i++)
{
result[i] = (byte)((block[i+(block.Length-pLen)]&(~correct)) | (random[i]&correct));
}
return result;
}
/**
* @exception InvalidCipherTextException if the decrypted block is not in Pkcs1 format.
*/
private byte[] DecodeBlock(
byte[] input,
int inOff,
int inLen)
{
/*
* If the length of the expected plaintext is known, we use a constant-time decryption.
* If the decryption fails, we return a random value.
*/
if (this.pLen != -1)
{
return this.DecodeBlockOrRandom(input, inOff, inLen);
}
byte[] block = engine.ProcessBlock(input, inOff, inLen);
if (block.Length < GetOutputBlockSize())
{
throw new InvalidCipherTextException("block truncated");
}
byte type = block[0];
if (type != 1 && type != 2)
{
throw new InvalidCipherTextException("unknown block type");
}
if (useStrictLength && block.Length != engine.GetOutputBlockSize())
{
throw new InvalidCipherTextException("block incorrect size");
}
//
// find and extract the message block.
//
int start;
for (start = 1; start != block.Length; start++)
{
byte pad = block[start];
if (pad == 0)
{
break;
}
if (type == 1 && pad != (byte)0xff)
{
throw new InvalidCipherTextException("block padding incorrect");
}
}
start++; // data should start at the next byte
if (start > block.Length || start < HeaderLength)
{
throw new InvalidCipherTextException("no data in block");
}
byte[] result = new byte[block.Length - start];
Array.Copy(block, start, result, 0, result.Length);
return result;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 885b5b4270fcf4b9a8528c81b42fcab1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: