Browse Source

Init assembly with Valis compression

master
BahaBulle 7 years ago
commit
d2ac6e64a4
7 changed files with 801 additions and 0 deletions
  1. +63
    -0
      .gitattributes
  2. +189
    -0
      .gitignore
  3. +22
    -0
      Fr.BahaBulle.Compression.sln
  4. +17
    -0
      Fr.BahaBulle.Compression/Compression.cs
  5. +70
    -0
      Fr.BahaBulle.Compression/Fr.BahaBulle.Compression.csproj
  6. +36
    -0
      Fr.BahaBulle.Compression/Properties/AssemblyInfo.cs
  7. +404
    -0
      Fr.BahaBulle.Compression/Valis/Valis.cs

+ 63
- 0
.gitattributes View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

+ 189
- 0
.gitignore View File

@ -0,0 +1,189 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
# NuGet Packages Directory
packages/*
## TODO: If the tool you use requires repositories.config
## uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since
# NuGet packages use it for MSBuild targets.
# This line needs to be after the ignore of the build folder
# (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml

+ 22
- 0
Fr.BahaBulle.Compression.sln View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.40629.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fr.BahaBulle.Compression", "Fr.BahaBulle.Compression\Fr.BahaBulle.Compression.csproj", "{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

+ 17
- 0
Fr.BahaBulle.Compression/Compression.cs View File

@ -0,0 +1,17 @@
using Fr.BahaBulle.Compression.Valis;
using Fr.BahaBulle.Tools.Parser;
namespace Fr.BahaBulle.Compression
{
public class Compression
{
public static void InitModule()
{
Parser parser = Parser.INSTANCE;
parser.AddMethod("decomp-valis", new Parser.ProcessMethod(cValis.ValisDecompression));
parser.AddMethod("comp-valis", new Parser.ProcessMethod(cValis.ValisCompression));
}
}
}

+ 70
- 0
Fr.BahaBulle.Compression/Fr.BahaBulle.Compression.csproj View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{611D58ED-62D2-4B2F-8DBD-B9172AD37A52}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Fr.BahaBulle.Compression</RootNamespace>
<AssemblyName>Fr.BahaBulle.Compression</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Build\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<BaseIntermediateOutputPath>..\Build\obj</BaseIntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\Build\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<BaseIntermediateOutputPath>..\Build\obj</BaseIntermediateOutputPath>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>Fr.BahaBulle.Key.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Fr.BahaBulle.Tools, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ee7bc49d31bfd871, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Fr.BahaBulle.Tools\Build\bin\Release\Fr.BahaBulle.Tools.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Compression.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Valis\Valis.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Fr.BahaBulle.Key.pfx" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

+ 36
- 0
Fr.BahaBulle.Compression/Properties/AssemblyInfo.cs View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Les informations générales relatives à un assembly dépendent de
// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations
// associées à un assembly.
[assembly: AssemblyTitle("Fr.BahaBulle.Compression")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("BahaBulle")]
[assembly: AssemblyProduct("Fr.BahaBulle.Compression")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly
// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de
// COM, affectez la valeur true à l'attribut ComVisible sur ce type.
[assembly: ComVisible(false)]
// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM
[assembly: Guid("f93fea32-332e-485b-ad8b-2e4126a35a47")]
// Les informations de version pour un assembly se composent des quatre valeurs suivantes :
//
// Version principale
// Version secondaire
// Numéro de build
// Révision
//
// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut
// en utilisant '*', comme indiqué ci-dessous :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

+ 404
- 0
Fr.BahaBulle.Compression/Valis/Valis.cs View File

@ -0,0 +1,404 @@
using Fr.BahaBulle.Tools.Common;
using Fr.BahaBulle.Tools.Parser;
using Fr.BahaBulle.Tools.TraceLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace Fr.BahaBulle.Compression.Valis
{
internal static class cValis
{
private struct st_param
{
public int length;
public int offset;
}
public static bool ValisDecompression(List<Parser.stParam> param)
{
Parser parser = Parser.INSTANCE;
if (param.Count != 2)
{
Trace.WriteLine(string.Format("Number of parameters incorrect - 2 waited ; got {0}", param.Count));
return false;
}
Parser.stStream source = default(Parser.stStream);
long source_adress = 0;
Parser.stStream destination = default(Parser.stStream);
object obj;
foreach (Parser.stParam arg in param)
{
if (arg.name == "source")
{
obj = parser.GetParam(arg.param, "file");
if (obj == null)
{
Trace.WriteLine("Parameter file not found");
return false;
}
else
source = (Parser.stStream)obj;
obj = parser.GetParam(arg.param, "adress", "long");
if (obj != null)
source_adress = (long)obj;
else
source_adress = 0;
}
else if (arg.name == "dest")
{
obj = parser.GetParam(arg.param, "file", null, true);
if (obj == null)
destination = parser.NewStream(arg.param["file"], true);
else
destination = (Parser.stStream)obj;
}
else
{
Trace.WriteLine(string.Format("Incorrect option - source, dest, pointer ; get {0}", arg.name));
return false;
}
}
source.file.Position = source_adress;
if (!Decomp(source, destination))
return false;
if (!string.IsNullOrEmpty(destination.filename))
parser.WriteStream(destination);
return true;
}
public static bool ValisCompression(List<Parser.stParam> param)
{
Parser parser = Parser.INSTANCE;
if (param.Count != 2)
{
Trace.WriteLine(string.Format("Number of parameters incorrect - 2 waited ; got {0}", param.Count));
return false;
}
Parser.stStream source = default(Parser.stStream);
Parser.stStream destination = default(Parser.stStream);
object obj;
foreach (Parser.stParam arg in param)
{
if (arg.name == "source")
{
obj = parser.GetParam(arg.param, "file");
if (obj == null)
{
Trace.WriteLine("Parameter file not found");
return false;
}
else
source = (Parser.stStream)obj;
}
else if (arg.name == "dest")
{
obj = parser.GetParam(arg.param, "file", null, true);
if (obj == null)
destination = parser.NewStream(arg.param["file"], true);
else
destination = (Parser.stStream)obj;
}
else
{
Trace.WriteLine(string.Format("Incorrect option - source, dest, pointer ; get {0}", arg.name));
return false;
}
}
if (!Comp(source, destination))
return false;
return true;
}
#region Private methods
/// <summary>
/// Initialise the buffer
/// </summary>
/// <returns></returns>
private static byte[] InitBuffer(int size = 0x1000)
{
byte[] buffer = new byte[size];
int pos = 0;
byte value = 0x00;
for (int i = 0; i < 0x100; i++)
{
for (int j = 0; j < 13; j++)
{
buffer[pos++] = value;
}
value++;
}
value = 0x00;
for (int i = 0; i < 0x100; i++)
{
buffer[pos++] = value++;
}
value = 0xFF;
for (int i = 0; i < 0x100; i++)
{
buffer[pos++] = value--;
}
for (int i = 0; i < 0x80; i++)
{
buffer[pos++] = 0x00;
}
for (int i = 0; i < 0x80; i++)
{
buffer[pos++] = 0x20;
}
return buffer;
}
/// <summary>
/// Find the greater LZ compression from a position
/// </summary>
/// <param name="a_bytes">Array of bytes where look for RLE compression</param>
/// <param name="pos">Current position in the array</param>
/// <returns></returns>
private static st_param FindLZInBuffer(byte[] a_bytes, int pos, int size_buffer, int len_max)
{
st_param param;
int dict = 0;
int pos_result = 0;
int len_result = 0;
if (pos > 0x1000)
dict = pos - 0xFFF;
while (dict < pos)
{
int len = 0;
while (pos + len < a_bytes.Length && a_bytes[dict + len] == a_bytes[pos + len] && (len < len_max))
len++;
if (len >= len_result)
{
pos_result = dict;
len_result = len;
}
dict++;
}
param.length = len_result;
param.offset = pos_result;
return param;
}
/// <summary>
/// Decompress a stream
/// </summary>
/// <param name="Source">Stream to decompress</param>
/// <param name="Destination">Destination stream</param>
/// <returns></returns>
private static bool Decomp(Parser.stStream Source, Parser.stStream Destination)
{
int pos = 0xFEE;
int curr_pos = 0;
byte[] bytes = new byte[0x1000];
byte one_byte;
UInt16 two_bytes;
LogTrace trace = LogTrace.INSTANCE;
try
{
using (BinaryReader br = new BinaryReader(Source.file, Encoding.Default, true))
{
UInt32 size_in = br.ReadUInt32().ToBigEndian32();
UInt32 size_out = br.ReadUInt32().ToBigEndian32();
bytes = InitBuffer();
if (trace.TSwitch.TraceVerbose)
{
using (BinaryWriter bw = new BinaryWriter(File.Open("buffer.bin", FileMode.Create)))
{
bw.Write(bytes);
}
}
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format("Size IN {0}\nSize OUT {1}", size_in, size_out));
while (curr_pos < size_out)
{
byte header = br.ReadByte();
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format("header {0:X08} : {1:X02}", br.BaseStream.Position - 1, header));
for (int i = 0; i < 8; i++)
{
if ((header & 1) == 1)
{
one_byte = br.ReadByte();
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" {0} - Ecrit {1:X08} : {2:X02}", i, Destination.file.Position, one_byte));
Destination.file.WriteByte(one_byte);
curr_pos++;
bytes[pos] = one_byte;
pos = (pos + 1) & 0xFFF;
}
else
{
two_bytes = br.ReadUInt16();
int nb = ((two_bytes >> 8) & 0x0F) + 3;
int pos_lz = ((two_bytes & 0xF000) >> 4) | (two_bytes & 0xFF);
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" {0} - LZ : {1:X04}", i, two_bytes));
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" nb : 0x{0:X02} ({1})", nb, nb));
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" Pos : 0x{0:X04} ({1})", pos_lz, pos_lz));
Trace.WriteIf(trace.TSwitch.TraceVerbose, string.Format(" Ecrit {0:X08} : ", Destination.file.Position));
for (int j = 0; j < nb; j++)
{
Destination.file.WriteByte(bytes[pos_lz]);
curr_pos++;
bytes[pos] = bytes[pos_lz];
Trace.WriteIf(trace.TSwitch.TraceVerbose, string.Format("{0:X02} ", bytes[pos_lz]));
pos = (pos + 1) & 0xFFF;
pos_lz = (pos_lz + 1) & 0xFFF;
}
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, "");
}
header >>= 1;
if (curr_pos >= size_out)
break;
}
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, "");
}
}
return true;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("(EE) {0}", ex.Message));
return false;
}
}
/// <summary>
/// Compress a stream
/// </summary>
/// <param name="Source">Stream to compress</param>
/// <param name="Destination">Destination stream</param>
/// <returns></returns>
private static bool Comp(Parser.stStream Source, Parser.stStream Destination)
{
st_param param_lz;
int size_out = (int)Source.file.Length;
int pos_temp = 0;
int pos = 0xFEE;
int curr_pos = pos;
byte[] b_size_out = BitConverter.GetBytes(size_out.ToBigEndian32());
byte[] temp = new byte[24];
int buffer_size = 0x1000;
LogTrace trace = LogTrace.INSTANCE;
try
{
Source.file.Position = 0;
Destination.file.Position = 0;
Destination.file.Write(b_size_out, 0, 4);
Destination.file.Write(b_size_out, 0, 4);
byte[] bytes = InitBuffer(pos + size_out);
Source.file.Read(bytes, pos, size_out);
while (curr_pos < size_out + pos)
{
byte header = 0;
for (int i = 0; i < 8; i++)
{
if (curr_pos >= size_out + pos)
break;
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format("{0} - Pos : {1:X}", i, curr_pos - pos));
param_lz = FindLZInBuffer(bytes, curr_pos, buffer_size, 0x12);
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" nb LZ : {0:000}", param_lz.length));
if (param_lz.length < 3)
{
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" -> Byte : {0:X02}", bytes[curr_pos]));
temp[pos_temp++] = bytes[curr_pos];
header |= (byte)(1 << i);
curr_pos++;
}
else
{
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" POS_LZ : {0:X04}", param_lz.offset));
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format(" -> LZ : {0:X02}{1:X02}", (byte)(((param_lz.offset & 0xF00) >> 4) | ((param_lz.length - 3) & 0x0F)), (byte)(param_lz.offset & 0xFF)));
temp[pos_temp++] = (byte)(param_lz.offset & 0xFF);
temp[pos_temp++] = (byte)(((param_lz.offset & 0xF00) >> 4) | ((param_lz.length - 3) & 0x0F));
curr_pos += param_lz.length;
}
}
Trace.WriteLineIf(trace.TSwitch.TraceVerbose, string.Format("Header : {0:X02}\n", header));
Destination.file.WriteByte(header);
Destination.file.Write(temp, 0, pos_temp);
pos_temp = 0;
}
byte[] b_size_in = BitConverter.GetBytes((int)((Destination.file.Length - 8).ToBigEndian32()));
Destination.file.Position = 0;
Destination.file.Write(b_size_in, 0, 4);
return true;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("(EE) {0}", ex.Message));
return false;
}
}
#endregion
}
}

Loading…
Cancel
Save