mirror of
https://github.com/rvtr/ctr_test_tools.git
synced 2025-10-31 13:41:24 -04:00
FalsifyTwlBackup:改ざん処理部を別ファイルに分離。
git-svn-id: file:///Volumes/Transfer/gigaleak_20231201/2020-09-30%20-%20paladin.7z/paladin/ctr_test_tools@32 6b0af911-cb57-b745-895f-eec5701120e1
This commit is contained in:
parent
c50568f384
commit
217a112a59
403
TwlBkpCheck/Windows/FalsifyTwlBackup/Falsify.cs
Normal file
403
TwlBkpCheck/Windows/FalsifyTwlBackup/Falsify.cs
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using TwlBackupBlock;
|
||||||
|
|
||||||
|
namespace FalsifyTwlBackup
|
||||||
|
{
|
||||||
|
partial class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 指定されたbyteサイズで、0のみのbyte列を作成します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataSize">作成するデータのサイズ[単位:byte]</param>
|
||||||
|
/// <returns>作成したデータのbyte列</returns>
|
||||||
|
// TODO 不正データの作成方法を選べるようにしてもいいかも
|
||||||
|
static byte[] CreateImproperData(int dataSize)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[dataSize];
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
//data[i] = (byte)(i);
|
||||||
|
data[i] = 0;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// データの差し替えを行います。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataBlocks">上書き対象となるデータ本体</param>
|
||||||
|
/// <param name="impData">上書きする不正データ</param>
|
||||||
|
static void ReplaceImproperData(AbstractBody dataBlocks, byte[] impData)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < impData.Length; i++)
|
||||||
|
{
|
||||||
|
if (i < dataBlocks.Length) // 上書き先のデータサイズを超えませんように
|
||||||
|
{
|
||||||
|
dataBlocks[i] = impData[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ※未使用
|
||||||
|
/// 暗号化に使用するIVを作成します。
|
||||||
|
/// AES方式で、ブロックサイズはAES_BLOCK_SIZE[bit]と同じです。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>生成したivのbyte列</returns>
|
||||||
|
static byte[] GenerateNewIv()
|
||||||
|
{
|
||||||
|
// 暗号化用IV作成
|
||||||
|
/*
|
||||||
|
* [参考] C#(.NET Framework)のAESを使ってファイルを暗号化してみる
|
||||||
|
* http://hibara.sblo.jp/article/43505136.html
|
||||||
|
*/
|
||||||
|
System.Security.Cryptography.AesCryptoServiceProvider
|
||||||
|
aes = new System.Security.Cryptography.AesCryptoServiceProvider();
|
||||||
|
aes.BlockSize = AES_BLOCK_SIZE; // AESはブロックサイズ、キー長ともに16byte
|
||||||
|
aes.GenerateIV(); // IVの設定(ブロックサイズと同サイズ = 16byte)
|
||||||
|
byte[] byteIV = aes.IV; // IVをバイト列に
|
||||||
|
return byteIV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 署名のハッシュセットを変更します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataBlocks">変更対象となる署名ブロックを有するBlocks</param>
|
||||||
|
/// <param name="hash">新しいハッシュ値</param>
|
||||||
|
/// <param name="section">変更するブロックセクション</param>
|
||||||
|
static void ChangeHashSet(Blocks dataBlocks, byte[] hash, int section)
|
||||||
|
{
|
||||||
|
Debug.Assert((section <= BLOCK_NUM - 1) && (section >= 0));
|
||||||
|
|
||||||
|
SignatureBody signature;
|
||||||
|
signature = (SignatureBody)dataBlocks.signature.body;
|
||||||
|
if (SECTION_CONTENT <= section && section < SECTION_CONTENT + MAX_CONTENTS)
|
||||||
|
{
|
||||||
|
hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.CONTENT + section - SECTION_CONTENT], 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
case SECTION_BANNER:
|
||||||
|
hash.CopyTo(signature.digest.banner, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_HEADER:
|
||||||
|
hash.CopyTo(signature.digest.header, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_TMD:
|
||||||
|
hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.TMD], 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_PUBLIC_SAVE:
|
||||||
|
hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.PUBLIC_SAVE], 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_SUB_BANNER:
|
||||||
|
hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.SUB_BANNER], 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_PRIVATE_SAVE:
|
||||||
|
hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.PRIVATE_SAVE], 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Debug.Assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// byte列を結合します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="front">前半部分に来るbyte列</param>
|
||||||
|
/// <param name="rear">後半部分に来るbyte列</param>
|
||||||
|
/// <returns>連結後のbyte列</returns>
|
||||||
|
static byte[] MergeByteArray(byte[] front, byte[] rear)
|
||||||
|
{
|
||||||
|
return front.Concat(rear).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// byte列をbinファイルに出力します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="improperNo">ファイル名となる改ざんパターンの番号</param>
|
||||||
|
/// <param name="outFolderPath">出力ディレクトリのパス</param>
|
||||||
|
/// <param name="data">ファイルに出力するデータ</param>
|
||||||
|
static void OutputFalsifiedData(int improperNo, string outFolderPath, byte[] data)
|
||||||
|
{
|
||||||
|
string outFalsifiedDataPath = Convert.ToString(improperNo) + EXTENSION; // ファイル名を改ざん番号から生成
|
||||||
|
outFalsifiedDataPath = Path.Combine(outFolderPath, outFalsifiedDataPath); // 出力ディレクトリとファイル名を結合
|
||||||
|
File.WriteAllBytes(outFalsifiedDataPath, data); // 出力
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// データ本体とMACとIVをひとつのブロックに結合します。
|
||||||
|
/// 必要に応じて不正データを挿入します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataBlocks">暗号化するBlockを持つBlocks</param>
|
||||||
|
/// <param name="section">暗号化するBlockのセクション</param>
|
||||||
|
/// <param name="prop">バックアップデータに関するインスタンス</param>
|
||||||
|
/// <param name="impData">挿入する不正データ</param>
|
||||||
|
/// <param name="insertPos">挿入する位置</param>
|
||||||
|
/// <param name="byteMac">格納するMAC</param>
|
||||||
|
/// <param name="byteHash">MACを算出するためのハッシュ値</param>
|
||||||
|
/// <returns>データ本体、MAC、IVを順に結合したbyte列</returns>
|
||||||
|
static byte[] EncryptBlock(Blocks dataBlocks, int section, Properties prop, byte[] impData, int insertPos, byte[] byteMac, byte[] byteHash = null)
|
||||||
|
{
|
||||||
|
byte[] encryptedBlock = new byte[0];
|
||||||
|
|
||||||
|
// (データ本体の前に不正データ挿入)
|
||||||
|
if (insertPos >= FRONT_BANNER && insertPos <= FRONT_SUB_BANNER)
|
||||||
|
{
|
||||||
|
if (section == insertPos - FRONT_BANNER)
|
||||||
|
{
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, impData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// データ本体を暗号化
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, dataBlocks[section].body.GetBytes()); // bodyを暗号化
|
||||||
|
|
||||||
|
// (データ本体の後に不正データ挿入)
|
||||||
|
if (insertPos >= REAR_BANNER && insertPos <= REAR_SUB_BANNER)
|
||||||
|
{
|
||||||
|
if (section == insertPos - REAR_BANNER)
|
||||||
|
{
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, impData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteHash != null)
|
||||||
|
{
|
||||||
|
byte[] buff = Utility.GetSha256(encryptedBlock); // 平文からSHA256ハッシュ算出
|
||||||
|
buff.CopyTo(byteHash, 0);
|
||||||
|
byteMac = Utility.GetAesCmac(byteHash, prop.macKeyData); // SHA256ハッシュからMACを算出
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedBlock = Utility.EncryptBody(encryptedBlock, prop.keyData, dataBlocks[section].iv); // データ本体を暗号化
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, byteMac); // MACを結合
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, dataBlocks[section].iv); // IVを結合
|
||||||
|
|
||||||
|
// (データブロックの後に不正データ挿入)
|
||||||
|
if (section == SECTION_SUB_BANNER && insertPos == REAR_DATA)
|
||||||
|
{
|
||||||
|
encryptedBlock = MergeByteArray(encryptedBlock, impData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 各ブロックを暗号化した後、1つの byte 列に結合します。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataBlocks">暗号化するデータ</param>
|
||||||
|
/// <param name="prop">バックアップデータに関するインスタンス</param>
|
||||||
|
/// <param name="impData">不正データ</param>
|
||||||
|
/// <param name="insertPos">不正データを挿入する位置</param>
|
||||||
|
/// <param name="chgMacHash">ハッシュセット更新の有無</param>
|
||||||
|
/// <returns>全てのBlockを暗号化し、連結したbyte列</returns>
|
||||||
|
static byte[] MergeEncryptBody(Blocks dataBlocks, Properties prop, byte[] impData, int insertPos, int chgMacHash)
|
||||||
|
{
|
||||||
|
byte[] outData = new byte[0];
|
||||||
|
byte[][] encryptedBlocks = new byte[BLOCK_NUM][];
|
||||||
|
|
||||||
|
byte[][] byteHash = new byte[BLOCK_NUM][];
|
||||||
|
byte[][] byteMac = new byte[BLOCK_NUM][];
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
// 各ブロックを暗号化
|
||||||
|
// ( MAC & HASH SET更新する場合 )
|
||||||
|
if (chgMacHash == CHG_MAC_AND_HASHSET)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < BLOCK_NUM; i++) // 署名部以外を暗号化&byte列に結合
|
||||||
|
{
|
||||||
|
byteHash[i] = new byte[HASH_SIZE];
|
||||||
|
if (i != SECTION_SIGNATURE)
|
||||||
|
{
|
||||||
|
if (dataBlocks[i].body != null)
|
||||||
|
{
|
||||||
|
encryptedBlocks[i] = EncryptBlock(dataBlocks, i, prop, impData, insertPos, byteMac[i], byteHash[i]); // ブロックを暗号化&結合
|
||||||
|
ChangeHashSet(dataBlocks, byteHash[i], i); // ハッシュセットを更新
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encryptedBlocks[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 最後に署名部を暗号化&byte列に結合
|
||||||
|
byteHash[SECTION_SIGNATURE] = Utility.GetSha256(dataBlocks[SECTION_SIGNATURE].body.GetBytes()); // 平文からSHA256ハッシュ算出
|
||||||
|
byteMac[SECTION_SIGNATURE] = Utility.GetAesCmac(byteHash[SECTION_SIGNATURE], prop.macKeyData); // SHA256ハッシュからMACを算出
|
||||||
|
encryptedBlocks[SECTION_SIGNATURE] = EncryptBlock(dataBlocks, SECTION_SIGNATURE, prop, impData, insertPos, byteMac[SECTION_SIGNATURE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ( MAC & HASH SET更新しない場合 )
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < BLOCK_NUM; i++)
|
||||||
|
{
|
||||||
|
if (dataBlocks[i].body != null)
|
||||||
|
{
|
||||||
|
encryptedBlocks[i] = EncryptBlock(dataBlocks, i, prop, impData, insertPos, dataBlocks[i].mac); // ブロックを暗号化&結合
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encryptedBlocks[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
// 暗号化した各ブロックを結合
|
||||||
|
for (int i = 0; i < BLOCK_NUM; i++)
|
||||||
|
{
|
||||||
|
if (encryptedBlocks[i] != null)
|
||||||
|
{
|
||||||
|
outData = MergeByteArray(outData, encryptedBlocks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static void FalsifyingData(int improperNo, Properties prop, byte[] improperData)
|
||||||
|
//------------------------------------------------------------
|
||||||
|
// パターン番号に応じた改ざんを実行
|
||||||
|
// [in] int improperNo : 改ざんパターン番号
|
||||||
|
// [in] Properties prop : バックアップデータに関するインスタンス
|
||||||
|
// [in] byte[] improperData : 改ざんに使用する不正データ
|
||||||
|
//------------------------------------------------------------
|
||||||
|
/// <summary>
|
||||||
|
/// パターン番号に応じた改ざんを実行
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="improperNo"></param>
|
||||||
|
/// <param name="prop"></param>
|
||||||
|
/// <param name="improperData"></param>
|
||||||
|
static void FalsifyingData(int improperNo, Properties prop, byte[] improperData)
|
||||||
|
{
|
||||||
|
if (MIN_OF_CAT100 <= improperNo && improperNo <= MAX_OF_CAT100)
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory1xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
else if (MIN_OF_CAT200 <= improperNo && improperNo <= MAX_OF_CAT200)
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory2xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
else if (MIN_OF_CAT300 <= improperNo && improperNo <= MAX_OF_CAT300)
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory3xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
else if (MIN_OF_CAT400 <= improperNo && improperNo <= MAX_OF_CAT400)
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory4xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
else if (MIN_OF_CAT500 <= improperNo && improperNo <= MAX_OF_CAT500)
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory5xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FalsifyingDataCategory9xx(improperNo, prop, improperData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static void SwitchFalsifyingPattern(int falsifyingType, Properties prop)
|
||||||
|
//------------------------------------------------------------
|
||||||
|
// 作成する改ざんデータのパターンを決定。
|
||||||
|
// [in] int falsifyingMode : 出力する改ざんデータパターン
|
||||||
|
// [in] Properties prop : バックアップデータに関するインスタンス
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="falsifyingMode"></param>
|
||||||
|
/// <param name="prop"></param>
|
||||||
|
static void SwitchFalsifyingPattern(int falsifyingMode, Properties prop)
|
||||||
|
{
|
||||||
|
// 改ざんに使用する不正なデータ作成
|
||||||
|
byte[] improperData = CreateImproperData(IMPROPER_DATA_SIZE);
|
||||||
|
|
||||||
|
switch (falsifyingMode)
|
||||||
|
{
|
||||||
|
case MODE_ALL: // 全パターン
|
||||||
|
Console.WriteLine(" Falsifying \"all pattern\".");
|
||||||
|
for (int i = 0; i < NUM_OF_PATTERN.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
for (int j = NUM_OF_PATTERN[i, 0]; j <= NUM_OF_PATTERN[i, 1]; j++)
|
||||||
|
{
|
||||||
|
FalsifyingData(j, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", j);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[i, 0]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_CAT100: // 1xx系列全て
|
||||||
|
Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[MODE_CAT100 - 1, 0]);
|
||||||
|
for (int i = 100; i <= MAX_OF_CAT100; i++)
|
||||||
|
{
|
||||||
|
FalsifyingData(i, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", i);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[MODE_CAT100 - 1, 0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_CAT200: // 2xx系列全て
|
||||||
|
Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[MODE_CAT200 - 1, 0]);
|
||||||
|
for (int i = 200; i <= MAX_OF_CAT200; i++)
|
||||||
|
{
|
||||||
|
FalsifyingData(i, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", i);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[MODE_CAT200 - 1, 0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_CAT300: // 3xx系列全て
|
||||||
|
Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[MODE_CAT300 - 1, 0]);
|
||||||
|
for (int i = 300; i <= MAX_OF_CAT300; i++)
|
||||||
|
{
|
||||||
|
FalsifyingData(i, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", i);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[MODE_CAT300 - 1, 0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_CAT400: // 4xx系列全て
|
||||||
|
Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[MODE_CAT400 - 1, 0]);
|
||||||
|
for (int i = 400; i <= MAX_OF_CAT400; i++)
|
||||||
|
{
|
||||||
|
FalsifyingData(i, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", i);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[MODE_CAT400 - 1, 0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_CAT500: // 5xx系列全て
|
||||||
|
Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[MODE_CAT500 - 1, 0]);
|
||||||
|
for (int i = 500; i <= MAX_OF_CAT500; i++)
|
||||||
|
{
|
||||||
|
FalsifyingData(i, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.", i);
|
||||||
|
}
|
||||||
|
Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[MODE_CAT500 - 1, 0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 1パターンのみを個別指定
|
||||||
|
Console.WriteLine(" Falsifying pattern No.{0}.", falsifyingMode);
|
||||||
|
FalsifyingData(falsifyingMode, prop, improperData);
|
||||||
|
Console.WriteLine(" Complete falsifying No.{0}.\n", falsifyingMode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -49,6 +49,7 @@
|
|||||||
<Compile Include="Category4xx.cs" />
|
<Compile Include="Category4xx.cs" />
|
||||||
<Compile Include="Category5xx.cs" />
|
<Compile Include="Category5xx.cs" />
|
||||||
<Compile Include="Category9xx.cs" />
|
<Compile Include="Category9xx.cs" />
|
||||||
|
<Compile Include="Falsify.cs" />
|
||||||
<Compile Include="main.cs" />
|
<Compile Include="main.cs" />
|
||||||
<Compile Include="Properties.cs" />
|
<Compile Include="Properties.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user