From 2dd378b17c7395bc479b89d4c2eedcb795259875 Mon Sep 17 00:00:00 2001 From: zhicheng233 Date: Sun, 17 May 2026 01:52:51 +0800 Subject: [PATCH] Initial commit --- .../.idea.PN532-Aime-Reader/.idea/.gitignore | 15 + .../.idea/encodings.xml | 4 + .../.idea/indexLayout.xml | 8 + .idea/.idea.PN532-Aime-Reader/.idea/misc.xml | 12 + .idea/.idea.PN532-Aime-Reader/.idea/vcs.xml | 6 + PN532-Aime-Reader.sln | 34 ++ PN532-Aime-Reader.sln.DotSettings.user | 8 + src/NfcAime.Cli/AccessCodeFormatter.cs | 18 + src/NfcAime.Cli/FeliCaDecryptor.cs | 398 +++++++++++++++++ src/NfcAime.Cli/FelicaCommandBuilder.cs | 36 ++ src/NfcAime.Cli/FelicaResponseParser.cs | 17 + src/NfcAime.Cli/Nfcaime.Cli.csproj | 14 + src/NfcAime.Cli/Pn532/IPn532FrameTransport.cs | 9 + src/NfcAime.Cli/Pn532/Pn532FrameKind.cs | 10 + .../Pn532/Pn532FrameParseResult.cs | 10 + src/NfcAime.Cli/Pn532/Pn532FrameParser.cs | 77 ++++ src/NfcAime.Cli/Pn532/Pn532HsuFrame.cs | 61 +++ src/NfcAime.Cli/Pn532/Pn532Session.cs | 96 +++++ src/NfcAime.Cli/Pn532/ReplayFrameTransport.cs | 50 +++ src/NfcAime.Cli/Pn532/SerialFrameTransport.cs | 148 +++++++ src/NfcAime.Cli/Program.cs | 299 +++++++++++++ src/NfcAime.Dll/AccessCodeFormatter.cs | 47 ++ src/NfcAime.Dll/AimeReader.cs | 185 ++++++++ src/NfcAime.Dll/Config.cs | 38 ++ src/NfcAime.Dll/FeliCaDecryptor.cs | 400 ++++++++++++++++++ src/NfcAime.Dll/FelicaCommandBuilder.cs | 38 ++ src/NfcAime.Dll/FelicaResponseParser.cs | 19 + src/NfcAime.Dll/FodyWeavers.xml | 4 + src/NfcAime.Dll/FodyWeavers.xsd | 186 ++++++++ src/NfcAime.Dll/MainDll.cs | 97 +++++ src/NfcAime.Dll/MiFareHandle.cs | 68 +++ src/NfcAime.Dll/NfcAime.Dll.csproj | 160 +++++++ src/NfcAime.Dll/PN532/CompilerSupport.cs | 23 + src/NfcAime.Dll/PN532/IPn532FrameTransport.cs | 13 + src/NfcAime.Dll/PN532/Pn532FrameKind.cs | 10 + .../PN532/Pn532FrameParseResult.cs | 14 + src/NfcAime.Dll/PN532/Pn532FrameParser.cs | 85 ++++ src/NfcAime.Dll/PN532/Pn532HsuFrame.cs | 63 +++ src/NfcAime.Dll/PN532/Pn532Session.cs | 98 +++++ src/NfcAime.Dll/PN532/ReplayFrameTransport.cs | 60 +++ src/NfcAime.Dll/PN532/SerialFrameTransport.cs | 162 +++++++ src/NfcAime.Dll/Properties/AssemblyInfo.cs | 35 ++ src/NfcAime.Dll/packages.config | 11 + 43 files changed, 3146 insertions(+) create mode 100644 .idea/.idea.PN532-Aime-Reader/.idea/.gitignore create mode 100644 .idea/.idea.PN532-Aime-Reader/.idea/encodings.xml create mode 100644 .idea/.idea.PN532-Aime-Reader/.idea/indexLayout.xml create mode 100644 .idea/.idea.PN532-Aime-Reader/.idea/misc.xml create mode 100644 .idea/.idea.PN532-Aime-Reader/.idea/vcs.xml create mode 100644 PN532-Aime-Reader.sln create mode 100644 PN532-Aime-Reader.sln.DotSettings.user create mode 100644 src/NfcAime.Cli/AccessCodeFormatter.cs create mode 100644 src/NfcAime.Cli/FeliCaDecryptor.cs create mode 100644 src/NfcAime.Cli/FelicaCommandBuilder.cs create mode 100644 src/NfcAime.Cli/FelicaResponseParser.cs create mode 100644 src/NfcAime.Cli/Nfcaime.Cli.csproj create mode 100644 src/NfcAime.Cli/Pn532/IPn532FrameTransport.cs create mode 100644 src/NfcAime.Cli/Pn532/Pn532FrameKind.cs create mode 100644 src/NfcAime.Cli/Pn532/Pn532FrameParseResult.cs create mode 100644 src/NfcAime.Cli/Pn532/Pn532FrameParser.cs create mode 100644 src/NfcAime.Cli/Pn532/Pn532HsuFrame.cs create mode 100644 src/NfcAime.Cli/Pn532/Pn532Session.cs create mode 100644 src/NfcAime.Cli/Pn532/ReplayFrameTransport.cs create mode 100644 src/NfcAime.Cli/Pn532/SerialFrameTransport.cs create mode 100644 src/NfcAime.Cli/Program.cs create mode 100644 src/NfcAime.Dll/AccessCodeFormatter.cs create mode 100644 src/NfcAime.Dll/AimeReader.cs create mode 100644 src/NfcAime.Dll/Config.cs create mode 100644 src/NfcAime.Dll/FeliCaDecryptor.cs create mode 100644 src/NfcAime.Dll/FelicaCommandBuilder.cs create mode 100644 src/NfcAime.Dll/FelicaResponseParser.cs create mode 100644 src/NfcAime.Dll/FodyWeavers.xml create mode 100644 src/NfcAime.Dll/FodyWeavers.xsd create mode 100644 src/NfcAime.Dll/MainDll.cs create mode 100644 src/NfcAime.Dll/MiFareHandle.cs create mode 100644 src/NfcAime.Dll/NfcAime.Dll.csproj create mode 100644 src/NfcAime.Dll/PN532/CompilerSupport.cs create mode 100644 src/NfcAime.Dll/PN532/IPn532FrameTransport.cs create mode 100644 src/NfcAime.Dll/PN532/Pn532FrameKind.cs create mode 100644 src/NfcAime.Dll/PN532/Pn532FrameParseResult.cs create mode 100644 src/NfcAime.Dll/PN532/Pn532FrameParser.cs create mode 100644 src/NfcAime.Dll/PN532/Pn532HsuFrame.cs create mode 100644 src/NfcAime.Dll/PN532/Pn532Session.cs create mode 100644 src/NfcAime.Dll/PN532/ReplayFrameTransport.cs create mode 100644 src/NfcAime.Dll/PN532/SerialFrameTransport.cs create mode 100644 src/NfcAime.Dll/Properties/AssemblyInfo.cs create mode 100644 src/NfcAime.Dll/packages.config diff --git a/.idea/.idea.PN532-Aime-Reader/.idea/.gitignore b/.idea/.idea.PN532-Aime-Reader/.idea/.gitignore new file mode 100644 index 0000000..3770828 --- /dev/null +++ b/.idea/.idea.PN532-Aime-Reader/.idea/.gitignore @@ -0,0 +1,15 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# Rider 忽略的文件 +/modules.xml +/.idea.PN532-Aime-Reader.iml +/projectSettingsUpdater.xml +/contentModel.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.PN532-Aime-Reader/.idea/encodings.xml b/.idea/.idea.PN532-Aime-Reader/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.PN532-Aime-Reader/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.PN532-Aime-Reader/.idea/indexLayout.xml b/.idea/.idea.PN532-Aime-Reader/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.PN532-Aime-Reader/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.PN532-Aime-Reader/.idea/misc.xml b/.idea/.idea.PN532-Aime-Reader/.idea/misc.xml new file mode 100644 index 0000000..40c082b --- /dev/null +++ b/.idea/.idea.PN532-Aime-Reader/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.PN532-Aime-Reader/.idea/vcs.xml b/.idea/.idea.PN532-Aime-Reader/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.PN532-Aime-Reader/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PN532-Aime-Reader.sln b/PN532-Aime-Reader.sln new file mode 100644 index 0000000..336c8bb --- /dev/null +++ b/PN532-Aime-Reader.sln @@ -0,0 +1,34 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NfcAime.Cli", "src\NfcAime.Cli\Nfcaime.Cli.csproj", "{BAD35401-BA7C-633C-3088-B4D88E8C1516}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NfcAime.Dll", "src\NfcAime.Dll\NfcAime.Dll.csproj", "{2ED77BFD-0EF1-49C4-A199-75D89EC3941E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Release|Any CPU.Build.0 = Release|Any CPU + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {BAD35401-BA7C-633C-3088-B4D88E8C1516}.Release|Any CPU.Deploy.0 = Release|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Release|Any CPU.Build.0 = Release|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2F6D6CBD-6360-4FBA-84B9-36604BFE2F8B} + EndGlobalSection +EndGlobal diff --git a/PN532-Aime-Reader.sln.DotSettings.user b/PN532-Aime-Reader.sln.DotSettings.user new file mode 100644 index 0000000..8f7571e --- /dev/null +++ b/PN532-Aime-Reader.sln.DotSettings.user @@ -0,0 +1,8 @@ + + ForceIncluded + ForceIncluded + ForceIncluded + C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe + 1179648 + + \ No newline at end of file diff --git a/src/NfcAime.Cli/AccessCodeFormatter.cs b/src/NfcAime.Cli/AccessCodeFormatter.cs new file mode 100644 index 0000000..957d99a --- /dev/null +++ b/src/NfcAime.Cli/AccessCodeFormatter.cs @@ -0,0 +1,18 @@ +namespace Nfcaime.Cli; + +internal static class AccessCodeFormatter +{ + private const int DecryptedByteCount = 16; + private const int AccessCodeHexLength = 20; + + internal static string ToAccessCodeString(ReadOnlySpan decryptedBytes) + { + if (decryptedBytes.Length != DecryptedByteCount) + { + throw new ArgumentException($"Expected {DecryptedByteCount} decrypted bytes.", nameof(decryptedBytes)); + } + + var hex = Convert.ToHexString(decryptedBytes); + return hex[^AccessCodeHexLength..]; + } +} diff --git a/src/NfcAime.Cli/FeliCaDecryptor.cs b/src/NfcAime.Cli/FeliCaDecryptor.cs new file mode 100644 index 0000000..8bf0da2 --- /dev/null +++ b/src/NfcAime.Cli/FeliCaDecryptor.cs @@ -0,0 +1,398 @@ +namespace Nfcaime.Cli; + +internal sealed class FeliCaDecryptor +{ + private const int IterAdd = 5; + private static readonly byte[][] SBox = new byte[][] + { + new byte[] + { + 0xaf, 0xa8, 0x63, 0xa9, 0x24, 0xc0, 0xf1, 0xbe, 0xe7, 0xb3, 0xac, 0x16, 0x85, 0xb9, 0x11, 0x6f, + 0x10, 0x31, 0x32, 0xae, 0xb1, 0x78, 0x23, 0xd2, 0xa2, 0xca, 0xf9, 0x2b, 0x45, 0xb8, 0x3c, 0x62, + 0x1a, 0x6d, 0x82, 0x8c, 0x00, 0xd9, 0xe2, 0x7a, 0x46, 0x19, 0x2a, 0xcc, 0x13, 0xa7, 0xe4, 0xb7, + 0x84, 0x9b, 0x8e, 0xff, 0xfb, 0x8b, 0x03, 0x89, 0x30, 0x29, 0xd3, 0x87, 0x01, 0xbf, 0x35, 0xda, + 0x59, 0x5c, 0xcd, 0x09, 0x7f, 0x70, 0x41, 0x81, 0x6c, 0x5f, 0x53, 0xc2, 0x72, 0xd4, 0x79, 0x48, + 0x95, 0xb4, 0x5b, 0x39, 0x44, 0x9d, 0xfd, 0x51, 0x71, 0xf6, 0xd7, 0xd5, 0x2d, 0x9f, 0xed, 0x33, + 0xea, 0xaa, 0x5a, 0x17, 0x6a, 0xba, 0xbd, 0xe9, 0x9c, 0xce, 0x4d, 0x7c, 0x18, 0x3b, 0x0d, 0xee, + 0x0c, 0x80, 0xa1, 0x0a, 0x8f, 0xb5, 0x6b, 0xf7, 0x3f, 0xd1, 0x47, 0x25, 0xc6, 0x36, 0xe8, 0x7d, + 0x7b, 0x3e, 0xf3, 0x99, 0x75, 0x05, 0x1c, 0xe0, 0xcf, 0x34, 0xbb, 0x9e, 0xeb, 0x91, 0xad, 0x26, + 0x8d, 0xc7, 0x66, 0x08, 0xe6, 0x58, 0x40, 0x14, 0xa6, 0xc5, 0xfa, 0x12, 0xec, 0xc4, 0xf0, 0x21, + 0x8a, 0x86, 0xd6, 0xe1, 0x06, 0xb6, 0x60, 0x74, 0x2e, 0xe3, 0xfe, 0x27, 0x2f, 0x5e, 0xf4, 0xb2, + 0x68, 0x28, 0x56, 0xef, 0x9a, 0x61, 0x22, 0xa3, 0xc9, 0x0b, 0x02, 0x96, 0x4f, 0xdb, 0xe5, 0x90, + 0x4a, 0x4c, 0x92, 0xcb, 0x98, 0x69, 0xd0, 0x57, 0x64, 0x0e, 0x37, 0x50, 0xab, 0xbc, 0xde, 0x76, + 0x07, 0xc8, 0x3d, 0x49, 0xfc, 0x77, 0x94, 0xf8, 0xc1, 0x97, 0x65, 0x3a, 0x4b, 0x83, 0x6e, 0x73, + 0x88, 0x1e, 0x5d, 0x04, 0x54, 0x67, 0xdf, 0x15, 0xb0, 0xc3, 0x93, 0x2c, 0x38, 0xdd, 0xf5, 0xa4, + 0x55, 0x0f, 0x4e, 0x1d, 0xa0, 0x1f, 0x20, 0xa5, 0xdc, 0x43, 0xf2, 0xd8, 0x7e, 0x52, 0x1b, 0x42 + }, + new byte[] + { + 0x03, 0x4b, 0xb8, 0x97, 0xb7, 0x8f, 0x0d, 0x71, 0xdc, 0x49, 0xa7, 0x90, 0xff, 0xa0, 0x9d, 0xf8, + 0x23, 0x5f, 0x20, 0x6c, 0x11, 0xf9, 0x69, 0x8e, 0xef, 0xcb, 0xf0, 0x14, 0xdf, 0x1b, 0x7a, 0xc6, + 0x36, 0x5b, 0xc5, 0x93, 0xb6, 0x28, 0x35, 0xb2, 0x3b, 0xd1, 0xec, 0xf3, 0x9f, 0x80, 0x64, 0x25, + 0x05, 0xcf, 0x54, 0xaf, 0x46, 0x3f, 0x96, 0x06, 0xe5, 0x10, 0xfb, 0xaa, 0xde, 0x62, 0x41, 0x51, + 0x59, 0xe7, 0x29, 0xb5, 0x4d, 0x3d, 0xf2, 0x37, 0x6a, 0xe8, 0x16, 0x8b, 0x5c, 0xa8, 0x1e, 0x57, + 0xe2, 0x09, 0xeb, 0x32, 0xc9, 0x2e, 0xb4, 0xf1, 0xa4, 0x76, 0xd3, 0xc1, 0xa5, 0x70, 0x60, 0x12, + 0xb1, 0xea, 0x66, 0xe1, 0x4a, 0x92, 0xa1, 0x6d, 0x3a, 0xf4, 0xd5, 0x6f, 0xda, 0xd8, 0x0a, 0x58, + 0x18, 0x63, 0x2b, 0xa3, 0x5d, 0x67, 0x77, 0x6b, 0x39, 0xc3, 0xdb, 0x4c, 0x02, 0xba, 0xce, 0xbb, + 0x55, 0x01, 0x17, 0x85, 0x27, 0xe4, 0x30, 0x72, 0xc8, 0x8d, 0xfc, 0x78, 0x19, 0xb9, 0x43, 0x2d, + 0x08, 0x9b, 0x1a, 0xd9, 0xcd, 0x26, 0xfa, 0x82, 0xcc, 0x7f, 0x75, 0x79, 0x9e, 0x65, 0xfe, 0xed, + 0x91, 0xc7, 0x31, 0xd0, 0x52, 0x2a, 0x48, 0x84, 0x74, 0xdd, 0x7d, 0xe9, 0xae, 0x6e, 0x33, 0x47, + 0xd6, 0x50, 0x0c, 0x53, 0xa6, 0xbd, 0x22, 0xac, 0x8a, 0x04, 0x89, 0x42, 0x40, 0xb3, 0x45, 0x5a, + 0x44, 0xe6, 0xa9, 0x1d, 0xc4, 0x81, 0xe3, 0x15, 0x1f, 0x56, 0x87, 0xd4, 0x3e, 0x0f, 0x95, 0xd7, + 0xe0, 0x94, 0xab, 0x24, 0x68, 0x07, 0xa2, 0x98, 0x7b, 0xee, 0xf5, 0xb0, 0x88, 0xc0, 0x38, 0x9c, + 0x99, 0x5e, 0xad, 0xd2, 0x3c, 0x2c, 0x21, 0xca, 0x8c, 0x7e, 0x1c, 0x61, 0xc2, 0x34, 0x7c, 0x2f, + 0x0b, 0x83, 0x9a, 0x4f, 0xbe, 0x4e, 0xbc, 0x73, 0x13, 0x00, 0xf6, 0x0e, 0x86, 0xbf, 0xfd, 0xf7 + }, + new byte[] + { + 0x41, 0x07, 0x93, 0x8a, 0x40, 0xff, 0x0e, 0x2f, 0x54, 0xfc, 0xac, 0xd4, 0x1e, 0xb0, 0xc8, 0x89, + 0xdf, 0x00, 0x64, 0x4f, 0xf3, 0xcb, 0x2c, 0xc0, 0x8f, 0x6e, 0x17, 0xbf, 0x95, 0x99, 0x2a, 0x0d, + 0x51, 0xad, 0x4b, 0xca, 0xf7, 0x69, 0x55, 0x38, 0x4c, 0xc6, 0x68, 0xc3, 0xa3, 0x1f, 0x83, 0x3e, + 0x0b, 0x70, 0xc7, 0xb2, 0x4e, 0xcf, 0xfe, 0x4d, 0xfd, 0x8b, 0xb4, 0x96, 0xce, 0xf9, 0xf1, 0xd3, + 0x43, 0x60, 0xae, 0xd5, 0xb3, 0xdb, 0x4a, 0xb9, 0xe5, 0xea, 0x6a, 0x48, 0x5d, 0xa7, 0x87, 0xda, + 0xb8, 0x06, 0x2d, 0x94, 0xc1, 0x66, 0x24, 0x26, 0xb1, 0x9a, 0x25, 0x08, 0x5c, 0x59, 0xa6, 0xaf, + 0x27, 0x92, 0x88, 0xf2, 0xed, 0x1b, 0x1c, 0x11, 0xd2, 0x22, 0x16, 0x76, 0x56, 0x67, 0x14, 0x9f, + 0xee, 0x5f, 0xdc, 0x5b, 0xa4, 0x46, 0xdd, 0x10, 0x33, 0x29, 0x7f, 0x5a, 0xc9, 0x97, 0x84, 0x85, + 0x42, 0xbc, 0xab, 0x77, 0x3c, 0xa1, 0xc2, 0xaa, 0x37, 0x49, 0x61, 0x35, 0x9c, 0x86, 0x19, 0x57, + 0x5e, 0xe6, 0xe2, 0xa0, 0x04, 0x6d, 0xd1, 0xa8, 0x9d, 0xec, 0x02, 0xbd, 0xd8, 0x47, 0x62, 0x1a, + 0x05, 0x7d, 0x01, 0x3f, 0x18, 0x6f, 0x75, 0x44, 0xe4, 0x7a, 0xf6, 0xa9, 0xe8, 0x72, 0xf4, 0x6b, + 0xfb, 0x7e, 0xeb, 0x36, 0x52, 0xde, 0x79, 0x23, 0x8d, 0xe7, 0x71, 0x63, 0x98, 0x6c, 0xd9, 0xe9, + 0x3b, 0x13, 0x90, 0x03, 0x78, 0x74, 0x20, 0x81, 0xba, 0x34, 0x32, 0xfa, 0x7b, 0x31, 0xd6, 0x3d, + 0x30, 0x0a, 0xe3, 0x12, 0x8e, 0x0f, 0x1d, 0xbb, 0xa5, 0x73, 0x53, 0x82, 0x80, 0xb5, 0xd7, 0xe1, + 0x3a, 0x2b, 0xd0, 0xe0, 0x21, 0xbe, 0xcd, 0x15, 0x65, 0xb6, 0xef, 0xcc, 0x91, 0xf5, 0x0c, 0xc4, + 0x2e, 0x9e, 0x58, 0xf0, 0xc5, 0x9b, 0x39, 0x09, 0xa2, 0x7c, 0x45, 0xb7, 0x28, 0x8c, 0xf8, 0x50 + }, + new byte[] + { + 0x76, 0xcd, 0x16, 0x7f, 0xe9, 0x6b, 0x95, 0x3a, 0x4e, 0xbb, 0x6e, 0xcc, 0xd2, 0xb3, 0x69, 0xa9, + 0x72, 0xaf, 0xc7, 0x82, 0x38, 0x6f, 0xbc, 0x37, 0xa6, 0x28, 0x36, 0xbd, 0xc3, 0xcf, 0x85, 0x46, + 0x0a, 0x99, 0x63, 0x97, 0x83, 0x56, 0x88, 0xa1, 0x02, 0x64, 0xac, 0x79, 0x55, 0x65, 0xff, 0xc2, + 0xef, 0x7a, 0x59, 0x2b, 0x1c, 0xe0, 0x8c, 0x8f, 0x5a, 0xe4, 0xb4, 0x9f, 0xfc, 0xf7, 0x45, 0x47, + 0x4a, 0xea, 0xc0, 0x0d, 0xeb, 0xc8, 0x78, 0xc9, 0x67, 0x03, 0x05, 0x31, 0x14, 0xde, 0xd3, 0x73, + 0xfa, 0x1f, 0xb6, 0x50, 0x13, 0x04, 0xb7, 0x7b, 0x49, 0x1a, 0xf9, 0x17, 0x4d, 0x4f, 0x86, 0x20, + 0xc1, 0xfd, 0xa0, 0xa5, 0x48, 0xe1, 0x3c, 0x60, 0xf2, 0x0b, 0x30, 0x0f, 0x4c, 0xee, 0x8d, 0x6d, + 0xbe, 0x8e, 0xc6, 0xf6, 0xd8, 0x08, 0x94, 0x1b, 0x2a, 0xb5, 0x9e, 0xa2, 0x93, 0xf5, 0x22, 0xa8, + 0x34, 0x26, 0xa7, 0x7c, 0xec, 0x3f, 0x0e, 0x3b, 0xb2, 0xf4, 0x29, 0xce, 0x2e, 0x42, 0x81, 0x32, + 0x25, 0x5f, 0x66, 0xdb, 0x7d, 0xd7, 0xe7, 0x2d, 0x43, 0x6a, 0x5b, 0x19, 0xad, 0x3e, 0x9b, 0xc5, + 0xab, 0x84, 0xc4, 0x6c, 0x33, 0xda, 0xfb, 0x8a, 0x11, 0x54, 0xfe, 0x57, 0x4b, 0x3d, 0x7e, 0x09, + 0xa3, 0x91, 0x40, 0xbf, 0x68, 0xe5, 0xb9, 0xf3, 0x96, 0xf8, 0x51, 0xca, 0x9a, 0xb0, 0x98, 0xae, + 0x1e, 0x9c, 0x5e, 0xe8, 0x00, 0x39, 0x12, 0xdc, 0x0c, 0x10, 0x8b, 0x5d, 0xd6, 0x2c, 0x27, 0xd5, + 0x61, 0x87, 0x53, 0xba, 0x23, 0x71, 0x2f, 0x1d, 0x74, 0x89, 0x9d, 0xdd, 0x35, 0x15, 0xd0, 0x44, + 0xb1, 0xcb, 0xf0, 0x62, 0x58, 0xa4, 0x77, 0xe3, 0x18, 0xd4, 0xe6, 0x80, 0xd1, 0xaa, 0x90, 0x70, + 0x01, 0x24, 0xf1, 0x21, 0xd9, 0x06, 0x75, 0xb8, 0x41, 0x5c, 0x92, 0xdf, 0xed, 0x07, 0xe2, 0x52 + }, + new byte[] + { + 0xaa, 0x94, 0x99, 0x74, 0x92, 0xd6, 0x1c, 0x46, 0x47, 0x79, 0x2f, 0x56, 0x7a, 0xf7, 0xb5, 0x0a, + 0xc9, 0x06, 0xd7, 0xfa, 0x40, 0x11, 0xa5, 0xff, 0x1f, 0xe8, 0x00, 0x8c, 0xdd, 0xb7, 0x42, 0x6e, + 0x35, 0xf0, 0xbe, 0xc2, 0x1d, 0xca, 0xb6, 0x8e, 0xe2, 0x44, 0x4e, 0x9f, 0x51, 0x81, 0x8b, 0x4c, + 0x5d, 0x59, 0x38, 0xd3, 0x8f, 0x25, 0xb3, 0x09, 0x31, 0xad, 0x0b, 0x6b, 0x0e, 0x50, 0xbb, 0xd8, + 0x96, 0x88, 0xde, 0x49, 0xea, 0xb4, 0x4a, 0x9a, 0x30, 0x6d, 0x2c, 0x0f, 0xcd, 0xf6, 0x7e, 0x4f, + 0x52, 0xd9, 0xfb, 0x28, 0x07, 0x63, 0x19, 0xa4, 0xee, 0x01, 0xf8, 0xeb, 0xd2, 0xf5, 0x3d, 0x0d, + 0x91, 0x5b, 0x67, 0xb8, 0x57, 0xf3, 0x2b, 0xe6, 0x68, 0x16, 0x7b, 0x6f, 0x65, 0xc3, 0x76, 0xe3, + 0x3c, 0x2e, 0x41, 0x3e, 0xcf, 0x54, 0xe1, 0x17, 0xa7, 0x45, 0x2d, 0x5a, 0xfc, 0xc6, 0x82, 0xef, + 0xcb, 0x78, 0xac, 0x34, 0xba, 0x71, 0x27, 0x22, 0xf2, 0x12, 0x3f, 0x1a, 0x24, 0xa8, 0xc1, 0x9e, + 0xc7, 0x18, 0xe4, 0x5f, 0xfe, 0x6c, 0xaf, 0xc8, 0x37, 0xc5, 0x77, 0xbd, 0x02, 0x29, 0x7d, 0xfd, + 0x5e, 0x83, 0xe9, 0xbf, 0x2a, 0x5c, 0x73, 0xe5, 0xda, 0xe7, 0xec, 0x80, 0x6a, 0x03, 0x58, 0x3b, + 0x21, 0x13, 0xd1, 0x53, 0x70, 0x93, 0xbc, 0xa1, 0x10, 0xf4, 0x64, 0x85, 0x87, 0xb0, 0x61, 0xb1, + 0xf1, 0x43, 0x62, 0xc4, 0xf9, 0xa2, 0xae, 0xd4, 0x04, 0x86, 0xdf, 0x26, 0xd0, 0xab, 0x36, 0x1e, + 0x4b, 0xe0, 0x60, 0x90, 0xcc, 0x98, 0x95, 0x3a, 0x9c, 0x9b, 0x15, 0x1b, 0x33, 0x8d, 0x4d, 0x23, + 0x9d, 0x84, 0xce, 0xb2, 0x05, 0xa6, 0x0c, 0xb9, 0x14, 0x48, 0xd5, 0xa3, 0x75, 0x08, 0x7c, 0xed, + 0x7f, 0xdc, 0x89, 0x66, 0xa9, 0xc0, 0x39, 0x72, 0x20, 0x97, 0x8a, 0xa0, 0x55, 0x69, 0xdb, 0x32 + }, + new byte[] + { + 0xde, 0x5a, 0x1b, 0x69, 0x3c, 0x42, 0xa3, 0x51, 0x40, 0x38, 0xf1, 0x17, 0x28, 0x1c, 0xb8, 0xac, + 0xe8, 0x99, 0x45, 0xa0, 0x12, 0x68, 0xce, 0x15, 0xc9, 0x47, 0x6d, 0xc8, 0x46, 0x4e, 0x1e, 0xa6, + 0x11, 0xf8, 0x3e, 0x31, 0xfa, 0xe6, 0x2d, 0xfd, 0x32, 0x14, 0x9c, 0x01, 0xb1, 0xed, 0x57, 0x71, + 0xe3, 0x30, 0xcf, 0x0e, 0xe7, 0xcc, 0x37, 0x4a, 0x78, 0xab, 0x1d, 0x96, 0xea, 0x25, 0x85, 0x20, + 0x49, 0x4d, 0x75, 0x52, 0xee, 0xef, 0xaf, 0x54, 0x33, 0xf9, 0x09, 0x9e, 0x81, 0xe1, 0x73, 0x2a, + 0xf2, 0x3d, 0x22, 0x13, 0x92, 0x8b, 0xf0, 0xc0, 0x84, 0x0d, 0xa8, 0x04, 0xdc, 0x8d, 0xbb, 0x2c, + 0x94, 0x03, 0xf5, 0xd1, 0xd0, 0x2e, 0x5d, 0xeb, 0x5e, 0x62, 0x6f, 0x21, 0xb0, 0x91, 0xfe, 0xcb, + 0x0a, 0xec, 0xb7, 0x2f, 0x34, 0x24, 0xd6, 0xe9, 0x66, 0x82, 0xc6, 0x19, 0x1f, 0xd9, 0xc1, 0xa7, + 0xf6, 0x88, 0x02, 0x87, 0x07, 0xdd, 0xd8, 0x67, 0xc5, 0xb9, 0x29, 0xa4, 0xc2, 0x65, 0xd3, 0x53, + 0xaa, 0x3b, 0x79, 0xe0, 0x72, 0xd7, 0x06, 0x1a, 0x58, 0x93, 0x7e, 0x0c, 0x90, 0x6e, 0x74, 0x0f, + 0x4c, 0x0b, 0xe4, 0x6c, 0x43, 0xd2, 0x41, 0x36, 0xbd, 0xdb, 0x35, 0x59, 0x5f, 0xa9, 0x9f, 0x55, + 0x80, 0x39, 0x8e, 0xf3, 0xd5, 0xe5, 0x63, 0xc3, 0x77, 0xa2, 0x5b, 0x7b, 0x7f, 0xae, 0xba, 0xa1, + 0x10, 0xdf, 0x4b, 0x2b, 0x9b, 0x16, 0xbc, 0x00, 0xc7, 0x18, 0xfb, 0x64, 0xff, 0xf4, 0xf7, 0xbe, + 0x05, 0x56, 0xcd, 0x08, 0x4f, 0x6a, 0x9a, 0x76, 0x7a, 0xb5, 0x95, 0xca, 0x50, 0x7d, 0x7c, 0x6b, + 0xa5, 0xc4, 0x3a, 0x8a, 0x27, 0xda, 0xb6, 0x61, 0x23, 0x86, 0x70, 0x48, 0x97, 0x9d, 0xfc, 0x8f, + 0x5c, 0x44, 0xb2, 0xad, 0x83, 0xbf, 0x26, 0xd4, 0xb3, 0x60, 0x98, 0xe2, 0x3f, 0x8c, 0xb4, 0x89 + }, + new byte[] + { + 0x19, 0x2e, 0x5d, 0x7a, 0x5b, 0x7c, 0x7e, 0x39, 0xef, 0x8b, 0x2f, 0xcc, 0x86, 0x1b, 0xc2, 0x8c, + 0x20, 0xfb, 0x59, 0x5f, 0x21, 0xcd, 0x9f, 0x94, 0xa7, 0x0b, 0xba, 0x0e, 0x47, 0x7b, 0xa5, 0x06, + 0xb7, 0xd8, 0xc9, 0x26, 0xf0, 0x9d, 0x68, 0xf2, 0x5e, 0x12, 0x3a, 0x65, 0xb0, 0x5c, 0xe2, 0x22, + 0x99, 0x82, 0x10, 0xf5, 0x44, 0x50, 0x74, 0xa1, 0xf9, 0x42, 0xa8, 0x25, 0x4d, 0xea, 0x08, 0x3e, + 0x96, 0x92, 0xc4, 0x2d, 0xa2, 0x23, 0x34, 0xa9, 0x45, 0x6b, 0x79, 0x7f, 0xc8, 0x4c, 0x43, 0x67, + 0x29, 0x04, 0xfa, 0xb5, 0x1e, 0x05, 0xc6, 0xb4, 0xaf, 0x1d, 0xbb, 0xf7, 0x30, 0xed, 0xec, 0xab, + 0x40, 0xe0, 0x98, 0xe9, 0x4b, 0x11, 0x87, 0x15, 0x18, 0xbe, 0xee, 0x78, 0xb1, 0x02, 0x57, 0x90, + 0x32, 0x8e, 0x61, 0xcb, 0x3c, 0xe6, 0xd7, 0x75, 0x31, 0xb9, 0x54, 0x36, 0xe1, 0xdd, 0x6c, 0x97, + 0x62, 0xac, 0x41, 0x89, 0x9c, 0x60, 0xf6, 0x63, 0xbc, 0x14, 0x7d, 0x77, 0xd5, 0x09, 0x95, 0xdf, + 0x17, 0x4a, 0x0c, 0x2b, 0x52, 0x38, 0xfe, 0xb6, 0x33, 0xa4, 0x51, 0x73, 0x6d, 0x4f, 0xb3, 0xbf, + 0x71, 0xeb, 0x69, 0x3f, 0x13, 0x00, 0x1c, 0x6f, 0x9b, 0x35, 0xca, 0xa3, 0x2a, 0x76, 0x8d, 0x80, + 0x01, 0x16, 0x03, 0x3d, 0xe5, 0x83, 0x70, 0x8f, 0x5a, 0x58, 0x88, 0xf3, 0xe3, 0x0f, 0xc3, 0x07, + 0x46, 0xff, 0x49, 0xfd, 0xe8, 0xce, 0x85, 0xd1, 0xdc, 0xf1, 0xb8, 0xd3, 0x6e, 0xd4, 0xa0, 0xad, + 0x72, 0xda, 0xe7, 0x3b, 0x84, 0xf8, 0xd2, 0x28, 0xde, 0xc1, 0x1a, 0x64, 0x91, 0x0a, 0xe4, 0xc5, + 0x24, 0x6a, 0xd6, 0x53, 0xc7, 0x81, 0x2c, 0xc0, 0x93, 0xd9, 0x0d, 0xa6, 0xdb, 0xae, 0xcf, 0xf4, + 0x55, 0x66, 0x1f, 0x37, 0xfc, 0xbd, 0x48, 0xaa, 0x4e, 0x56, 0x27, 0x9e, 0xd0, 0x8a, 0xb2, 0x9a + }, + new byte[] + { + 0x54, 0xd9, 0x42, 0x50, 0xb8, 0x74, 0xab, 0x32, 0xa6, 0x24, 0x48, 0x6f, 0xf0, 0x7e, 0xd7, 0x30, + 0xb4, 0x6d, 0x06, 0xa2, 0x88, 0xd6, 0x59, 0x39, 0x4c, 0x2c, 0xaf, 0x6e, 0xa8, 0x2b, 0x66, 0x5c, + 0xb9, 0x1c, 0xe7, 0x53, 0x19, 0x56, 0x8a, 0x11, 0xd8, 0x61, 0x5f, 0x07, 0x03, 0xda, 0xb5, 0xb0, + 0x08, 0xd0, 0x27, 0x45, 0x00, 0xc3, 0xf5, 0xb1, 0x79, 0x6b, 0xc4, 0x5b, 0x21, 0xe2, 0x55, 0x3d, + 0xa0, 0xfe, 0x31, 0x9a, 0x36, 0x0f, 0xec, 0xcb, 0xbb, 0x72, 0x29, 0x1a, 0xf9, 0x76, 0x87, 0x6a, + 0xae, 0xc9, 0x52, 0xb7, 0xeb, 0x7b, 0xf3, 0x05, 0x94, 0x04, 0x01, 0xc6, 0xa1, 0xf4, 0x37, 0xe3, + 0xfb, 0xc1, 0x4f, 0xba, 0x9e, 0x80, 0x28, 0x2f, 0x5a, 0xf6, 0x91, 0x3e, 0xcf, 0x41, 0xad, 0xbe, + 0xb6, 0x33, 0x9b, 0xfc, 0xce, 0xc0, 0x68, 0xee, 0x3f, 0x51, 0xbf, 0x4e, 0xd2, 0x49, 0xa9, 0xf7, + 0x8f, 0x86, 0xc8, 0x2a, 0x67, 0x38, 0xe4, 0x4d, 0xc7, 0x2e, 0x63, 0x7f, 0xef, 0xdf, 0x97, 0xaa, + 0x5d, 0x62, 0x0b, 0x70, 0xd1, 0xe6, 0x13, 0x99, 0x92, 0xf8, 0xd3, 0x12, 0x3c, 0x64, 0x84, 0xa7, + 0x0a, 0xb2, 0x9c, 0xea, 0x1b, 0xcc, 0x5e, 0x43, 0x1e, 0xcd, 0x8d, 0x93, 0xe1, 0x71, 0x3b, 0xf2, + 0xa3, 0x23, 0xe9, 0xc2, 0xdd, 0x98, 0x8b, 0x73, 0x96, 0x57, 0x22, 0x35, 0x0e, 0xa5, 0xac, 0x0c, + 0xd4, 0x9f, 0x09, 0xdb, 0x3a, 0x16, 0x60, 0xe5, 0x89, 0x85, 0x2d, 0x77, 0x1d, 0x81, 0x20, 0xc5, + 0x7a, 0xed, 0xca, 0x14, 0x83, 0xfa, 0x1f, 0x26, 0x18, 0x47, 0x9d, 0x02, 0xe0, 0x58, 0x10, 0xfd, + 0x25, 0x75, 0xbc, 0x46, 0xde, 0xa4, 0x15, 0x40, 0x78, 0x34, 0x4a, 0x17, 0xd5, 0x0d, 0x69, 0x65, + 0xdc, 0x4b, 0x7c, 0x95, 0xe8, 0x8e, 0xff, 0x7d, 0x90, 0x8c, 0x82, 0x6c, 0xb3, 0xbd, 0x44, 0xf1 + }, + new byte[] + { + 0xc3, 0x0b, 0xb8, 0x15, 0x00, 0xa4, 0x3d, 0xd9, 0xfd, 0x16, 0xe9, 0x08, 0x20, 0xe6, 0x8d, 0xc4, + 0x47, 0x38, 0x25, 0x93, 0x5e, 0xaa, 0xec, 0x2a, 0x10, 0x6e, 0x58, 0x95, 0x26, 0x53, 0x04, 0x1e, + 0x92, 0x9e, 0x2f, 0x8b, 0xbd, 0x0a, 0x69, 0xa9, 0x3b, 0x41, 0xf0, 0xd0, 0xda, 0x6a, 0xa1, 0x71, + 0x13, 0x63, 0xef, 0x90, 0x94, 0xb0, 0xeb, 0xe7, 0xdc, 0x73, 0xb9, 0x78, 0x7a, 0xe4, 0xc7, 0x6d, + 0x7f, 0xa3, 0xac, 0x85, 0x50, 0xf3, 0x91, 0xea, 0x36, 0xfa, 0x9b, 0x8e, 0xf7, 0xae, 0xc1, 0x43, + 0x1c, 0xc0, 0xca, 0x8f, 0x4e, 0xa2, 0x31, 0x12, 0x74, 0x2c, 0x9d, 0xf1, 0xad, 0x3f, 0x68, 0xbb, + 0x35, 0x9a, 0x7c, 0x03, 0xfb, 0x09, 0x23, 0x27, 0xd4, 0x4d, 0x17, 0xde, 0x4c, 0xee, 0x76, 0x9c, + 0xa0, 0xe1, 0x75, 0xdb, 0x5b, 0x11, 0xa7, 0x21, 0xaf, 0x02, 0x42, 0x4b, 0x83, 0xd3, 0xce, 0x9f, + 0xcd, 0xcc, 0x30, 0x2b, 0x52, 0x22, 0xcb, 0x59, 0x7d, 0x14, 0xf9, 0xb1, 0x70, 0x54, 0xe0, 0x81, + 0x5a, 0x1d, 0x0e, 0x4a, 0xf4, 0x84, 0xed, 0x96, 0x1a, 0xd7, 0xc9, 0x82, 0x89, 0x2d, 0x87, 0x33, + 0xe2, 0x5c, 0x51, 0x3a, 0x1b, 0xf6, 0xe5, 0xd8, 0x32, 0x0d, 0x5f, 0x72, 0x88, 0xd6, 0xb7, 0xc6, + 0xdd, 0x1f, 0x80, 0x37, 0x64, 0xb5, 0x7b, 0xdf, 0xff, 0xd2, 0x40, 0xfc, 0x60, 0x05, 0xb6, 0x66, + 0x61, 0xc8, 0x28, 0x99, 0xbc, 0xa5, 0x34, 0x19, 0xf8, 0xfe, 0x24, 0x48, 0xd1, 0x0c, 0x55, 0x62, + 0x6b, 0x86, 0xb3, 0xba, 0x8a, 0x2e, 0x6f, 0xa6, 0x67, 0x65, 0x01, 0xe8, 0x18, 0x3e, 0x7e, 0x77, + 0x29, 0x8c, 0xf2, 0x07, 0x6c, 0xa8, 0x79, 0x4f, 0x44, 0x46, 0x06, 0xc2, 0xab, 0x0f, 0xb4, 0xb2, + 0xbe, 0x98, 0xd5, 0x49, 0xe3, 0xf5, 0xcf, 0x97, 0x5d, 0xbf, 0x57, 0x3c, 0x39, 0xc5, 0x45, 0x56 + } + }; + + private static readonly byte[][] SBoxInv = + { + new byte[] + { + 0x24, 0x3c, 0xba, 0x36, 0xe3, 0x85, 0xa4, 0xd0, 0x93, 0x43, 0x73, 0xb9, 0x70, 0x6e, 0xc9, 0xf1, + 0x10, 0x0e, 0x9b, 0x2c, 0x97, 0xe7, 0x0b, 0x63, 0x6c, 0x29, 0x20, 0xfe, 0x86, 0xf3, 0xe1, 0xf5, + 0xf6, 0x9f, 0xb6, 0x16, 0x04, 0x7b, 0x8f, 0xab, 0xb1, 0x39, 0x2a, 0x1b, 0xeb, 0x5c, 0xa8, 0xac, + 0x38, 0x11, 0x12, 0x5f, 0x89, 0x3e, 0x7d, 0xca, 0xec, 0x53, 0xdb, 0x6d, 0x1e, 0xd2, 0x81, 0x78, + 0x96, 0x46, 0xff, 0xf9, 0x54, 0x1c, 0x28, 0x7a, 0x4f, 0xd3, 0xc0, 0xdc, 0xc1, 0x6a, 0xf2, 0xbc, + 0xcb, 0x57, 0xfd, 0x4a, 0xe4, 0xf0, 0xb2, 0xc7, 0x95, 0x40, 0x62, 0x52, 0x41, 0xe2, 0xad, 0x49, + 0xa6, 0xb5, 0x1f, 0x02, 0xc8, 0xda, 0x92, 0xe5, 0xb0, 0xc5, 0x64, 0x76, 0x48, 0x21, 0xde, 0x0f, + 0x45, 0x58, 0x4c, 0xdf, 0xa7, 0x84, 0xcf, 0xd5, 0x15, 0x4e, 0x27, 0x80, 0x6b, 0x7f, 0xfc, 0x44, + 0x71, 0x47, 0x22, 0xdd, 0x30, 0x0c, 0xa1, 0x3b, 0xe0, 0x37, 0xa0, 0x35, 0x23, 0x90, 0x32, 0x74, + 0xbf, 0x8d, 0xc2, 0xea, 0xd6, 0x50, 0xbb, 0xd9, 0xc4, 0x83, 0xb4, 0x31, 0x68, 0x55, 0x8b, 0x5d, + 0xf4, 0x72, 0x18, 0xb7, 0xef, 0xf7, 0x98, 0x2d, 0x01, 0x03, 0x61, 0xcc, 0x0a, 0x8e, 0x13, 0x00, + 0xe8, 0x14, 0xaf, 0x09, 0x51, 0x75, 0xa5, 0x2f, 0x1d, 0x0d, 0x65, 0x8a, 0xcd, 0x66, 0x07, 0x3d, + 0x05, 0xd8, 0x4b, 0xe9, 0x9d, 0x99, 0x7c, 0x91, 0xd1, 0xb8, 0x19, 0xc3, 0x2b, 0x42, 0x69, 0x88, + 0xc6, 0x79, 0x17, 0x3a, 0x4d, 0x5b, 0xa2, 0x5a, 0xfb, 0x25, 0x3f, 0xbd, 0xf8, 0xed, 0xce, 0xe6, + 0x87, 0xa3, 0x26, 0xa9, 0x2e, 0xbe, 0x94, 0x08, 0x7e, 0x67, 0x60, 0x8c, 0x9c, 0x5e, 0x6f, 0xb3, + 0x9e, 0x06, 0xfa, 0x82, 0xae, 0xee, 0x59, 0x77, 0xd7, 0x1a, 0x9a, 0x34, 0xd4, 0x56, 0xaa, 0x33 + }, + new byte[] + { + 0xf9, 0x81, 0x7c, 0x00, 0xb9, 0x30, 0x37, 0xd5, 0x90, 0x51, 0x6e, 0xf0, 0xb2, 0x06, 0xfb, 0xcd, + 0x39, 0x14, 0x5f, 0xf8, 0x1b, 0xc7, 0x4a, 0x82, 0x70, 0x8c, 0x92, 0x1d, 0xea, 0xc3, 0x4e, 0xc8, + 0x12, 0xe6, 0xb6, 0x10, 0xd3, 0x2f, 0x95, 0x84, 0x25, 0x42, 0xa5, 0x72, 0xe5, 0x8f, 0x55, 0xef, + 0x86, 0xa2, 0x53, 0xae, 0xed, 0x26, 0x20, 0x47, 0xde, 0x78, 0x68, 0x28, 0xe4, 0x45, 0xcc, 0x35, + 0xbc, 0x3e, 0xbb, 0x8e, 0xc0, 0xbe, 0x34, 0xaf, 0xa6, 0x09, 0x64, 0x01, 0x7b, 0x44, 0xf5, 0xf3, + 0xb1, 0x3f, 0xa4, 0xb3, 0x32, 0x80, 0xc9, 0x4f, 0x6f, 0x40, 0xbf, 0x21, 0x4c, 0x74, 0xe1, 0x11, + 0x5e, 0xeb, 0x3d, 0x71, 0x2e, 0x9d, 0x62, 0x75, 0xd4, 0x16, 0x48, 0x77, 0x13, 0x67, 0xad, 0x6b, + 0x5d, 0x07, 0x87, 0xf7, 0xa8, 0x9a, 0x59, 0x76, 0x8b, 0x9b, 0x1e, 0xd8, 0xee, 0xaa, 0xe9, 0x99, + 0x2d, 0xc5, 0x97, 0xf1, 0xa7, 0x83, 0xfc, 0xca, 0xdc, 0xba, 0xb8, 0x4b, 0xe8, 0x89, 0x17, 0x05, + 0x0b, 0xa0, 0x65, 0x23, 0xd1, 0xce, 0x36, 0x03, 0xd7, 0xe0, 0xf2, 0x91, 0xdf, 0x0e, 0x9c, 0x2c, + 0x0d, 0x66, 0xd6, 0x73, 0x58, 0x5c, 0xb4, 0x0a, 0x4d, 0xc2, 0x3b, 0xd2, 0xb7, 0xe2, 0xac, 0x33, + 0xdb, 0x60, 0x27, 0xbd, 0x56, 0x43, 0x24, 0x04, 0x02, 0x8d, 0x7d, 0x7f, 0xf6, 0xb5, 0xf4, 0xfd, + 0xdd, 0x5b, 0xec, 0x79, 0xc4, 0x22, 0x1f, 0xa1, 0x88, 0x54, 0xe7, 0x19, 0x98, 0x94, 0x7e, 0x31, + 0xa3, 0x29, 0xe3, 0x5a, 0xcb, 0x6a, 0xb0, 0xcf, 0x6d, 0x93, 0x6c, 0x7a, 0x08, 0xa9, 0x3c, 0x1c, + 0xd0, 0x63, 0x50, 0xc6, 0x85, 0x38, 0xc1, 0x41, 0x49, 0xab, 0x61, 0x52, 0x2a, 0x9f, 0xd9, 0x18, + 0x1a, 0x57, 0x46, 0x2b, 0x69, 0xda, 0xfa, 0xff, 0x0f, 0x15, 0x96, 0x3a, 0x8a, 0xfe, 0x9e, 0x0c + }, + new byte[] + { + 0x11, 0xa2, 0x9a, 0xc3, 0x94, 0xa0, 0x51, 0x01, 0x5b, 0xf7, 0xd1, 0x30, 0xee, 0x1f, 0x06, 0xd5, + 0x77, 0x67, 0xd3, 0xc1, 0x6e, 0xe7, 0x6a, 0x1a, 0xa4, 0x8e, 0x9f, 0x65, 0x66, 0xd6, 0x0c, 0x2d, + 0xc6, 0xe4, 0x69, 0xb7, 0x56, 0x5a, 0x57, 0x60, 0xfc, 0x79, 0x1e, 0xe1, 0x16, 0x52, 0xf0, 0x07, + 0xd0, 0xcd, 0xca, 0x78, 0xc9, 0x8b, 0xb3, 0x88, 0x27, 0xf6, 0xe0, 0xc0, 0x84, 0xcf, 0x2f, 0xa3, + 0x04, 0x00, 0x80, 0x40, 0xa7, 0xfa, 0x75, 0x9d, 0x4b, 0x89, 0x46, 0x22, 0x28, 0x37, 0x34, 0x13, + 0xff, 0x20, 0xb4, 0xda, 0x08, 0x26, 0x6c, 0x8f, 0xf2, 0x5d, 0x7b, 0x73, 0x5c, 0x4c, 0x90, 0x71, + 0x41, 0x8a, 0x9e, 0xbb, 0x12, 0xe8, 0x55, 0x6d, 0x2a, 0x25, 0x4a, 0xaf, 0xbd, 0x95, 0x19, 0xa5, + 0x31, 0xba, 0xad, 0xd9, 0xc5, 0xa6, 0x6b, 0x83, 0xc4, 0xb6, 0xa9, 0xcc, 0xf9, 0xa1, 0xb1, 0x7a, + 0xdc, 0xc7, 0xdb, 0x2e, 0x7e, 0x7f, 0x8d, 0x4e, 0x62, 0x0f, 0x03, 0x39, 0xfd, 0xb8, 0xd4, 0x18, + 0xc2, 0xec, 0x61, 0x02, 0x53, 0x1c, 0x3b, 0x7d, 0xbc, 0x1d, 0x59, 0xf5, 0x8c, 0x98, 0xf1, 0x6f, + 0x93, 0x85, 0xf8, 0x2c, 0x74, 0xd8, 0x5e, 0x4d, 0x97, 0xab, 0x87, 0x82, 0x0a, 0x21, 0x42, 0x5f, + 0x0d, 0x58, 0x33, 0x44, 0x3a, 0xdd, 0xe9, 0xfb, 0x50, 0x47, 0xc8, 0xd7, 0x81, 0x9b, 0xe5, 0x1b, + 0x17, 0x54, 0x86, 0x2b, 0xef, 0xf4, 0x29, 0x32, 0x0e, 0x7c, 0x23, 0x15, 0xeb, 0xe6, 0x3c, 0x35, + 0xe2, 0x96, 0x68, 0x3f, 0x0b, 0x43, 0xce, 0xde, 0x9c, 0xbe, 0x4f, 0x45, 0x72, 0x76, 0xb5, 0x10, + 0xe3, 0xdf, 0x92, 0xd2, 0xa8, 0x48, 0x91, 0xb9, 0xac, 0xbf, 0x49, 0xb2, 0x99, 0x64, 0x70, 0xea, + 0xf3, 0x3e, 0x63, 0x14, 0xae, 0xed, 0xaa, 0x24, 0xfe, 0x3d, 0xcb, 0xb0, 0x09, 0x38, 0x36, 0x05 + }, + new byte[] + { + 0xc4, 0xf0, 0x28, 0x49, 0x55, 0x4a, 0xf5, 0xfd, 0x75, 0xaf, 0x20, 0x69, 0xc8, 0x43, 0x86, 0x6b, + 0xc9, 0xa8, 0xc6, 0x54, 0x4c, 0xdd, 0x02, 0x5b, 0xe8, 0x9b, 0x59, 0x77, 0x34, 0xd7, 0xc0, 0x51, + 0x5f, 0xf3, 0x7e, 0xd4, 0xf1, 0x90, 0x81, 0xce, 0x19, 0x8a, 0x78, 0x33, 0xcd, 0x97, 0x8c, 0xd6, + 0x6a, 0x4b, 0x8f, 0xa4, 0x80, 0xdc, 0x1a, 0x17, 0x14, 0xc5, 0x07, 0x87, 0x66, 0xad, 0x9d, 0x85, + 0xb2, 0xf8, 0x8d, 0x98, 0xdf, 0x3e, 0x1f, 0x3f, 0x64, 0x58, 0x40, 0xac, 0x6c, 0x5c, 0x08, 0x5d, + 0x53, 0xba, 0xff, 0xd2, 0xa9, 0x2c, 0x25, 0xab, 0xe4, 0x32, 0x38, 0x9a, 0xf9, 0xcb, 0xc2, 0x91, + 0x67, 0xd0, 0xe3, 0x22, 0x29, 0x2d, 0x92, 0x48, 0xb4, 0x0e, 0x99, 0x05, 0xa3, 0x6f, 0x0a, 0x15, + 0xef, 0xd5, 0x10, 0x4f, 0xd8, 0xf6, 0x00, 0xe6, 0x46, 0x2b, 0x31, 0x57, 0x83, 0x94, 0xae, 0x03, + 0xeb, 0x8e, 0x13, 0x24, 0xa1, 0x1e, 0x5e, 0xd1, 0x26, 0xd9, 0xa7, 0xca, 0x36, 0x6e, 0x71, 0x37, + 0xee, 0xb1, 0xfa, 0x7c, 0x76, 0x06, 0xb8, 0x23, 0xbe, 0x21, 0xbc, 0x9e, 0xc1, 0xda, 0x7a, 0x3b, + 0x62, 0x27, 0x7b, 0xb0, 0xe5, 0x63, 0x18, 0x82, 0x7f, 0x0f, 0xed, 0xa0, 0x2a, 0x9c, 0xbf, 0x11, + 0xbd, 0xe0, 0x88, 0x0d, 0x3a, 0x79, 0x52, 0x56, 0xf7, 0xb6, 0xd3, 0x09, 0x16, 0x1b, 0x70, 0xb3, + 0x42, 0x60, 0x2f, 0x1c, 0xa2, 0x9f, 0x72, 0x12, 0x45, 0x47, 0xbb, 0xe1, 0x0b, 0x01, 0x8b, 0x1d, + 0xde, 0xec, 0x0c, 0x4e, 0xe9, 0xcf, 0xcc, 0x95, 0x74, 0xf4, 0xa5, 0x93, 0xc7, 0xdb, 0x4d, 0xfb, + 0x35, 0x65, 0xfe, 0xe7, 0x39, 0xb5, 0xea, 0x96, 0xc3, 0x04, 0x41, 0x44, 0x84, 0xfc, 0x6d, 0x30, + 0xe2, 0xf2, 0x68, 0xb7, 0x89, 0x7d, 0x73, 0x3d, 0xb9, 0x5a, 0x50, 0xa6, 0x3c, 0x61, 0xaa, 0x2e + }, + new byte[] + { + 0x1a, 0x59, 0x9c, 0xad, 0xc8, 0xe4, 0x11, 0x54, 0xed, 0x37, 0x0f, 0x3a, 0xe6, 0x5f, 0x3c, 0x4b, + 0xb8, 0x15, 0x89, 0xb1, 0xe8, 0xda, 0x69, 0x77, 0x91, 0x56, 0x8b, 0xdb, 0x06, 0x24, 0xcf, 0x18, + 0xf8, 0xb0, 0x87, 0xdf, 0x8c, 0x35, 0xcb, 0x86, 0x53, 0x9d, 0xa4, 0x66, 0x4a, 0x7a, 0x71, 0x0a, + 0x48, 0x38, 0xff, 0xdc, 0x83, 0x20, 0xce, 0x98, 0x32, 0xf6, 0xd7, 0xaf, 0x70, 0x5e, 0x73, 0x8a, + 0x14, 0x72, 0x1e, 0xc1, 0x29, 0x79, 0x07, 0x08, 0xe9, 0x43, 0x46, 0xd0, 0x2f, 0xde, 0x2a, 0x4f, + 0x3d, 0x2c, 0x50, 0xb3, 0x75, 0xfc, 0x0b, 0x64, 0xae, 0x31, 0x7b, 0x61, 0xa5, 0x30, 0xa0, 0x93, + 0xd2, 0xbe, 0xc2, 0x55, 0xba, 0x6c, 0xf3, 0x62, 0x68, 0xfd, 0xac, 0x3b, 0x95, 0x49, 0x1f, 0x6b, + 0xb4, 0x85, 0xf7, 0xa6, 0x03, 0xec, 0x6e, 0x9a, 0x81, 0x09, 0x0c, 0x6a, 0xee, 0x9e, 0x4e, 0xf0, + 0xab, 0x2d, 0x7e, 0xa1, 0xe1, 0xbb, 0xc9, 0xbc, 0x41, 0xf2, 0xfa, 0x2e, 0x1b, 0xdd, 0x27, 0x34, + 0xd3, 0x60, 0x04, 0xb5, 0x01, 0xd6, 0x40, 0xf9, 0xd5, 0x02, 0x47, 0xd9, 0xd8, 0xe0, 0x8f, 0x2b, + 0xfb, 0xb7, 0xc5, 0xeb, 0x57, 0x16, 0xe5, 0x78, 0x8d, 0xf4, 0x00, 0xcd, 0x82, 0x39, 0xc6, 0x96, + 0xbd, 0xbf, 0xe3, 0x36, 0x45, 0x0e, 0x26, 0x1d, 0x63, 0xe7, 0x84, 0x3e, 0xb6, 0x9b, 0x22, 0xa3, + 0xf5, 0x8e, 0x23, 0x6d, 0xc3, 0x99, 0x7d, 0x90, 0x97, 0x10, 0x25, 0x80, 0xd4, 0x4c, 0xe2, 0x74, + 0xcc, 0xb2, 0x5c, 0x33, 0xc7, 0xea, 0x05, 0x12, 0x3f, 0x51, 0xa8, 0xfe, 0xf1, 0x1c, 0x42, 0xca, + 0xd1, 0x76, 0x28, 0x6f, 0x92, 0xa7, 0x67, 0xa9, 0x19, 0xa2, 0x44, 0x5b, 0xaa, 0xef, 0x58, 0x7f, + 0x21, 0xc0, 0x88, 0x65, 0xb9, 0x5d, 0x4d, 0x0d, 0x5a, 0xc4, 0x13, 0x52, 0x7c, 0x9f, 0x94, 0x17 + }, + new byte[] + { + 0xc7, 0x2b, 0x82, 0x61, 0x5b, 0xd0, 0x96, 0x84, 0xd3, 0x4a, 0x70, 0xa1, 0x9b, 0x59, 0x33, 0x9f, + 0xc0, 0x20, 0x14, 0x53, 0x29, 0x17, 0xc5, 0x0b, 0xc9, 0x7b, 0x97, 0x02, 0x0d, 0x3a, 0x1e, 0x7c, + 0x3f, 0x6b, 0x52, 0xe8, 0x75, 0x3d, 0xf6, 0xe4, 0x0c, 0x8a, 0x4f, 0xc3, 0x5f, 0x26, 0x65, 0x73, + 0x31, 0x23, 0x28, 0x48, 0x74, 0xaa, 0xa7, 0x36, 0x09, 0xb1, 0xe2, 0x91, 0x04, 0x51, 0x22, 0xfc, + 0x08, 0xa6, 0x05, 0xa4, 0xf1, 0x12, 0x1c, 0x19, 0xeb, 0x40, 0x37, 0xc2, 0xa0, 0x41, 0x1d, 0xd4, + 0xdc, 0x07, 0x43, 0x8f, 0x47, 0xaf, 0xd1, 0x2e, 0x98, 0xab, 0x01, 0xba, 0xf0, 0x66, 0x68, 0xac, + 0xf9, 0xe7, 0x69, 0xb6, 0xcb, 0x8d, 0x78, 0x87, 0x15, 0x03, 0xd5, 0xdf, 0xa3, 0x1a, 0x9d, 0x6a, + 0xea, 0x2f, 0x94, 0x4e, 0x9e, 0x42, 0xd7, 0xb8, 0x38, 0x92, 0xd8, 0xbb, 0xde, 0xdd, 0x9a, 0xbc, + 0xb0, 0x4c, 0x79, 0xf4, 0x58, 0x3e, 0xe9, 0x83, 0x81, 0xff, 0xe3, 0x55, 0xfd, 0x5d, 0xb2, 0xef, + 0x9c, 0x6d, 0x54, 0x99, 0x60, 0xda, 0x3b, 0xec, 0xfa, 0x11, 0xd6, 0xc4, 0x2a, 0xed, 0x4b, 0xae, + 0x13, 0xbf, 0xb9, 0x06, 0x8b, 0xe0, 0x1f, 0x7f, 0x5a, 0xad, 0x90, 0x39, 0x0f, 0xf3, 0xbd, 0x46, + 0x6c, 0x2c, 0xf2, 0xf8, 0xfe, 0xd9, 0xe6, 0x72, 0x0e, 0x89, 0xbe, 0x5e, 0xc6, 0xa8, 0xcf, 0xf5, + 0x57, 0x7e, 0x8c, 0xb7, 0xe1, 0x88, 0x7a, 0xc8, 0x1b, 0x18, 0xdb, 0x6f, 0x35, 0xd2, 0x16, 0x32, + 0x64, 0x63, 0xa5, 0x8e, 0xf7, 0xb4, 0x76, 0x95, 0x86, 0x7d, 0xe5, 0xa9, 0x5c, 0x85, 0x00, 0xc1, + 0x93, 0x4d, 0xfb, 0x30, 0xa2, 0xb5, 0x25, 0x34, 0x10, 0x77, 0x3c, 0x67, 0x71, 0x2d, 0x44, 0x45, + 0x56, 0x0a, 0x50, 0xb3, 0xcd, 0x62, 0x80, 0xce, 0x21, 0x49, 0x24, 0xca, 0xee, 0x27, 0x6e, 0xcc + }, + new byte[] + { + 0xa5, 0xb0, 0x6d, 0xb2, 0x51, 0x55, 0x1f, 0xbf, 0x3e, 0x8d, 0xdd, 0x19, 0x92, 0xea, 0x1b, 0xbd, + 0x32, 0x65, 0x29, 0xa4, 0x89, 0x67, 0xb1, 0x90, 0x68, 0x00, 0xda, 0x0d, 0xa6, 0x59, 0x54, 0xf2, + 0x10, 0x14, 0x2f, 0x45, 0xe0, 0x3b, 0x23, 0xfa, 0xd7, 0x50, 0xac, 0x93, 0xe6, 0x43, 0x01, 0x0a, + 0x5c, 0x78, 0x70, 0x98, 0x46, 0xa9, 0x7b, 0xf3, 0x95, 0x07, 0x2a, 0xd3, 0x74, 0xb3, 0x3f, 0xa3, + 0x60, 0x82, 0x39, 0x4e, 0x34, 0x48, 0xc0, 0x1c, 0xf6, 0xc2, 0x91, 0x64, 0x4d, 0x3c, 0xf8, 0x9d, + 0x35, 0x9a, 0x94, 0xe3, 0x7a, 0xf0, 0xf9, 0x6e, 0xb9, 0x12, 0xb8, 0x04, 0x2d, 0x02, 0x28, 0x13, + 0x85, 0x72, 0x80, 0x87, 0xdb, 0x2b, 0xf1, 0x4f, 0x26, 0xa2, 0xe1, 0x49, 0x7e, 0x9c, 0xcc, 0xa7, + 0xb6, 0xa0, 0xd0, 0x9b, 0x36, 0x77, 0xad, 0x8b, 0x6b, 0x4a, 0x03, 0x1d, 0x05, 0x8a, 0x06, 0x4b, + 0xaf, 0xe5, 0x31, 0xb5, 0xd4, 0xc6, 0x0c, 0x66, 0xba, 0x83, 0xfd, 0x09, 0x0f, 0xae, 0x71, 0xb7, + 0x6f, 0xdc, 0x41, 0xe8, 0x17, 0x8e, 0x40, 0x7f, 0x62, 0x30, 0xff, 0xa8, 0x84, 0x25, 0xfb, 0x16, + 0xce, 0x37, 0x44, 0xab, 0x99, 0x1e, 0xeb, 0x18, 0x3a, 0x47, 0xf7, 0x5f, 0x81, 0xcf, 0xed, 0x58, + 0x2c, 0x6c, 0xfe, 0x9e, 0x57, 0x53, 0x97, 0x20, 0xca, 0x79, 0x1a, 0x5a, 0x88, 0xf5, 0x69, 0x9f, + 0xe7, 0xd9, 0x0e, 0xbe, 0x42, 0xdf, 0x56, 0xe4, 0x4c, 0x22, 0xaa, 0x73, 0x0b, 0x15, 0xc5, 0xee, + 0xfc, 0xc7, 0xd6, 0xcb, 0xcd, 0x8c, 0xe2, 0x76, 0x21, 0xe9, 0xd1, 0xec, 0xc8, 0x7d, 0xd8, 0x8f, + 0x61, 0x7c, 0x2e, 0xbc, 0xde, 0xb4, 0x75, 0xd2, 0xc4, 0x63, 0x3d, 0xa1, 0x5e, 0x5d, 0x6a, 0x08, + 0x24, 0xc9, 0x27, 0xbb, 0xef, 0x33, 0x86, 0x5b, 0xd5, 0x38, 0x52, 0x11, 0xf4, 0xc3, 0x96, 0xc1 + }, + new byte[] + { + 0x34, 0x5a, 0xdb, 0x2c, 0x59, 0x57, 0x12, 0x2b, 0x30, 0xc2, 0xa0, 0x92, 0xbf, 0xed, 0xbc, 0x45, + 0xde, 0x27, 0x9b, 0x96, 0xd3, 0xe6, 0xc5, 0xeb, 0xd8, 0x24, 0x4b, 0xa4, 0x21, 0xcc, 0xa8, 0xd6, + 0xce, 0x3c, 0xba, 0xb1, 0x09, 0xe0, 0xd7, 0x32, 0x66, 0x4a, 0x83, 0x1d, 0x19, 0xca, 0x89, 0x67, + 0x0f, 0x42, 0x07, 0x71, 0xe9, 0xbb, 0x44, 0x5e, 0x85, 0x17, 0xc4, 0xae, 0x9c, 0x3f, 0x6b, 0x78, + 0xe7, 0x6d, 0x02, 0xa7, 0xfe, 0x33, 0xe3, 0xd9, 0x0a, 0x7d, 0xea, 0xf1, 0x18, 0x87, 0x7b, 0x62, + 0x03, 0x79, 0x52, 0x23, 0x00, 0x3e, 0x25, 0xb9, 0xdd, 0x16, 0x68, 0x3b, 0x1f, 0x90, 0xa6, 0x2a, + 0xc6, 0x29, 0x91, 0x8a, 0x9d, 0xef, 0x1e, 0x84, 0x76, 0xee, 0x4f, 0x39, 0xfb, 0x11, 0x1b, 0x0b, + 0x93, 0xad, 0x49, 0xb7, 0x05, 0xe1, 0x4d, 0xcb, 0xe8, 0x38, 0xd0, 0x55, 0xf2, 0xf7, 0x0d, 0x8b, + 0x65, 0xcd, 0xfa, 0xd4, 0x9e, 0xc9, 0x81, 0x4e, 0x14, 0xc8, 0x26, 0xb6, 0xf9, 0xaa, 0xf5, 0x80, + 0xf8, 0x6a, 0x98, 0xab, 0x58, 0xf3, 0xb8, 0x8e, 0xb5, 0x97, 0x43, 0x72, 0xa2, 0xda, 0x64, 0xc1, + 0x40, 0x5c, 0x13, 0xb0, 0xe5, 0xbd, 0x08, 0x9f, 0x1c, 0x7e, 0x8f, 0x06, 0xbe, 0x6e, 0x50, 0x1a, + 0x2f, 0x37, 0xa1, 0xfc, 0x10, 0x2e, 0x70, 0x53, 0x04, 0x20, 0x63, 0x48, 0xe2, 0xfd, 0x6f, 0x7a, + 0x75, 0x61, 0xb3, 0x35, 0x3a, 0xcf, 0x5b, 0x88, 0x82, 0x51, 0xd2, 0x47, 0xa5, 0xa9, 0x74, 0x6c, + 0x31, 0x94, 0x7c, 0x9a, 0xc0, 0xec, 0x15, 0x0e, 0x28, 0x01, 0x2d, 0xc3, 0xf0, 0xb4, 0xe4, 0x8d, + 0xdc, 0xac, 0x3d, 0x5f, 0x86, 0xc7, 0x95, 0x22, 0xf4, 0xb2, 0xa3, 0x54, 0x46, 0xd1, 0x77, 0x8c, + 0x0c, 0xff, 0xaf, 0x56, 0x5d, 0x36, 0x69, 0x7f, 0x99, 0x4c, 0xd5, 0x60, 0x73, 0xdf, 0x41, 0xf6 + }, + new byte[] + { + 0x04, 0xda, 0x79, 0x63, 0x1e, 0xbd, 0xea, 0xe3, 0x0b, 0x65, 0x25, 0x01, 0xcd, 0xa9, 0x92, 0xed, + 0x18, 0x75, 0x57, 0x30, 0x89, 0x03, 0x09, 0x6a, 0xdc, 0xc7, 0x98, 0xa4, 0x50, 0x91, 0x1f, 0xb1, + 0x0c, 0x77, 0x85, 0x66, 0xca, 0x12, 0x1c, 0x67, 0xc2, 0xe0, 0x17, 0x83, 0x59, 0x9d, 0xd5, 0x22, + 0x82, 0x56, 0xa8, 0x9f, 0xc6, 0x60, 0x48, 0xb3, 0x11, 0xfc, 0xa3, 0x28, 0xfb, 0x06, 0xdd, 0x5d, + 0xba, 0x29, 0x7a, 0x4f, 0xe8, 0xfe, 0xe9, 0x10, 0xcb, 0xf3, 0x93, 0x7b, 0x6c, 0x69, 0x54, 0xe7, + 0x44, 0xa2, 0x84, 0x1d, 0x8d, 0xce, 0xff, 0xfa, 0x1a, 0x87, 0x90, 0x74, 0xa1, 0xf8, 0x14, 0xaa, + 0xbc, 0xc0, 0xcf, 0x31, 0xb4, 0xd9, 0xbf, 0xd8, 0x5e, 0x26, 0x2d, 0xd0, 0xe4, 0x3f, 0x19, 0xd6, + 0x8c, 0x2f, 0xab, 0x39, 0x58, 0x72, 0x6e, 0xdf, 0x3b, 0xe6, 0x3c, 0xb6, 0x62, 0x88, 0xde, 0x40, + 0xb2, 0x8f, 0x9b, 0x7c, 0x95, 0x43, 0xd1, 0x9e, 0xac, 0x9c, 0xd4, 0x23, 0xe1, 0x0e, 0x4b, 0x53, + 0x33, 0x46, 0x20, 0x13, 0x34, 0x1b, 0x97, 0xf7, 0xf1, 0xc3, 0x61, 0x4a, 0x6f, 0x5a, 0x21, 0x7f, + 0x70, 0x2e, 0x55, 0x41, 0x05, 0xc5, 0xd7, 0x76, 0xe5, 0x27, 0x15, 0xec, 0x42, 0x5c, 0x4d, 0x78, + 0x35, 0x8b, 0xef, 0xd2, 0xee, 0xb5, 0xbe, 0xae, 0x02, 0x3a, 0xd3, 0x5f, 0xc4, 0x24, 0xf0, 0xf9, + 0x51, 0x4e, 0xeb, 0x00, 0x0f, 0xfd, 0xaf, 0x3e, 0xc1, 0x9a, 0x52, 0x86, 0x81, 0x80, 0x7e, 0xf6, + 0x2b, 0xcc, 0xb9, 0x7d, 0x68, 0xf2, 0xad, 0x99, 0xa7, 0x07, 0x2c, 0x73, 0x38, 0xb0, 0x6b, 0xb7, + 0x8e, 0x71, 0xa0, 0xf4, 0x3d, 0xa6, 0x0d, 0x37, 0xdb, 0x0a, 0x47, 0x36, 0x16, 0x96, 0x6d, 0x32, + 0x2a, 0x5b, 0xe2, 0x45, 0x94, 0xf5, 0xa5, 0x4c, 0xc8, 0x8a, 0x49, 0x64, 0xbb, 0x08, 0xc9, 0xb8 + } + }; + + public byte[] Decrypt(ReadOnlySpan spadInput) + { + if (spadInput.Length != 16) + { + throw new ArgumentException("spadInput must be exactly 16 bytes.", nameof(spadInput)); + } + + var spad = spadInput.ToArray(); + var nTables = SBoxInv.Length - 1; + + for (var i = 0; i < spad.Length; i++) + { + spad[i] = SBoxInv[nTables][spad[i]]; + } + + var count = (spad[15] >> 4) + 7; + var table = spad[15] + IterAdd * count; + + for (var iteration = 0; iteration < count; iteration++) + { + table -= IterAdd; + RotateRight(spad, 15, 5); + var tableIndex = table % nTables; + for (var i = 0; i < 15; i++) + { + spad[i] = SBoxInv[tableIndex][spad[i]]; + } + } + + return spad; + } + + private static void RotateRight(byte[] data, int nBytes, int nBits) + { + var prior = data[nBytes - 1]; + for (var i = 0; i < nBytes; i++) + { + var current = data[i]; + data[i] = (byte)(((current >> nBits) | ((prior & ((1 << nBits) - 1)) << (8 - nBits))) & 0xFF); + prior = current; + } + } +} diff --git a/src/NfcAime.Cli/FelicaCommandBuilder.cs b/src/NfcAime.Cli/FelicaCommandBuilder.cs new file mode 100644 index 0000000..6a24cb4 --- /dev/null +++ b/src/NfcAime.Cli/FelicaCommandBuilder.cs @@ -0,0 +1,36 @@ +namespace Nfcaime.Cli; + +internal static class FelicaCommandBuilder +{ + internal static byte[] BuildPollingCommand() => new byte[] { 0x06, 0x00, 0x88, 0xB4, 0x01, 0x0F }; + + internal static byte[] BuildReadWithoutEncryptionCommand(ReadOnlySpan idm) + { + if (idm.Length != 8) + { + throw new ArgumentException("IDm must be exactly 8 bytes.", nameof(idm)); + } + + // FeliCa Read Without Encryption: + // Byte 0: Length (16 bytes = 0x10) + // Byte 1: Command Code (0x06) + // Byte 2-9: IDm + // Byte 10: Number of Services (0x01) + // Byte 11-12: Service Code List (0x0B, 0x00 for Service 0x000B, Little Endian) + // Byte 13: Number of Blocks (0x01) + // Byte 14-15: Block List (0x80, 0x00 for Service 0, Block 0) + + byte[] command = new byte[16]; + command[0] = 0x10; + command[1] = 0x06; + idm.CopyTo(command.AsSpan(2, 8)); + command[10] = 0x01; + command[11] = 0x0B; + command[12] = 0x00; + command[13] = 0x01; + command[14] = 0x80; + command[15] = 0x00; + + return command; + } +} diff --git a/src/NfcAime.Cli/FelicaResponseParser.cs b/src/NfcAime.Cli/FelicaResponseParser.cs new file mode 100644 index 0000000..7b94f39 --- /dev/null +++ b/src/NfcAime.Cli/FelicaResponseParser.cs @@ -0,0 +1,17 @@ +namespace Nfcaime.Cli; + +internal static class FelicaResponseParser +{ + private const int Spad0Offset = 13; + private const int Spad0Length = 16; + + internal static byte[] ParseSpad0(ReadOnlySpan response) + { + if (response.Length < Spad0Offset + Spad0Length) + { + throw new ArgumentException($"Response must be at least {Spad0Offset + Spad0Length} bytes.", nameof(response)); + } + + return response.Slice(Spad0Offset, Spad0Length).ToArray(); + } +} diff --git a/src/NfcAime.Cli/Nfcaime.Cli.csproj b/src/NfcAime.Cli/Nfcaime.Cli.csproj new file mode 100644 index 0000000..6c3ca54 --- /dev/null +++ b/src/NfcAime.Cli/Nfcaime.Cli.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/src/NfcAime.Cli/Pn532/IPn532FrameTransport.cs b/src/NfcAime.Cli/Pn532/IPn532FrameTransport.cs new file mode 100644 index 0000000..140902f --- /dev/null +++ b/src/NfcAime.Cli/Pn532/IPn532FrameTransport.cs @@ -0,0 +1,9 @@ +namespace Nfcaime.Cli.Pn532; + +internal interface IPn532FrameTransport : IDisposable +{ + void Open(); + void Close(); + void WriteFrame(ReadOnlySpan frame); + Pn532FrameParseResult ReadFrame(TimeSpan timeout); +} diff --git a/src/NfcAime.Cli/Pn532/Pn532FrameKind.cs b/src/NfcAime.Cli/Pn532/Pn532FrameKind.cs new file mode 100644 index 0000000..d74d734 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/Pn532FrameKind.cs @@ -0,0 +1,10 @@ +namespace Nfcaime.Cli.Pn532; + +internal enum Pn532FrameKind +{ + Ack, + Nak, + Data, + ChecksumError, + Invalid +} diff --git a/src/NfcAime.Cli/Pn532/Pn532FrameParseResult.cs b/src/NfcAime.Cli/Pn532/Pn532FrameParseResult.cs new file mode 100644 index 0000000..213c7c4 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/Pn532FrameParseResult.cs @@ -0,0 +1,10 @@ +namespace Nfcaime.Cli.Pn532; + +internal sealed class Pn532FrameParseResult +{ + public required Pn532FrameKind Kind { get; init; } + public byte Tfi { get; init; } + public byte[] Payload { get; init; } = Array.Empty(); + public byte[]? RawFrame { get; init; } + public string? Error { get; init; } +} diff --git a/src/NfcAime.Cli/Pn532/Pn532FrameParser.cs b/src/NfcAime.Cli/Pn532/Pn532FrameParser.cs new file mode 100644 index 0000000..62f7b29 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/Pn532FrameParser.cs @@ -0,0 +1,77 @@ +namespace Nfcaime.Cli.Pn532; + +internal static class Pn532FrameParser +{ + public static Pn532FrameParseResult Parse(ReadOnlySpan frame) + { + if (frame.Length < 6) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Frame too short", RawFrame = frame.ToArray() }; + } + + if (!frame.StartsWith(Pn532HsuFrame.AckFrame) && !frame.StartsWith(Pn532HsuFrame.NakFrame)) + { + if (frame[0] != Pn532HsuFrame.Preamble || frame[1] != Pn532HsuFrame.StartCode1 || frame[2] != Pn532HsuFrame.StartCode2) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Missing preamble/start codes", RawFrame = frame.ToArray() }; + } + } + + if (frame.StartsWith(Pn532HsuFrame.AckFrame)) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Ack, RawFrame = frame.ToArray() }; + } + + if (frame.StartsWith(Pn532HsuFrame.NakFrame)) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Nak, RawFrame = frame.ToArray() }; + } + + var length = frame[3]; + var lcs = frame[4]; + if (((length + lcs) & 0xFF) != 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.ChecksumError, Error = "LCS checksum mismatch", RawFrame = frame.ToArray() }; + } + + if (length == 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Invalid length", RawFrame = frame.ToArray() }; + } + + var expectedLength = 7 + length; + if (frame.Length < expectedLength) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Frame length mismatch", RawFrame = frame.ToArray() }; + } + + var tfi = frame[5]; + var payloadLength = length - 1; + + var payload = payloadLength == 0 ? Array.Empty() : frame.Slice(6, payloadLength).ToArray(); + var dcs = frame[6 + payloadLength]; + var sum = tfi; + for (var index = 0; index < payload.Length; index++) + { + sum += payload[index]; + } + + if (((sum + dcs) & 0xFF) != 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.ChecksumError, Error = "DCS checksum mismatch", RawFrame = frame.ToArray() }; + } + + if (frame[7 + payloadLength] != Pn532HsuFrame.Postamble) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Missing postamble", RawFrame = frame.ToArray() }; + } + + return new Pn532FrameParseResult + { + Kind = Pn532FrameKind.Data, + Tfi = tfi, + Payload = payload, + RawFrame = frame[..expectedLength].ToArray() + }; + } +} diff --git a/src/NfcAime.Cli/Pn532/Pn532HsuFrame.cs b/src/NfcAime.Cli/Pn532/Pn532HsuFrame.cs new file mode 100644 index 0000000..aaa6c09 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/Pn532HsuFrame.cs @@ -0,0 +1,61 @@ +namespace Nfcaime.Cli.Pn532; + +internal static class Pn532HsuFrame +{ + public const byte Preamble = 0x00; + public const byte StartCode1 = 0x00; + public const byte StartCode2 = 0xFF; + public const byte Postamble = 0x00; + + public const byte HostToPn532Tfi = 0xD4; + public const byte Pn532ToHostTfi = 0xD5; + + public static readonly byte[] AckFrame = + [ + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00 + ]; + + public static readonly byte[] NakFrame = + [ + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00 + ]; + + public static byte[] BuildDataFrame(byte tfi, ReadOnlySpan data) + { + if (data.Length + 1 > byte.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(data), "PN532 HSU frames must fit in 255 bytes."); + } + + var length = (byte)(data.Length + 1); + var lcs = ComputeLcs(length); + var dcs = ComputeDcs(tfi, data); + + var frame = new byte[7 + length]; + frame[0] = Preamble; + frame[1] = StartCode1; + frame[2] = StartCode2; + frame[3] = length; + frame[4] = lcs; + frame[5] = tfi; + data.CopyTo(frame.AsSpan(6)); + frame[6 + data.Length] = dcs; + frame[7 + data.Length] = Postamble; + + return frame; + } + + public static byte ComputeLcs(byte length) + => (byte)((0x100 - length) & 0xFF); + + public static byte ComputeDcs(byte tfi, ReadOnlySpan data) + { + var sum = tfi; + for (var index = 0; index < data.Length; index++) + { + sum += data[index]; + } + + return (byte)((0x100 - (sum & 0xFF)) & 0xFF); + } +} diff --git a/src/NfcAime.Cli/Pn532/Pn532Session.cs b/src/NfcAime.Cli/Pn532/Pn532Session.cs new file mode 100644 index 0000000..5cd5865 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/Pn532Session.cs @@ -0,0 +1,96 @@ +namespace Nfcaime.Cli.Pn532; + +internal sealed class Pn532Session : IDisposable +{ + private readonly IPn532FrameTransport _transport; + private readonly TimeSpan _timeout; + private readonly int _maxRetries; + + public Pn532Session(IPn532FrameTransport transport, TimeSpan timeout, int maxRetries) + { + _transport = transport; + _timeout = timeout; + _maxRetries = maxRetries; + } + + public void Open() => _transport.Open(); + + public void Close() => _transport.Close(); + + public void Dispose() => _transport.Dispose(); + + public Pn532FrameParseResult SendCommand(ReadOnlySpan payload) + => SendCommand(payload, responseTimeout: null); + + public Pn532FrameParseResult SendCommand(ReadOnlySpan payload, TimeSpan? responseTimeout) + { + var responseReadTimeout = responseTimeout ?? _timeout; + var frame = Pn532HsuFrame.BuildDataFrame(Pn532HsuFrame.HostToPn532Tfi, payload); + var maxAttempts = _maxRetries + 1; + for (var attempt = 0; attempt < maxAttempts; attempt++) + { + _transport.WriteFrame(frame); + var ack = _transport.ReadFrame(_timeout); + if (ack.Kind == Pn532FrameKind.Ack) + { + var response = _transport.ReadFrame(responseReadTimeout); + if (response.Kind == Pn532FrameKind.Data) + { + return response; + } + + if (response.Kind == Pn532FrameKind.ChecksumError) + { + Console.WriteLine($"PN532 checksum error (response); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (response.Kind == Pn532FrameKind.Invalid) + { + if (string.Equals(response.Error, "Read timeout", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"PN532 timeout (response after ACK); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + Console.WriteLine($"PN532 read error (response): {response.Error}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + // Unexpected response kind after ACK; retry rather than immediately failing. + Console.WriteLine($"PN532 unexpected response kind {response.Kind}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.Nak) + { + Console.WriteLine($"PN532 NAK received; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.ChecksumError) + { + Console.WriteLine($"PN532 checksum error; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.Invalid) + { + if (string.Equals(ack.Error, "Read timeout", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"PN532 timeout (waiting for ACK); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + Console.WriteLine($"PN532 read error: {ack.Error}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + } + + return new Pn532FrameParseResult + { + Kind = Pn532FrameKind.Invalid, + Error = "Retry limit exceeded (no valid ACK/response). Check COM port, baud rate, module mode (HSU/UART), and power." + }; + } +} diff --git a/src/NfcAime.Cli/Pn532/ReplayFrameTransport.cs b/src/NfcAime.Cli/Pn532/ReplayFrameTransport.cs new file mode 100644 index 0000000..0139c10 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/ReplayFrameTransport.cs @@ -0,0 +1,50 @@ +namespace Nfcaime.Cli.Pn532; + +internal sealed class ReplayFrameTransport : IPn532FrameTransport +{ + private readonly Queue _frames; + private bool _injectCorruption; + + public ReplayFrameTransport(IEnumerable frames, bool injectCorruption) + { + _frames = new Queue(frames.Select(frame => frame.ToArray())); + _injectCorruption = injectCorruption; + } + + public void Open() + { + } + + public void Close() + { + } + + public void WriteFrame(ReadOnlySpan frame) + { + } + + public Pn532FrameParseResult ReadFrame(TimeSpan timeout) + { + if (_frames.Count == 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Replay buffer empty" }; + } + + var frame = _frames.Dequeue(); + if (_injectCorruption) + { + _injectCorruption = false; + if (frame.Length > 0) + { + frame[^1] ^= 0xFF; + } + } + + return Pn532FrameParser.Parse(frame); + } + + public void Dispose() + { + _frames.Clear(); + } +} diff --git a/src/NfcAime.Cli/Pn532/SerialFrameTransport.cs b/src/NfcAime.Cli/Pn532/SerialFrameTransport.cs new file mode 100644 index 0000000..4075f40 --- /dev/null +++ b/src/NfcAime.Cli/Pn532/SerialFrameTransport.cs @@ -0,0 +1,148 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.IO.Ports; + +namespace Nfcaime.Cli.Pn532; + +internal sealed class SerialFrameTransport : IPn532FrameTransport +{ + private readonly SerialPort _port; + private readonly TimeSpan _readChunkTimeout; + private readonly int _maxFrameBytes; + private bool _wakeupSent; + + public SerialFrameTransport(string portName, int baud, TimeSpan readChunkTimeout, int maxFrameBytes = 300) + { + var chunkTimeout = readChunkTimeout; + if (chunkTimeout <= TimeSpan.Zero || chunkTimeout > TimeSpan.FromMilliseconds(50)) + { + chunkTimeout = TimeSpan.FromMilliseconds(50); + } + + _port = new SerialPort(portName, baud) + { + ReadTimeout = (int)chunkTimeout.TotalMilliseconds, + WriteTimeout = (int)readChunkTimeout.TotalMilliseconds, + Handshake = Handshake.None, + Parity = Parity.None, + DataBits = 8, + StopBits = StopBits.One, + DtrEnable = false, + RtsEnable = false + }; + _readChunkTimeout = chunkTimeout; + _maxFrameBytes = maxFrameBytes; + } + + public void Open() + { + if (!_port.IsOpen) + { + _port.Open(); + _port.DiscardInBuffer(); + _port.DiscardOutBuffer(); + SendWakeupPattern(); + } + } + + public void Close() + { + if (_port.IsOpen) + { + _port.Close(); + } + } + + public void WriteFrame(ReadOnlySpan frame) + { + if (!_wakeupSent) + { + SendWakeupPattern(); + } + + // Clear any stale data from previous commands or wake-up echos + if (_port.IsOpen && _port.BytesToRead > 0) + { + _port.DiscardInBuffer(); + } + + var buffer = frame.ToArray(); + _port.Write(buffer, 0, buffer.Length); + } + + private void SendWakeupPattern() + { + // Simple HSU wakeup: Just long preamble. + // We avoid sending SAMConfig here to keep the buffer clean for the first real command. + var wakeup = new byte[] { 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + _port.Write(wakeup, 0, wakeup.Length); + Thread.Sleep(50); + + if (_port.IsOpen) + { + _port.DiscardInBuffer(); + } + + _wakeupSent = true; + } + + public Pn532FrameParseResult ReadFrame(TimeSpan timeout) + { + var deadline = Stopwatch.GetTimestamp() + (long)(timeout.TotalSeconds * Stopwatch.Frequency); + var buffer = new List(_maxFrameBytes); + + while (Stopwatch.GetTimestamp() < deadline) + { + try + { + if (_port.BytesToRead == 0) + { + Thread.Sleep(1); + continue; + } + + var next = _port.ReadByte(); + if (next < 0) continue; + buffer.Add((byte)next); + + if (buffer.Count >= 6) + { + var span = CollectionsMarshal.AsSpan(buffer); + for (int i = 0; i <= span.Length - 6; i++) + { + var window = span[i..]; + if (window.StartsWith(Pn532HsuFrame.AckFrame)) + { + return Pn532FrameParser.Parse(Pn532HsuFrame.AckFrame); + } + if (window.StartsWith(Pn532HsuFrame.NakFrame)) + { + return Pn532FrameParser.Parse(Pn532HsuFrame.NakFrame); + } + if (window.Length >= 7 && window[0] == Pn532HsuFrame.Preamble && window[1] == Pn532HsuFrame.StartCode1 && window[2] == Pn532HsuFrame.StartCode2) + { + var length = window[3]; + var expectedLength = 7 + length; + if (window.Length >= expectedLength) + { + return Pn532FrameParser.Parse(window[..expectedLength]); + } + } + } + } + } + catch (TimeoutException) + { + Thread.Sleep(1); + } + } + + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Read timeout" }; + } + + public void Dispose() + { + Close(); + _port.Dispose(); + } +} diff --git a/src/NfcAime.Cli/Program.cs b/src/NfcAime.Cli/Program.cs new file mode 100644 index 0000000..e9ed895 --- /dev/null +++ b/src/NfcAime.Cli/Program.cs @@ -0,0 +1,299 @@ +using Nfcaime.Cli.Pn532; +using System.IO.Ports; + +namespace Nfcaime.Cli; + +internal static class Program +{ + private const string DefaultPort = "COM14"; + private const int DefaultBaud = 115200; + private const int DefaultRetryCount = 2; + private const int DefaultTimeoutMs = 1500; + + private static int Main(string[] args) + { + if (!TryParseArgs(args, out var options, out var errorMessage)) + { + Console.Error.WriteLine(errorMessage); + return 1; + } + + Console.WriteLine($"Mode: {options.Mode}"); + Console.WriteLine($"Port: {options.Port}"); + Console.WriteLine($"Baud: {options.Baud}"); + Console.WriteLine($"Retries: {options.Retries}"); + Console.WriteLine($"TimeoutMs: {options.TimeoutMs}"); + + if (options.Mode == "replay") RunReplay(options); + else if (options.Mode == "diag") RunDiag(options); + else RunLive(options); + + return 0; + } + + private static bool TryParseArgs(string[] args, out CliOptions options, out string errorMessage) + { + var parsed = new CliOptions { Port = DefaultPort, Baud = DefaultBaud, Mode = "live" }; + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + if (!arg.StartsWith("--", StringComparison.Ordinal)) { errorMessage = $"Unknown argument: {arg}"; options = default!; return false; } + var equalsIndex = arg.IndexOf('='); + var name = equalsIndex >= 0 ? arg[..equalsIndex] : arg; + var value = equalsIndex >= 0 ? arg[(equalsIndex + 1)..] : (index + 1 < args.Length && !args[index + 1].StartsWith("--") ? args[++index] : null); + switch (name) + { + case "--port": parsed.Port = value ?? DefaultPort; break; + case "--baud": if (int.TryParse(value, out var baud)) parsed.Baud = baud; break; + case "--mode": parsed.Mode = value?.ToLowerInvariant() ?? "live"; break; + case "--retries": if (int.TryParse(value, out var retries)) parsed.Retries = retries; break; + case "--timeoutMs": if (int.TryParse(value, out var timeoutMs)) parsed.TimeoutMs = timeoutMs; break; + default: return Fail($"Unknown argument: {name}", out options, out errorMessage); + } + } + options = parsed; errorMessage = string.Empty; return true; + } + + private static bool Fail(string message, out CliOptions options, out string errorMessage) + { + options = default!; errorMessage = message; return false; + } + + private sealed class CliOptions { public string Port { get; set; } = DefaultPort; public int Baud { get; set; } = DefaultBaud; public string Mode { get; set; } = "live"; public string? ReplayFile { get; set; } public bool ReplayCorrupt { get; set; } public int Retries { get; set; } = DefaultRetryCount; public int TimeoutMs { get; set; } = DefaultTimeoutMs; } + + private static void RunLive(CliOptions options) + { + var timeout = TimeSpan.FromMilliseconds(options.TimeoutMs); + using var transport = new SerialFrameTransport(options.Port, options.Baud, timeout); + using var session = new Pn532Session(transport, timeout, options.Retries); + session.Open(); + try + { + var result = RunPn532FelicaFlow(session); + Console.WriteLine($"CardId: {Convert.ToHexString(result.CardId)}"); + Console.WriteLine($"AccessCode: {result.AccessCode}"); + } + finally { session.Close(); } + } + + private static void RunReplay(CliOptions options) + { + var timeout = TimeSpan.FromMilliseconds(options.TimeoutMs); + var frames = LoadReplayFrames(options.ReplayFile!); + using var transport = new ReplayFrameTransport(frames, options.ReplayCorrupt); + using var session = new Pn532Session(transport, timeout, options.Retries); + session.Open(); + try + { + var result = RunPn532FelicaFlow(session); + Console.WriteLine($"CardId: {Convert.ToHexString(result.CardId)}"); + Console.WriteLine($"AccessCode: {result.AccessCode}"); + } + finally { session.Close(); } + } + + private static void RunDiag(CliOptions options) + { + using var serial = new SerialPort(options.Port, options.Baud) { ReadTimeout = 500, WriteTimeout = 500 }; + serial.Open(); + serial.Write(new byte[] { 0x55, 0x55, 0x00, 0x00, 0x00 }, 0, 5); + Thread.Sleep(50); + var fw = Pn532HsuFrame.BuildDataFrame(Pn532HsuFrame.HostToPn532Tfi, new byte[] { 0x02 }); + serial.Write(fw, 0, fw.Length); + Thread.Sleep(200); + var buf = new byte[serial.BytesToRead]; + serial.Read(buf, 0, buf.Length); + Console.WriteLine($"RX: {Convert.ToHexString(buf)}"); + } + + private enum CardKind { Felica, MifareClassic } + private sealed record CardTarget(CardKind Kind, byte Tg, byte[] CardId); + private sealed record FlowResult(byte[] CardId, string AccessCode); + + private static FlowResult RunPn532FelicaFlow(Pn532Session session) + { + ExpectPn532ResponseCode(session.SendCommand(new byte[] { 0x02 }), expectedResponseCode: 0x03); + ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x14, 0x01, 0x14, 0x01 }), expectedResponseCode: 0x15); + ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x32, 0x01, 0x03 }), expectedResponseCode: 0x33); + + var target = WaitForCard(session); + Thread.Sleep(100); + + if (target.Kind == CardKind.Felica) + { + Console.WriteLine($"Card detected! IDm: {Convert.ToHexString(target.CardId)}"); + var readCmd = FelicaCommandBuilder.BuildReadWithoutEncryptionCommand(target.CardId); + var readResponse = SendInDataExchange(session, target.Tg, readCmd, TimeSpan.FromSeconds(5)); + var spad0 = FelicaResponseParser.ParseSpad0(readResponse); + var decryptor = new FeliCaDecryptor(); + var decrypted = decryptor.Decrypt(spad0); + var accessCode = AccessCodeFormatter.ToAccessCodeString(decrypted); + return new FlowResult(target.CardId, accessCode); + } + + Console.WriteLine($"Card detected! TypeA UID: {Convert.ToHexString(target.CardId)}"); + var m1AccessCode = TryReadMifareClassicAccessCode(session, target.Tg, target.CardId); + if (m1AccessCode is null) + { + throw new InvalidOperationException("Failed to read Mifare Classic AccessCode: no key matched sector 0."); + } + + return new FlowResult(target.CardId, m1AccessCode); + } + + private static CardTarget WaitForCard(Pn532Session session) + { + var warned = false; + while (true) + { + var f212 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00 }, TimeSpan.FromMilliseconds(500)); + if (ExtractFeliCaTarget(f212, out var target)) return target; + + var a106 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x00 }, TimeSpan.FromMilliseconds(600)); + if (ExtractTypeATarget(a106, out target)) + { + return target; + } + + if (!warned) { Console.WriteLine("Waiting for Aime/FeliCa or M1 card..."); warned = true; } + Thread.Sleep(100); + } + } + + private static bool ExtractFeliCaTarget(Pn532FrameParseResult result, out CardTarget target) + { + target = null; + if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 15 && result.Payload[0] == 0x4B && result.Payload[1] > 0) + { + var tg = result.Payload[2]; + var idm = result.Payload.AsSpan(5, 8).ToArray(); + target = new CardTarget(CardKind.Felica, tg, idm); + return true; + } + return false; + } + + private static bool ExtractTypeATarget(Pn532FrameParseResult result, out CardTarget target) + { + target = null; + if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 8 && result.Payload[0] == 0x4B && result.Payload[1] > 0) + { + var tg = result.Payload[2]; + var uidLen = result.Payload[6]; + var uidOffset = 7; + if (uidLen > 0 && result.Payload.Length >= uidOffset + uidLen) + { + var uid = result.Payload.AsSpan(uidOffset, uidLen).ToArray(); + target = new CardTarget(CardKind.MifareClassic, tg, uid); + return true; + } + } + return false; + } + + private static string? TryReadMifareClassicAccessCode(Pn532Session session, byte tg, ReadOnlySpan uid) + { + if (uid.Length < 4) + { + throw new InvalidOperationException("Mifare Classic UID is shorter than 4 bytes."); + } + + const byte blockNumber = 2; // sector 0 block 2 + var uid4 = uid[..4]; + + foreach (var keyHex in MifareClassicKeys) + { + var key = Convert.FromHexString(keyHex); + if (TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: true, key, uid4) || + TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: false, key, uid4)) + { + var block = ReadMifareBlock(session, tg, blockNumber); + var hex = Convert.ToHexString(block); + return hex.Length <= 20 ? hex : hex[^20..]; + } + } + + return null; + } + + private static bool TryMifareAuthenticate( + Pn532Session session, + byte tg, + byte blockNumber, + bool keyTypeA, + ReadOnlySpan key, + ReadOnlySpan uid4 + ) + { + var cmd = new byte[2 + 1 + 1 + 6 + 4]; + cmd[0] = 0x40; + cmd[1] = tg; + cmd[2] = keyTypeA ? (byte)0x60 : (byte)0x61; + cmd[3] = blockNumber; + key.CopyTo(cmd.AsSpan(4, 6)); + uid4.CopyTo(cmd.AsSpan(10, 4)); + + var response = session.SendCommand(cmd, TimeSpan.FromMilliseconds(1000)); + if (response.Kind != Pn532FrameKind.Data || response.Payload.Length < 2 || response.Payload[0] != 0x41) + { + return false; + } + + return response.Payload[1] == 0x00; + } + + private static byte[] ReadMifareBlock(Pn532Session session, byte tg, byte blockNumber) + { + var cmd = new byte[] { 0x40, tg, 0x30, blockNumber }; + var response = ExpectPn532ResponseCode(session.SendCommand(cmd, TimeSpan.FromMilliseconds(1200)), expectedResponseCode: 0x41); + if (response.Payload.Length < 2 || response.Payload[1] != 0x00) + { + throw new InvalidOperationException($"Mifare read block failed: status=0x{(response.Payload.Length > 1 ? response.Payload[1] : 0):X2}"); + } + + var data = response.Payload.Length == 2 ? Array.Empty() : response.Payload[2..]; + if (data.Length < 16) + { + throw new InvalidOperationException($"Mifare read block returned {data.Length} bytes, expected at least 16."); + } + + return data[..16]; + } + + private static readonly string[] MifareClassicKeys = + [ + "6090D00632F5","019761AA8082","574343467632","A99164400748","62742819AD7C","CC5075E42BA1", + "B9DF35A0814C","8AF9C718F23D","58CD5C3673CB","FC80E88EB88C","7A3CDAD7C023","30424C029001", + "024E4E44001F","ECBBFA57C6AD","4757698143BD","1D30972E6485","F8526D1A8D6D","1300EC8C7E80", + "F80A65A87FFA","DEB06ED4AF8E","4AD96BF28190","000390014D41","0800F9917CB0","730050555253", + "4146D4A956C4","131157FBB126","E69DD9015A43","337237F254D5","9A8389F32FBF","7B8FB4A7100B", + "C8382A233993","7B304F2A12A6","FC9418BF788B" + ]; + + private static byte[] SendInDataExchange(Pn532Session session, byte tg, ReadOnlySpan payloadToTarget, TimeSpan? timeout = null) + { + var pn532Payload = new byte[2 + payloadToTarget.Length]; + pn532Payload[0] = 0x40; pn532Payload[1] = tg; + payloadToTarget.CopyTo(pn532Payload.AsSpan(2)); + var response = ExpectPn532ResponseCode(session.SendCommand(pn532Payload, responseTimeout: timeout ?? TimeSpan.FromSeconds(4)), expectedResponseCode: 0x41); + if (response.Payload.Length < 2 || response.Payload[1] != 0x00) + throw new InvalidOperationException($"InDataExchange failed: 0x{(response.Payload.Length > 1 ? response.Payload[1] : 0):X2}"); + return response.Payload.Length == 2 ? Array.Empty() : response.Payload[2..]; + } + + private static Pn532FrameParseResult ExpectPn532ResponseCode(Pn532FrameParseResult response, byte expectedResponseCode) + { + if (response.Kind != Pn532FrameKind.Data) throw new InvalidOperationException($"PN532 error: {response.Kind} ({response.Error})."); + if (response.Payload.Length < 1 || response.Payload[0] != expectedResponseCode) + throw new InvalidOperationException($"Expected 0x{expectedResponseCode:X2}, got 0x{(response.Payload.Length > 0 ? response.Payload[0] : 0):X2}"); + return response; + } + + private static void ExpectPn532StatusOk(Pn532FrameParseResult response, byte expectedResponseCode) + { + response = ExpectPn532ResponseCode(response, expectedResponseCode); + if (response.Payload.Length >= 2 && response.Payload[1] != 0x00) throw new InvalidOperationException($"Status fail: 0x{response.Payload[1]:X2}"); + } + + private static IReadOnlyList LoadReplayFrames(string path) => File.ReadAllLines(path).Where(l => !string.IsNullOrWhiteSpace(l) && !l.Trim().StartsWith('#')).Select(line => line.Split(' ').Select(s => Convert.ToByte(s, 16)).ToArray()).ToList(); +} diff --git a/src/NfcAime.Dll/AccessCodeFormatter.cs b/src/NfcAime.Dll/AccessCodeFormatter.cs new file mode 100644 index 0000000..68cb853 --- /dev/null +++ b/src/NfcAime.Dll/AccessCodeFormatter.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; + +namespace NfcAime.Dll; + +internal static class AccessCodeFormatter +{ + private const int DecryptedByteCount = 16; + private const int AccessCodeHexLength = 20; + + internal static string ToAccessCodeString(ReadOnlySpan decryptedBytes) + { + if (decryptedBytes.Length != DecryptedByteCount) + { + throw new ArgumentException($"Expected {DecryptedByteCount} decrypted bytes.", nameof(decryptedBytes)); + } + + var bytes = decryptedBytes.ToArray(); + var hex = BitConverter.ToString(bytes).Replace("-", ""); + return hex.Substring(hex.Length - AccessCodeHexLength); + } + + public static byte[] ToAccessCodeBytes(string accessCode) + { + if (string.IsNullOrWhiteSpace(accessCode) || accessCode.Length != AccessCodeHexLength || !IsHexString(accessCode)) + { + return null; + } + + return HexStringToBytes(accessCode); + } + + private static bool IsHexString(string s) + { + return s.All(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + } + + private static byte[] HexStringToBytes(string hex) + { + byte[] bytes = new byte[hex.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return bytes; + } +} diff --git a/src/NfcAime.Dll/AimeReader.cs b/src/NfcAime.Dll/AimeReader.cs new file mode 100644 index 0000000..9b01c97 --- /dev/null +++ b/src/NfcAime.Dll/AimeReader.cs @@ -0,0 +1,185 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using NfcAime.Dll.PN532; +using static NfcAime.Dll.MiFareHandle; + +namespace NfcAime.Dll; + +public class AimeReader +{ + private string Port = "COM15"; + private int Baud = 115200; + + private Pn532Session session = null; + + public enum CardKind { Felica, MifareClassic, Null} + private sealed record FlowResult(CardKind CardKind, byte[] CardId, string AccessCode); + private sealed record CardTarget(CardKind Kind, byte Tg, byte[] CardId); + + // 兼容低版本 .NET:实现 ToHexString 和 FromHexString + public static string ToHexString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + + public static byte[] FromHexString(string hex) + { + if (hex.Length % 2 != 0) + throw new ArgumentException("Invalid hex string length."); + var bytes = new byte[hex.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + return bytes; + } + + public AimeReader(string port, int baud) + { + Port = port; + Baud = baud; + TimeSpan timeout = TimeSpan.FromMilliseconds(100); + using var transport = new SerialFrameTransport(Port, Baud, timeout); + session = new Pn532Session(transport, timeout, 0); + } + + public (CardKind CardKind, byte[] IDm, string AccessCode) ReadCard() + { + session.Open(); + try + { + var result = RunPn532Flow(session); + return (result.CardKind , result.CardId, result.AccessCode); + } + finally { session.Close(); } + } + + public void CloseReader() + { + session.Close(); + } + + private FlowResult RunPn532Flow(Pn532Session session) + { + ExpectPn532ResponseCode(session.SendCommand(new byte[] { 0x02 }), expectedResponseCode: 0x03); + ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x14, 0x01, 0x14, 0x01 }), expectedResponseCode: 0x15); + ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x32, 0x01, 0x03 }), expectedResponseCode: 0x33); + + var target = WaitForCard(session); + Thread.Sleep(100); + if (target.Kind == CardKind.Null) + { + return new FlowResult(target.Kind, target.CardId, ""); + } + if (target.Kind == CardKind.Felica) + { + Console.WriteLine($"Card detected! IDm: {ToHexString(target.CardId)}"); + var readCmd = FelicaCommandBuilder.BuildReadWithoutEncryptionCommand(target.CardId); + var readResponse = SendInDataExchange(session, target.Tg, readCmd, TimeSpan.FromSeconds(5)); + var spad0 = FelicaResponseParser.ParseSpad0(readResponse); + var decryptor = new FeliCaDecryptor(); + var decrypted = decryptor.Decrypt(spad0); + var accessCode = AccessCodeFormatter.ToAccessCodeString(decrypted); + return new FlowResult(target.Kind, target.CardId, accessCode); + } + + Console.WriteLine($"Card detected! TypeA UID: {ToHexString(target.CardId)}"); + var m1AccessCode = TryReadMifareClassicAccessCode(session, target.Tg, target.CardId); + if (m1AccessCode is null) + { + throw new InvalidOperationException("Failed to read Mifare Classic AccessCode: no key matched sector 0."); + } + + return new FlowResult(target.Kind, target.CardId, m1AccessCode); + } + + private byte[] SendInDataExchange(Pn532Session session, byte tg, ReadOnlySpan payloadToTarget, TimeSpan? timeout = null) + { + var pn532Payload = new byte[2 + payloadToTarget.Length]; + pn532Payload[0] = 0x40; pn532Payload[1] = tg; + payloadToTarget.CopyTo(pn532Payload.AsSpan(2)); + var response = ExpectPn532ResponseCode(session.SendCommand(pn532Payload, responseTimeout: timeout ?? TimeSpan.FromSeconds(4)), expectedResponseCode: 0x41); + if (response.Payload.Length < 2 || response.Payload[1] != 0x00) + throw new InvalidOperationException($"InDataExchange failed: 0x{(response.Payload.Length > 1 ? response.Payload[1] : 0):X2}"); + // 替换 System.Range 语法 + return response.Payload.Length == 2 ? Array.Empty() : response.Payload.Skip(2).ToArray(); + } + + private string? TryReadMifareClassicAccessCode(Pn532Session session, byte tg, ReadOnlySpan uid) + { + const byte blockNumber = 2; // sector 0 block 2 + var uid4 = new byte[4]; + Array.Copy(uid.ToArray(), 0, uid4, 0, 4); + + foreach (var keyHex in MifareClassicKeys) + { + var key = FromHexString(keyHex); + if (TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: true, key, uid4) || + TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: false, key, uid4)) + { + var block = ReadMifareBlock(session, tg, blockNumber); + var hex = ToHexString(block); + + return hex.Length <= 20 ? hex : hex.Substring(hex.Length - 20, 20); + } + } + + return null; + } + + public static Pn532FrameParseResult ExpectPn532ResponseCode(Pn532FrameParseResult response, byte expectedResponseCode) + { + if (response.Kind != Pn532FrameKind.Data) throw new InvalidOperationException($"PN532 error: {response.Kind} ({response.Error})."); + if (response.Payload.Length < 1 || response.Payload[0] != expectedResponseCode) + throw new InvalidOperationException($"Expected 0x{expectedResponseCode:X2}, got 0x{(response.Payload.Length > 0 ? response.Payload[0] : 0):X2}"); + return response; + } + + private void ExpectPn532StatusOk(Pn532FrameParseResult response, byte expectedResponseCode) + { + response = ExpectPn532ResponseCode(response, expectedResponseCode); + if (response.Payload.Length >= 2 && response.Payload[1] != 0x00) throw new InvalidOperationException($"Status fail: 0x{response.Payload[1]:X2}"); + } + + private CardTarget WaitForCard(Pn532Session session) + { + var f212 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00 }, TimeSpan.FromMilliseconds(500)); + if (ExtractFeliCaTarget(f212, out var target)) return target; + + var a106 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x00 }, TimeSpan.FromMilliseconds(600)); + if (ExtractTypeATarget(a106, out target)) return target; + + return new CardTarget(CardKind.Null, 0, [0]); + } + + private bool ExtractFeliCaTarget(Pn532FrameParseResult result, out CardTarget target) + { + target = null; + if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 15 && result.Payload[0] == 0x4B && result.Payload[1] > 0) + { + var tg = result.Payload[2]; + var idm = result.Payload.AsSpan(5, 8).ToArray(); + target = new CardTarget(CardKind.Felica, tg, idm); + return true; + } + return false; + } + + private bool ExtractTypeATarget(Pn532FrameParseResult result, out CardTarget target) + { + target = null; + if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 8 && result.Payload[0] == 0x4B && result.Payload[1] > 0) + { + var tg = result.Payload[2]; + var uidLen = result.Payload[6]; + var uidOffset = 7; + if (uidLen > 0 && result.Payload.Length >= uidOffset + uidLen) + { + var uid = result.Payload.AsSpan(uidOffset, uidLen).ToArray(); + target = new CardTarget(CardKind.MifareClassic, tg, uid); + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/NfcAime.Dll/Config.cs b/src/NfcAime.Dll/Config.cs new file mode 100644 index 0000000..8d684d7 --- /dev/null +++ b/src/NfcAime.Dll/Config.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace NfcAime.Dll; + +internal static class Config +{ + internal const string IOSection = "aimeio"; + private const string ConfigFileName = @".\segatools.ini"; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern uint GetPrivateProfileString( + string lpAppName, + string lpKeyName, + string lpDefault, + StringBuilder lpReturnedString, + uint nSize, + string lpFileName); + + static Config() + { + IDmMode = Convert.ToInt32(ReadKey(IOSection, "IDmMode", 1024, "1")); + ReaderCOM = ReadKey(IOSection, "ReaderCOM" , 1024, "COM3"); + ReaderBaud = Convert.ToInt32(ReadKey(IOSection, "Baud" , 1024, "115200")); + } + + public static int IDmMode { get; } + public static string ReaderCOM { get; } + public static int ReaderBaud { get; } + + internal static string ReadKey(string section, string key, uint maxLength, string @default = null) + { + var sb = new StringBuilder((int)maxLength); + GetPrivateProfileString(section, key, @default ?? string.Empty, sb, maxLength, ConfigFileName); + return sb.ToString(); + } +} diff --git a/src/NfcAime.Dll/FeliCaDecryptor.cs b/src/NfcAime.Dll/FeliCaDecryptor.cs new file mode 100644 index 0000000..e1ef625 --- /dev/null +++ b/src/NfcAime.Dll/FeliCaDecryptor.cs @@ -0,0 +1,400 @@ +using System; + +namespace NfcAime.Dll; + +internal sealed class FeliCaDecryptor +{ + private const int IterAdd = 5; + private static readonly byte[][] SBox = new byte[][] + { + new byte[] + { + 0xaf, 0xa8, 0x63, 0xa9, 0x24, 0xc0, 0xf1, 0xbe, 0xe7, 0xb3, 0xac, 0x16, 0x85, 0xb9, 0x11, 0x6f, + 0x10, 0x31, 0x32, 0xae, 0xb1, 0x78, 0x23, 0xd2, 0xa2, 0xca, 0xf9, 0x2b, 0x45, 0xb8, 0x3c, 0x62, + 0x1a, 0x6d, 0x82, 0x8c, 0x00, 0xd9, 0xe2, 0x7a, 0x46, 0x19, 0x2a, 0xcc, 0x13, 0xa7, 0xe4, 0xb7, + 0x84, 0x9b, 0x8e, 0xff, 0xfb, 0x8b, 0x03, 0x89, 0x30, 0x29, 0xd3, 0x87, 0x01, 0xbf, 0x35, 0xda, + 0x59, 0x5c, 0xcd, 0x09, 0x7f, 0x70, 0x41, 0x81, 0x6c, 0x5f, 0x53, 0xc2, 0x72, 0xd4, 0x79, 0x48, + 0x95, 0xb4, 0x5b, 0x39, 0x44, 0x9d, 0xfd, 0x51, 0x71, 0xf6, 0xd7, 0xd5, 0x2d, 0x9f, 0xed, 0x33, + 0xea, 0xaa, 0x5a, 0x17, 0x6a, 0xba, 0xbd, 0xe9, 0x9c, 0xce, 0x4d, 0x7c, 0x18, 0x3b, 0x0d, 0xee, + 0x0c, 0x80, 0xa1, 0x0a, 0x8f, 0xb5, 0x6b, 0xf7, 0x3f, 0xd1, 0x47, 0x25, 0xc6, 0x36, 0xe8, 0x7d, + 0x7b, 0x3e, 0xf3, 0x99, 0x75, 0x05, 0x1c, 0xe0, 0xcf, 0x34, 0xbb, 0x9e, 0xeb, 0x91, 0xad, 0x26, + 0x8d, 0xc7, 0x66, 0x08, 0xe6, 0x58, 0x40, 0x14, 0xa6, 0xc5, 0xfa, 0x12, 0xec, 0xc4, 0xf0, 0x21, + 0x8a, 0x86, 0xd6, 0xe1, 0x06, 0xb6, 0x60, 0x74, 0x2e, 0xe3, 0xfe, 0x27, 0x2f, 0x5e, 0xf4, 0xb2, + 0x68, 0x28, 0x56, 0xef, 0x9a, 0x61, 0x22, 0xa3, 0xc9, 0x0b, 0x02, 0x96, 0x4f, 0xdb, 0xe5, 0x90, + 0x4a, 0x4c, 0x92, 0xcb, 0x98, 0x69, 0xd0, 0x57, 0x64, 0x0e, 0x37, 0x50, 0xab, 0xbc, 0xde, 0x76, + 0x07, 0xc8, 0x3d, 0x49, 0xfc, 0x77, 0x94, 0xf8, 0xc1, 0x97, 0x65, 0x3a, 0x4b, 0x83, 0x6e, 0x73, + 0x88, 0x1e, 0x5d, 0x04, 0x54, 0x67, 0xdf, 0x15, 0xb0, 0xc3, 0x93, 0x2c, 0x38, 0xdd, 0xf5, 0xa4, + 0x55, 0x0f, 0x4e, 0x1d, 0xa0, 0x1f, 0x20, 0xa5, 0xdc, 0x43, 0xf2, 0xd8, 0x7e, 0x52, 0x1b, 0x42 + }, + new byte[] + { + 0x03, 0x4b, 0xb8, 0x97, 0xb7, 0x8f, 0x0d, 0x71, 0xdc, 0x49, 0xa7, 0x90, 0xff, 0xa0, 0x9d, 0xf8, + 0x23, 0x5f, 0x20, 0x6c, 0x11, 0xf9, 0x69, 0x8e, 0xef, 0xcb, 0xf0, 0x14, 0xdf, 0x1b, 0x7a, 0xc6, + 0x36, 0x5b, 0xc5, 0x93, 0xb6, 0x28, 0x35, 0xb2, 0x3b, 0xd1, 0xec, 0xf3, 0x9f, 0x80, 0x64, 0x25, + 0x05, 0xcf, 0x54, 0xaf, 0x46, 0x3f, 0x96, 0x06, 0xe5, 0x10, 0xfb, 0xaa, 0xde, 0x62, 0x41, 0x51, + 0x59, 0xe7, 0x29, 0xb5, 0x4d, 0x3d, 0xf2, 0x37, 0x6a, 0xe8, 0x16, 0x8b, 0x5c, 0xa8, 0x1e, 0x57, + 0xe2, 0x09, 0xeb, 0x32, 0xc9, 0x2e, 0xb4, 0xf1, 0xa4, 0x76, 0xd3, 0xc1, 0xa5, 0x70, 0x60, 0x12, + 0xb1, 0xea, 0x66, 0xe1, 0x4a, 0x92, 0xa1, 0x6d, 0x3a, 0xf4, 0xd5, 0x6f, 0xda, 0xd8, 0x0a, 0x58, + 0x18, 0x63, 0x2b, 0xa3, 0x5d, 0x67, 0x77, 0x6b, 0x39, 0xc3, 0xdb, 0x4c, 0x02, 0xba, 0xce, 0xbb, + 0x55, 0x01, 0x17, 0x85, 0x27, 0xe4, 0x30, 0x72, 0xc8, 0x8d, 0xfc, 0x78, 0x19, 0xb9, 0x43, 0x2d, + 0x08, 0x9b, 0x1a, 0xd9, 0xcd, 0x26, 0xfa, 0x82, 0xcc, 0x7f, 0x75, 0x79, 0x9e, 0x65, 0xfe, 0xed, + 0x91, 0xc7, 0x31, 0xd0, 0x52, 0x2a, 0x48, 0x84, 0x74, 0xdd, 0x7d, 0xe9, 0xae, 0x6e, 0x33, 0x47, + 0xd6, 0x50, 0x0c, 0x53, 0xa6, 0xbd, 0x22, 0xac, 0x8a, 0x04, 0x89, 0x42, 0x40, 0xb3, 0x45, 0x5a, + 0x44, 0xe6, 0xa9, 0x1d, 0xc4, 0x81, 0xe3, 0x15, 0x1f, 0x56, 0x87, 0xd4, 0x3e, 0x0f, 0x95, 0xd7, + 0xe0, 0x94, 0xab, 0x24, 0x68, 0x07, 0xa2, 0x98, 0x7b, 0xee, 0xf5, 0xb0, 0x88, 0xc0, 0x38, 0x9c, + 0x99, 0x5e, 0xad, 0xd2, 0x3c, 0x2c, 0x21, 0xca, 0x8c, 0x7e, 0x1c, 0x61, 0xc2, 0x34, 0x7c, 0x2f, + 0x0b, 0x83, 0x9a, 0x4f, 0xbe, 0x4e, 0xbc, 0x73, 0x13, 0x00, 0xf6, 0x0e, 0x86, 0xbf, 0xfd, 0xf7 + }, + new byte[] + { + 0x41, 0x07, 0x93, 0x8a, 0x40, 0xff, 0x0e, 0x2f, 0x54, 0xfc, 0xac, 0xd4, 0x1e, 0xb0, 0xc8, 0x89, + 0xdf, 0x00, 0x64, 0x4f, 0xf3, 0xcb, 0x2c, 0xc0, 0x8f, 0x6e, 0x17, 0xbf, 0x95, 0x99, 0x2a, 0x0d, + 0x51, 0xad, 0x4b, 0xca, 0xf7, 0x69, 0x55, 0x38, 0x4c, 0xc6, 0x68, 0xc3, 0xa3, 0x1f, 0x83, 0x3e, + 0x0b, 0x70, 0xc7, 0xb2, 0x4e, 0xcf, 0xfe, 0x4d, 0xfd, 0x8b, 0xb4, 0x96, 0xce, 0xf9, 0xf1, 0xd3, + 0x43, 0x60, 0xae, 0xd5, 0xb3, 0xdb, 0x4a, 0xb9, 0xe5, 0xea, 0x6a, 0x48, 0x5d, 0xa7, 0x87, 0xda, + 0xb8, 0x06, 0x2d, 0x94, 0xc1, 0x66, 0x24, 0x26, 0xb1, 0x9a, 0x25, 0x08, 0x5c, 0x59, 0xa6, 0xaf, + 0x27, 0x92, 0x88, 0xf2, 0xed, 0x1b, 0x1c, 0x11, 0xd2, 0x22, 0x16, 0x76, 0x56, 0x67, 0x14, 0x9f, + 0xee, 0x5f, 0xdc, 0x5b, 0xa4, 0x46, 0xdd, 0x10, 0x33, 0x29, 0x7f, 0x5a, 0xc9, 0x97, 0x84, 0x85, + 0x42, 0xbc, 0xab, 0x77, 0x3c, 0xa1, 0xc2, 0xaa, 0x37, 0x49, 0x61, 0x35, 0x9c, 0x86, 0x19, 0x57, + 0x5e, 0xe6, 0xe2, 0xa0, 0x04, 0x6d, 0xd1, 0xa8, 0x9d, 0xec, 0x02, 0xbd, 0xd8, 0x47, 0x62, 0x1a, + 0x05, 0x7d, 0x01, 0x3f, 0x18, 0x6f, 0x75, 0x44, 0xe4, 0x7a, 0xf6, 0xa9, 0xe8, 0x72, 0xf4, 0x6b, + 0xfb, 0x7e, 0xeb, 0x36, 0x52, 0xde, 0x79, 0x23, 0x8d, 0xe7, 0x71, 0x63, 0x98, 0x6c, 0xd9, 0xe9, + 0x3b, 0x13, 0x90, 0x03, 0x78, 0x74, 0x20, 0x81, 0xba, 0x34, 0x32, 0xfa, 0x7b, 0x31, 0xd6, 0x3d, + 0x30, 0x0a, 0xe3, 0x12, 0x8e, 0x0f, 0x1d, 0xbb, 0xa5, 0x73, 0x53, 0x82, 0x80, 0xb5, 0xd7, 0xe1, + 0x3a, 0x2b, 0xd0, 0xe0, 0x21, 0xbe, 0xcd, 0x15, 0x65, 0xb6, 0xef, 0xcc, 0x91, 0xf5, 0x0c, 0xc4, + 0x2e, 0x9e, 0x58, 0xf0, 0xc5, 0x9b, 0x39, 0x09, 0xa2, 0x7c, 0x45, 0xb7, 0x28, 0x8c, 0xf8, 0x50 + }, + new byte[] + { + 0x76, 0xcd, 0x16, 0x7f, 0xe9, 0x6b, 0x95, 0x3a, 0x4e, 0xbb, 0x6e, 0xcc, 0xd2, 0xb3, 0x69, 0xa9, + 0x72, 0xaf, 0xc7, 0x82, 0x38, 0x6f, 0xbc, 0x37, 0xa6, 0x28, 0x36, 0xbd, 0xc3, 0xcf, 0x85, 0x46, + 0x0a, 0x99, 0x63, 0x97, 0x83, 0x56, 0x88, 0xa1, 0x02, 0x64, 0xac, 0x79, 0x55, 0x65, 0xff, 0xc2, + 0xef, 0x7a, 0x59, 0x2b, 0x1c, 0xe0, 0x8c, 0x8f, 0x5a, 0xe4, 0xb4, 0x9f, 0xfc, 0xf7, 0x45, 0x47, + 0x4a, 0xea, 0xc0, 0x0d, 0xeb, 0xc8, 0x78, 0xc9, 0x67, 0x03, 0x05, 0x31, 0x14, 0xde, 0xd3, 0x73, + 0xfa, 0x1f, 0xb6, 0x50, 0x13, 0x04, 0xb7, 0x7b, 0x49, 0x1a, 0xf9, 0x17, 0x4d, 0x4f, 0x86, 0x20, + 0xc1, 0xfd, 0xa0, 0xa5, 0x48, 0xe1, 0x3c, 0x60, 0xf2, 0x0b, 0x30, 0x0f, 0x4c, 0xee, 0x8d, 0x6d, + 0xbe, 0x8e, 0xc6, 0xf6, 0xd8, 0x08, 0x94, 0x1b, 0x2a, 0xb5, 0x9e, 0xa2, 0x93, 0xf5, 0x22, 0xa8, + 0x34, 0x26, 0xa7, 0x7c, 0xec, 0x3f, 0x0e, 0x3b, 0xb2, 0xf4, 0x29, 0xce, 0x2e, 0x42, 0x81, 0x32, + 0x25, 0x5f, 0x66, 0xdb, 0x7d, 0xd7, 0xe7, 0x2d, 0x43, 0x6a, 0x5b, 0x19, 0xad, 0x3e, 0x9b, 0xc5, + 0xab, 0x84, 0xc4, 0x6c, 0x33, 0xda, 0xfb, 0x8a, 0x11, 0x54, 0xfe, 0x57, 0x4b, 0x3d, 0x7e, 0x09, + 0xa3, 0x91, 0x40, 0xbf, 0x68, 0xe5, 0xb9, 0xf3, 0x96, 0xf8, 0x51, 0xca, 0x9a, 0xb0, 0x98, 0xae, + 0x1e, 0x9c, 0x5e, 0xe8, 0x00, 0x39, 0x12, 0xdc, 0x0c, 0x10, 0x8b, 0x5d, 0xd6, 0x2c, 0x27, 0xd5, + 0x61, 0x87, 0x53, 0xba, 0x23, 0x71, 0x2f, 0x1d, 0x74, 0x89, 0x9d, 0xdd, 0x35, 0x15, 0xd0, 0x44, + 0xb1, 0xcb, 0xf0, 0x62, 0x58, 0xa4, 0x77, 0xe3, 0x18, 0xd4, 0xe6, 0x80, 0xd1, 0xaa, 0x90, 0x70, + 0x01, 0x24, 0xf1, 0x21, 0xd9, 0x06, 0x75, 0xb8, 0x41, 0x5c, 0x92, 0xdf, 0xed, 0x07, 0xe2, 0x52 + }, + new byte[] + { + 0xaa, 0x94, 0x99, 0x74, 0x92, 0xd6, 0x1c, 0x46, 0x47, 0x79, 0x2f, 0x56, 0x7a, 0xf7, 0xb5, 0x0a, + 0xc9, 0x06, 0xd7, 0xfa, 0x40, 0x11, 0xa5, 0xff, 0x1f, 0xe8, 0x00, 0x8c, 0xdd, 0xb7, 0x42, 0x6e, + 0x35, 0xf0, 0xbe, 0xc2, 0x1d, 0xca, 0xb6, 0x8e, 0xe2, 0x44, 0x4e, 0x9f, 0x51, 0x81, 0x8b, 0x4c, + 0x5d, 0x59, 0x38, 0xd3, 0x8f, 0x25, 0xb3, 0x09, 0x31, 0xad, 0x0b, 0x6b, 0x0e, 0x50, 0xbb, 0xd8, + 0x96, 0x88, 0xde, 0x49, 0xea, 0xb4, 0x4a, 0x9a, 0x30, 0x6d, 0x2c, 0x0f, 0xcd, 0xf6, 0x7e, 0x4f, + 0x52, 0xd9, 0xfb, 0x28, 0x07, 0x63, 0x19, 0xa4, 0xee, 0x01, 0xf8, 0xeb, 0xd2, 0xf5, 0x3d, 0x0d, + 0x91, 0x5b, 0x67, 0xb8, 0x57, 0xf3, 0x2b, 0xe6, 0x68, 0x16, 0x7b, 0x6f, 0x65, 0xc3, 0x76, 0xe3, + 0x3c, 0x2e, 0x41, 0x3e, 0xcf, 0x54, 0xe1, 0x17, 0xa7, 0x45, 0x2d, 0x5a, 0xfc, 0xc6, 0x82, 0xef, + 0xcb, 0x78, 0xac, 0x34, 0xba, 0x71, 0x27, 0x22, 0xf2, 0x12, 0x3f, 0x1a, 0x24, 0xa8, 0xc1, 0x9e, + 0xc7, 0x18, 0xe4, 0x5f, 0xfe, 0x6c, 0xaf, 0xc8, 0x37, 0xc5, 0x77, 0xbd, 0x02, 0x29, 0x7d, 0xfd, + 0x5e, 0x83, 0xe9, 0xbf, 0x2a, 0x5c, 0x73, 0xe5, 0xda, 0xe7, 0xec, 0x80, 0x6a, 0x03, 0x58, 0x3b, + 0x21, 0x13, 0xd1, 0x53, 0x70, 0x93, 0xbc, 0xa1, 0x10, 0xf4, 0x64, 0x85, 0x87, 0xb0, 0x61, 0xb1, + 0xf1, 0x43, 0x62, 0xc4, 0xf9, 0xa2, 0xae, 0xd4, 0x04, 0x86, 0xdf, 0x26, 0xd0, 0xab, 0x36, 0x1e, + 0x4b, 0xe0, 0x60, 0x90, 0xcc, 0x98, 0x95, 0x3a, 0x9c, 0x9b, 0x15, 0x1b, 0x33, 0x8d, 0x4d, 0x23, + 0x9d, 0x84, 0xce, 0xb2, 0x05, 0xa6, 0x0c, 0xb9, 0x14, 0x48, 0xd5, 0xa3, 0x75, 0x08, 0x7c, 0xed, + 0x7f, 0xdc, 0x89, 0x66, 0xa9, 0xc0, 0x39, 0x72, 0x20, 0x97, 0x8a, 0xa0, 0x55, 0x69, 0xdb, 0x32 + }, + new byte[] + { + 0xde, 0x5a, 0x1b, 0x69, 0x3c, 0x42, 0xa3, 0x51, 0x40, 0x38, 0xf1, 0x17, 0x28, 0x1c, 0xb8, 0xac, + 0xe8, 0x99, 0x45, 0xa0, 0x12, 0x68, 0xce, 0x15, 0xc9, 0x47, 0x6d, 0xc8, 0x46, 0x4e, 0x1e, 0xa6, + 0x11, 0xf8, 0x3e, 0x31, 0xfa, 0xe6, 0x2d, 0xfd, 0x32, 0x14, 0x9c, 0x01, 0xb1, 0xed, 0x57, 0x71, + 0xe3, 0x30, 0xcf, 0x0e, 0xe7, 0xcc, 0x37, 0x4a, 0x78, 0xab, 0x1d, 0x96, 0xea, 0x25, 0x85, 0x20, + 0x49, 0x4d, 0x75, 0x52, 0xee, 0xef, 0xaf, 0x54, 0x33, 0xf9, 0x09, 0x9e, 0x81, 0xe1, 0x73, 0x2a, + 0xf2, 0x3d, 0x22, 0x13, 0x92, 0x8b, 0xf0, 0xc0, 0x84, 0x0d, 0xa8, 0x04, 0xdc, 0x8d, 0xbb, 0x2c, + 0x94, 0x03, 0xf5, 0xd1, 0xd0, 0x2e, 0x5d, 0xeb, 0x5e, 0x62, 0x6f, 0x21, 0xb0, 0x91, 0xfe, 0xcb, + 0x0a, 0xec, 0xb7, 0x2f, 0x34, 0x24, 0xd6, 0xe9, 0x66, 0x82, 0xc6, 0x19, 0x1f, 0xd9, 0xc1, 0xa7, + 0xf6, 0x88, 0x02, 0x87, 0x07, 0xdd, 0xd8, 0x67, 0xc5, 0xb9, 0x29, 0xa4, 0xc2, 0x65, 0xd3, 0x53, + 0xaa, 0x3b, 0x79, 0xe0, 0x72, 0xd7, 0x06, 0x1a, 0x58, 0x93, 0x7e, 0x0c, 0x90, 0x6e, 0x74, 0x0f, + 0x4c, 0x0b, 0xe4, 0x6c, 0x43, 0xd2, 0x41, 0x36, 0xbd, 0xdb, 0x35, 0x59, 0x5f, 0xa9, 0x9f, 0x55, + 0x80, 0x39, 0x8e, 0xf3, 0xd5, 0xe5, 0x63, 0xc3, 0x77, 0xa2, 0x5b, 0x7b, 0x7f, 0xae, 0xba, 0xa1, + 0x10, 0xdf, 0x4b, 0x2b, 0x9b, 0x16, 0xbc, 0x00, 0xc7, 0x18, 0xfb, 0x64, 0xff, 0xf4, 0xf7, 0xbe, + 0x05, 0x56, 0xcd, 0x08, 0x4f, 0x6a, 0x9a, 0x76, 0x7a, 0xb5, 0x95, 0xca, 0x50, 0x7d, 0x7c, 0x6b, + 0xa5, 0xc4, 0x3a, 0x8a, 0x27, 0xda, 0xb6, 0x61, 0x23, 0x86, 0x70, 0x48, 0x97, 0x9d, 0xfc, 0x8f, + 0x5c, 0x44, 0xb2, 0xad, 0x83, 0xbf, 0x26, 0xd4, 0xb3, 0x60, 0x98, 0xe2, 0x3f, 0x8c, 0xb4, 0x89 + }, + new byte[] + { + 0x19, 0x2e, 0x5d, 0x7a, 0x5b, 0x7c, 0x7e, 0x39, 0xef, 0x8b, 0x2f, 0xcc, 0x86, 0x1b, 0xc2, 0x8c, + 0x20, 0xfb, 0x59, 0x5f, 0x21, 0xcd, 0x9f, 0x94, 0xa7, 0x0b, 0xba, 0x0e, 0x47, 0x7b, 0xa5, 0x06, + 0xb7, 0xd8, 0xc9, 0x26, 0xf0, 0x9d, 0x68, 0xf2, 0x5e, 0x12, 0x3a, 0x65, 0xb0, 0x5c, 0xe2, 0x22, + 0x99, 0x82, 0x10, 0xf5, 0x44, 0x50, 0x74, 0xa1, 0xf9, 0x42, 0xa8, 0x25, 0x4d, 0xea, 0x08, 0x3e, + 0x96, 0x92, 0xc4, 0x2d, 0xa2, 0x23, 0x34, 0xa9, 0x45, 0x6b, 0x79, 0x7f, 0xc8, 0x4c, 0x43, 0x67, + 0x29, 0x04, 0xfa, 0xb5, 0x1e, 0x05, 0xc6, 0xb4, 0xaf, 0x1d, 0xbb, 0xf7, 0x30, 0xed, 0xec, 0xab, + 0x40, 0xe0, 0x98, 0xe9, 0x4b, 0x11, 0x87, 0x15, 0x18, 0xbe, 0xee, 0x78, 0xb1, 0x02, 0x57, 0x90, + 0x32, 0x8e, 0x61, 0xcb, 0x3c, 0xe6, 0xd7, 0x75, 0x31, 0xb9, 0x54, 0x36, 0xe1, 0xdd, 0x6c, 0x97, + 0x62, 0xac, 0x41, 0x89, 0x9c, 0x60, 0xf6, 0x63, 0xbc, 0x14, 0x7d, 0x77, 0xd5, 0x09, 0x95, 0xdf, + 0x17, 0x4a, 0x0c, 0x2b, 0x52, 0x38, 0xfe, 0xb6, 0x33, 0xa4, 0x51, 0x73, 0x6d, 0x4f, 0xb3, 0xbf, + 0x71, 0xeb, 0x69, 0x3f, 0x13, 0x00, 0x1c, 0x6f, 0x9b, 0x35, 0xca, 0xa3, 0x2a, 0x76, 0x8d, 0x80, + 0x01, 0x16, 0x03, 0x3d, 0xe5, 0x83, 0x70, 0x8f, 0x5a, 0x58, 0x88, 0xf3, 0xe3, 0x0f, 0xc3, 0x07, + 0x46, 0xff, 0x49, 0xfd, 0xe8, 0xce, 0x85, 0xd1, 0xdc, 0xf1, 0xb8, 0xd3, 0x6e, 0xd4, 0xa0, 0xad, + 0x72, 0xda, 0xe7, 0x3b, 0x84, 0xf8, 0xd2, 0x28, 0xde, 0xc1, 0x1a, 0x64, 0x91, 0x0a, 0xe4, 0xc5, + 0x24, 0x6a, 0xd6, 0x53, 0xc7, 0x81, 0x2c, 0xc0, 0x93, 0xd9, 0x0d, 0xa6, 0xdb, 0xae, 0xcf, 0xf4, + 0x55, 0x66, 0x1f, 0x37, 0xfc, 0xbd, 0x48, 0xaa, 0x4e, 0x56, 0x27, 0x9e, 0xd0, 0x8a, 0xb2, 0x9a + }, + new byte[] + { + 0x54, 0xd9, 0x42, 0x50, 0xb8, 0x74, 0xab, 0x32, 0xa6, 0x24, 0x48, 0x6f, 0xf0, 0x7e, 0xd7, 0x30, + 0xb4, 0x6d, 0x06, 0xa2, 0x88, 0xd6, 0x59, 0x39, 0x4c, 0x2c, 0xaf, 0x6e, 0xa8, 0x2b, 0x66, 0x5c, + 0xb9, 0x1c, 0xe7, 0x53, 0x19, 0x56, 0x8a, 0x11, 0xd8, 0x61, 0x5f, 0x07, 0x03, 0xda, 0xb5, 0xb0, + 0x08, 0xd0, 0x27, 0x45, 0x00, 0xc3, 0xf5, 0xb1, 0x79, 0x6b, 0xc4, 0x5b, 0x21, 0xe2, 0x55, 0x3d, + 0xa0, 0xfe, 0x31, 0x9a, 0x36, 0x0f, 0xec, 0xcb, 0xbb, 0x72, 0x29, 0x1a, 0xf9, 0x76, 0x87, 0x6a, + 0xae, 0xc9, 0x52, 0xb7, 0xeb, 0x7b, 0xf3, 0x05, 0x94, 0x04, 0x01, 0xc6, 0xa1, 0xf4, 0x37, 0xe3, + 0xfb, 0xc1, 0x4f, 0xba, 0x9e, 0x80, 0x28, 0x2f, 0x5a, 0xf6, 0x91, 0x3e, 0xcf, 0x41, 0xad, 0xbe, + 0xb6, 0x33, 0x9b, 0xfc, 0xce, 0xc0, 0x68, 0xee, 0x3f, 0x51, 0xbf, 0x4e, 0xd2, 0x49, 0xa9, 0xf7, + 0x8f, 0x86, 0xc8, 0x2a, 0x67, 0x38, 0xe4, 0x4d, 0xc7, 0x2e, 0x63, 0x7f, 0xef, 0xdf, 0x97, 0xaa, + 0x5d, 0x62, 0x0b, 0x70, 0xd1, 0xe6, 0x13, 0x99, 0x92, 0xf8, 0xd3, 0x12, 0x3c, 0x64, 0x84, 0xa7, + 0x0a, 0xb2, 0x9c, 0xea, 0x1b, 0xcc, 0x5e, 0x43, 0x1e, 0xcd, 0x8d, 0x93, 0xe1, 0x71, 0x3b, 0xf2, + 0xa3, 0x23, 0xe9, 0xc2, 0xdd, 0x98, 0x8b, 0x73, 0x96, 0x57, 0x22, 0x35, 0x0e, 0xa5, 0xac, 0x0c, + 0xd4, 0x9f, 0x09, 0xdb, 0x3a, 0x16, 0x60, 0xe5, 0x89, 0x85, 0x2d, 0x77, 0x1d, 0x81, 0x20, 0xc5, + 0x7a, 0xed, 0xca, 0x14, 0x83, 0xfa, 0x1f, 0x26, 0x18, 0x47, 0x9d, 0x02, 0xe0, 0x58, 0x10, 0xfd, + 0x25, 0x75, 0xbc, 0x46, 0xde, 0xa4, 0x15, 0x40, 0x78, 0x34, 0x4a, 0x17, 0xd5, 0x0d, 0x69, 0x65, + 0xdc, 0x4b, 0x7c, 0x95, 0xe8, 0x8e, 0xff, 0x7d, 0x90, 0x8c, 0x82, 0x6c, 0xb3, 0xbd, 0x44, 0xf1 + }, + new byte[] + { + 0xc3, 0x0b, 0xb8, 0x15, 0x00, 0xa4, 0x3d, 0xd9, 0xfd, 0x16, 0xe9, 0x08, 0x20, 0xe6, 0x8d, 0xc4, + 0x47, 0x38, 0x25, 0x93, 0x5e, 0xaa, 0xec, 0x2a, 0x10, 0x6e, 0x58, 0x95, 0x26, 0x53, 0x04, 0x1e, + 0x92, 0x9e, 0x2f, 0x8b, 0xbd, 0x0a, 0x69, 0xa9, 0x3b, 0x41, 0xf0, 0xd0, 0xda, 0x6a, 0xa1, 0x71, + 0x13, 0x63, 0xef, 0x90, 0x94, 0xb0, 0xeb, 0xe7, 0xdc, 0x73, 0xb9, 0x78, 0x7a, 0xe4, 0xc7, 0x6d, + 0x7f, 0xa3, 0xac, 0x85, 0x50, 0xf3, 0x91, 0xea, 0x36, 0xfa, 0x9b, 0x8e, 0xf7, 0xae, 0xc1, 0x43, + 0x1c, 0xc0, 0xca, 0x8f, 0x4e, 0xa2, 0x31, 0x12, 0x74, 0x2c, 0x9d, 0xf1, 0xad, 0x3f, 0x68, 0xbb, + 0x35, 0x9a, 0x7c, 0x03, 0xfb, 0x09, 0x23, 0x27, 0xd4, 0x4d, 0x17, 0xde, 0x4c, 0xee, 0x76, 0x9c, + 0xa0, 0xe1, 0x75, 0xdb, 0x5b, 0x11, 0xa7, 0x21, 0xaf, 0x02, 0x42, 0x4b, 0x83, 0xd3, 0xce, 0x9f, + 0xcd, 0xcc, 0x30, 0x2b, 0x52, 0x22, 0xcb, 0x59, 0x7d, 0x14, 0xf9, 0xb1, 0x70, 0x54, 0xe0, 0x81, + 0x5a, 0x1d, 0x0e, 0x4a, 0xf4, 0x84, 0xed, 0x96, 0x1a, 0xd7, 0xc9, 0x82, 0x89, 0x2d, 0x87, 0x33, + 0xe2, 0x5c, 0x51, 0x3a, 0x1b, 0xf6, 0xe5, 0xd8, 0x32, 0x0d, 0x5f, 0x72, 0x88, 0xd6, 0xb7, 0xc6, + 0xdd, 0x1f, 0x80, 0x37, 0x64, 0xb5, 0x7b, 0xdf, 0xff, 0xd2, 0x40, 0xfc, 0x60, 0x05, 0xb6, 0x66, + 0x61, 0xc8, 0x28, 0x99, 0xbc, 0xa5, 0x34, 0x19, 0xf8, 0xfe, 0x24, 0x48, 0xd1, 0x0c, 0x55, 0x62, + 0x6b, 0x86, 0xb3, 0xba, 0x8a, 0x2e, 0x6f, 0xa6, 0x67, 0x65, 0x01, 0xe8, 0x18, 0x3e, 0x7e, 0x77, + 0x29, 0x8c, 0xf2, 0x07, 0x6c, 0xa8, 0x79, 0x4f, 0x44, 0x46, 0x06, 0xc2, 0xab, 0x0f, 0xb4, 0xb2, + 0xbe, 0x98, 0xd5, 0x49, 0xe3, 0xf5, 0xcf, 0x97, 0x5d, 0xbf, 0x57, 0x3c, 0x39, 0xc5, 0x45, 0x56 + } + }; + + private static readonly byte[][] SBoxInv = + { + new byte[] + { + 0x24, 0x3c, 0xba, 0x36, 0xe3, 0x85, 0xa4, 0xd0, 0x93, 0x43, 0x73, 0xb9, 0x70, 0x6e, 0xc9, 0xf1, + 0x10, 0x0e, 0x9b, 0x2c, 0x97, 0xe7, 0x0b, 0x63, 0x6c, 0x29, 0x20, 0xfe, 0x86, 0xf3, 0xe1, 0xf5, + 0xf6, 0x9f, 0xb6, 0x16, 0x04, 0x7b, 0x8f, 0xab, 0xb1, 0x39, 0x2a, 0x1b, 0xeb, 0x5c, 0xa8, 0xac, + 0x38, 0x11, 0x12, 0x5f, 0x89, 0x3e, 0x7d, 0xca, 0xec, 0x53, 0xdb, 0x6d, 0x1e, 0xd2, 0x81, 0x78, + 0x96, 0x46, 0xff, 0xf9, 0x54, 0x1c, 0x28, 0x7a, 0x4f, 0xd3, 0xc0, 0xdc, 0xc1, 0x6a, 0xf2, 0xbc, + 0xcb, 0x57, 0xfd, 0x4a, 0xe4, 0xf0, 0xb2, 0xc7, 0x95, 0x40, 0x62, 0x52, 0x41, 0xe2, 0xad, 0x49, + 0xa6, 0xb5, 0x1f, 0x02, 0xc8, 0xda, 0x92, 0xe5, 0xb0, 0xc5, 0x64, 0x76, 0x48, 0x21, 0xde, 0x0f, + 0x45, 0x58, 0x4c, 0xdf, 0xa7, 0x84, 0xcf, 0xd5, 0x15, 0x4e, 0x27, 0x80, 0x6b, 0x7f, 0xfc, 0x44, + 0x71, 0x47, 0x22, 0xdd, 0x30, 0x0c, 0xa1, 0x3b, 0xe0, 0x37, 0xa0, 0x35, 0x23, 0x90, 0x32, 0x74, + 0xbf, 0x8d, 0xc2, 0xea, 0xd6, 0x50, 0xbb, 0xd9, 0xc4, 0x83, 0xb4, 0x31, 0x68, 0x55, 0x8b, 0x5d, + 0xf4, 0x72, 0x18, 0xb7, 0xef, 0xf7, 0x98, 0x2d, 0x01, 0x03, 0x61, 0xcc, 0x0a, 0x8e, 0x13, 0x00, + 0xe8, 0x14, 0xaf, 0x09, 0x51, 0x75, 0xa5, 0x2f, 0x1d, 0x0d, 0x65, 0x8a, 0xcd, 0x66, 0x07, 0x3d, + 0x05, 0xd8, 0x4b, 0xe9, 0x9d, 0x99, 0x7c, 0x91, 0xd1, 0xb8, 0x19, 0xc3, 0x2b, 0x42, 0x69, 0x88, + 0xc6, 0x79, 0x17, 0x3a, 0x4d, 0x5b, 0xa2, 0x5a, 0xfb, 0x25, 0x3f, 0xbd, 0xf8, 0xed, 0xce, 0xe6, + 0x87, 0xa3, 0x26, 0xa9, 0x2e, 0xbe, 0x94, 0x08, 0x7e, 0x67, 0x60, 0x8c, 0x9c, 0x5e, 0x6f, 0xb3, + 0x9e, 0x06, 0xfa, 0x82, 0xae, 0xee, 0x59, 0x77, 0xd7, 0x1a, 0x9a, 0x34, 0xd4, 0x56, 0xaa, 0x33 + }, + new byte[] + { + 0xf9, 0x81, 0x7c, 0x00, 0xb9, 0x30, 0x37, 0xd5, 0x90, 0x51, 0x6e, 0xf0, 0xb2, 0x06, 0xfb, 0xcd, + 0x39, 0x14, 0x5f, 0xf8, 0x1b, 0xc7, 0x4a, 0x82, 0x70, 0x8c, 0x92, 0x1d, 0xea, 0xc3, 0x4e, 0xc8, + 0x12, 0xe6, 0xb6, 0x10, 0xd3, 0x2f, 0x95, 0x84, 0x25, 0x42, 0xa5, 0x72, 0xe5, 0x8f, 0x55, 0xef, + 0x86, 0xa2, 0x53, 0xae, 0xed, 0x26, 0x20, 0x47, 0xde, 0x78, 0x68, 0x28, 0xe4, 0x45, 0xcc, 0x35, + 0xbc, 0x3e, 0xbb, 0x8e, 0xc0, 0xbe, 0x34, 0xaf, 0xa6, 0x09, 0x64, 0x01, 0x7b, 0x44, 0xf5, 0xf3, + 0xb1, 0x3f, 0xa4, 0xb3, 0x32, 0x80, 0xc9, 0x4f, 0x6f, 0x40, 0xbf, 0x21, 0x4c, 0x74, 0xe1, 0x11, + 0x5e, 0xeb, 0x3d, 0x71, 0x2e, 0x9d, 0x62, 0x75, 0xd4, 0x16, 0x48, 0x77, 0x13, 0x67, 0xad, 0x6b, + 0x5d, 0x07, 0x87, 0xf7, 0xa8, 0x9a, 0x59, 0x76, 0x8b, 0x9b, 0x1e, 0xd8, 0xee, 0xaa, 0xe9, 0x99, + 0x2d, 0xc5, 0x97, 0xf1, 0xa7, 0x83, 0xfc, 0xca, 0xdc, 0xba, 0xb8, 0x4b, 0xe8, 0x89, 0x17, 0x05, + 0x0b, 0xa0, 0x65, 0x23, 0xd1, 0xce, 0x36, 0x03, 0xd7, 0xe0, 0xf2, 0x91, 0xdf, 0x0e, 0x9c, 0x2c, + 0x0d, 0x66, 0xd6, 0x73, 0x58, 0x5c, 0xb4, 0x0a, 0x4d, 0xc2, 0x3b, 0xd2, 0xb7, 0xe2, 0xac, 0x33, + 0xdb, 0x60, 0x27, 0xbd, 0x56, 0x43, 0x24, 0x04, 0x02, 0x8d, 0x7d, 0x7f, 0xf6, 0xb5, 0xf4, 0xfd, + 0xdd, 0x5b, 0xec, 0x79, 0xc4, 0x22, 0x1f, 0xa1, 0x88, 0x54, 0xe7, 0x19, 0x98, 0x94, 0x7e, 0x31, + 0xa3, 0x29, 0xe3, 0x5a, 0xcb, 0x6a, 0xb0, 0xcf, 0x6d, 0x93, 0x6c, 0x7a, 0x08, 0xa9, 0x3c, 0x1c, + 0xd0, 0x63, 0x50, 0xc6, 0x85, 0x38, 0xc1, 0x41, 0x49, 0xab, 0x61, 0x52, 0x2a, 0x9f, 0xd9, 0x18, + 0x1a, 0x57, 0x46, 0x2b, 0x69, 0xda, 0xfa, 0xff, 0x0f, 0x15, 0x96, 0x3a, 0x8a, 0xfe, 0x9e, 0x0c + }, + new byte[] + { + 0x11, 0xa2, 0x9a, 0xc3, 0x94, 0xa0, 0x51, 0x01, 0x5b, 0xf7, 0xd1, 0x30, 0xee, 0x1f, 0x06, 0xd5, + 0x77, 0x67, 0xd3, 0xc1, 0x6e, 0xe7, 0x6a, 0x1a, 0xa4, 0x8e, 0x9f, 0x65, 0x66, 0xd6, 0x0c, 0x2d, + 0xc6, 0xe4, 0x69, 0xb7, 0x56, 0x5a, 0x57, 0x60, 0xfc, 0x79, 0x1e, 0xe1, 0x16, 0x52, 0xf0, 0x07, + 0xd0, 0xcd, 0xca, 0x78, 0xc9, 0x8b, 0xb3, 0x88, 0x27, 0xf6, 0xe0, 0xc0, 0x84, 0xcf, 0x2f, 0xa3, + 0x04, 0x00, 0x80, 0x40, 0xa7, 0xfa, 0x75, 0x9d, 0x4b, 0x89, 0x46, 0x22, 0x28, 0x37, 0x34, 0x13, + 0xff, 0x20, 0xb4, 0xda, 0x08, 0x26, 0x6c, 0x8f, 0xf2, 0x5d, 0x7b, 0x73, 0x5c, 0x4c, 0x90, 0x71, + 0x41, 0x8a, 0x9e, 0xbb, 0x12, 0xe8, 0x55, 0x6d, 0x2a, 0x25, 0x4a, 0xaf, 0xbd, 0x95, 0x19, 0xa5, + 0x31, 0xba, 0xad, 0xd9, 0xc5, 0xa6, 0x6b, 0x83, 0xc4, 0xb6, 0xa9, 0xcc, 0xf9, 0xa1, 0xb1, 0x7a, + 0xdc, 0xc7, 0xdb, 0x2e, 0x7e, 0x7f, 0x8d, 0x4e, 0x62, 0x0f, 0x03, 0x39, 0xfd, 0xb8, 0xd4, 0x18, + 0xc2, 0xec, 0x61, 0x02, 0x53, 0x1c, 0x3b, 0x7d, 0xbc, 0x1d, 0x59, 0xf5, 0x8c, 0x98, 0xf1, 0x6f, + 0x93, 0x85, 0xf8, 0x2c, 0x74, 0xd8, 0x5e, 0x4d, 0x97, 0xab, 0x87, 0x82, 0x0a, 0x21, 0x42, 0x5f, + 0x0d, 0x58, 0x33, 0x44, 0x3a, 0xdd, 0xe9, 0xfb, 0x50, 0x47, 0xc8, 0xd7, 0x81, 0x9b, 0xe5, 0x1b, + 0x17, 0x54, 0x86, 0x2b, 0xef, 0xf4, 0x29, 0x32, 0x0e, 0x7c, 0x23, 0x15, 0xeb, 0xe6, 0x3c, 0x35, + 0xe2, 0x96, 0x68, 0x3f, 0x0b, 0x43, 0xce, 0xde, 0x9c, 0xbe, 0x4f, 0x45, 0x72, 0x76, 0xb5, 0x10, + 0xe3, 0xdf, 0x92, 0xd2, 0xa8, 0x48, 0x91, 0xb9, 0xac, 0xbf, 0x49, 0xb2, 0x99, 0x64, 0x70, 0xea, + 0xf3, 0x3e, 0x63, 0x14, 0xae, 0xed, 0xaa, 0x24, 0xfe, 0x3d, 0xcb, 0xb0, 0x09, 0x38, 0x36, 0x05 + }, + new byte[] + { + 0xc4, 0xf0, 0x28, 0x49, 0x55, 0x4a, 0xf5, 0xfd, 0x75, 0xaf, 0x20, 0x69, 0xc8, 0x43, 0x86, 0x6b, + 0xc9, 0xa8, 0xc6, 0x54, 0x4c, 0xdd, 0x02, 0x5b, 0xe8, 0x9b, 0x59, 0x77, 0x34, 0xd7, 0xc0, 0x51, + 0x5f, 0xf3, 0x7e, 0xd4, 0xf1, 0x90, 0x81, 0xce, 0x19, 0x8a, 0x78, 0x33, 0xcd, 0x97, 0x8c, 0xd6, + 0x6a, 0x4b, 0x8f, 0xa4, 0x80, 0xdc, 0x1a, 0x17, 0x14, 0xc5, 0x07, 0x87, 0x66, 0xad, 0x9d, 0x85, + 0xb2, 0xf8, 0x8d, 0x98, 0xdf, 0x3e, 0x1f, 0x3f, 0x64, 0x58, 0x40, 0xac, 0x6c, 0x5c, 0x08, 0x5d, + 0x53, 0xba, 0xff, 0xd2, 0xa9, 0x2c, 0x25, 0xab, 0xe4, 0x32, 0x38, 0x9a, 0xf9, 0xcb, 0xc2, 0x91, + 0x67, 0xd0, 0xe3, 0x22, 0x29, 0x2d, 0x92, 0x48, 0xb4, 0x0e, 0x99, 0x05, 0xa3, 0x6f, 0x0a, 0x15, + 0xef, 0xd5, 0x10, 0x4f, 0xd8, 0xf6, 0x00, 0xe6, 0x46, 0x2b, 0x31, 0x57, 0x83, 0x94, 0xae, 0x03, + 0xeb, 0x8e, 0x13, 0x24, 0xa1, 0x1e, 0x5e, 0xd1, 0x26, 0xd9, 0xa7, 0xca, 0x36, 0x6e, 0x71, 0x37, + 0xee, 0xb1, 0xfa, 0x7c, 0x76, 0x06, 0xb8, 0x23, 0xbe, 0x21, 0xbc, 0x9e, 0xc1, 0xda, 0x7a, 0x3b, + 0x62, 0x27, 0x7b, 0xb0, 0xe5, 0x63, 0x18, 0x82, 0x7f, 0x0f, 0xed, 0xa0, 0x2a, 0x9c, 0xbf, 0x11, + 0xbd, 0xe0, 0x88, 0x0d, 0x3a, 0x79, 0x52, 0x56, 0xf7, 0xb6, 0xd3, 0x09, 0x16, 0x1b, 0x70, 0xb3, + 0x42, 0x60, 0x2f, 0x1c, 0xa2, 0x9f, 0x72, 0x12, 0x45, 0x47, 0xbb, 0xe1, 0x0b, 0x01, 0x8b, 0x1d, + 0xde, 0xec, 0x0c, 0x4e, 0xe9, 0xcf, 0xcc, 0x95, 0x74, 0xf4, 0xa5, 0x93, 0xc7, 0xdb, 0x4d, 0xfb, + 0x35, 0x65, 0xfe, 0xe7, 0x39, 0xb5, 0xea, 0x96, 0xc3, 0x04, 0x41, 0x44, 0x84, 0xfc, 0x6d, 0x30, + 0xe2, 0xf2, 0x68, 0xb7, 0x89, 0x7d, 0x73, 0x3d, 0xb9, 0x5a, 0x50, 0xa6, 0x3c, 0x61, 0xaa, 0x2e + }, + new byte[] + { + 0x1a, 0x59, 0x9c, 0xad, 0xc8, 0xe4, 0x11, 0x54, 0xed, 0x37, 0x0f, 0x3a, 0xe6, 0x5f, 0x3c, 0x4b, + 0xb8, 0x15, 0x89, 0xb1, 0xe8, 0xda, 0x69, 0x77, 0x91, 0x56, 0x8b, 0xdb, 0x06, 0x24, 0xcf, 0x18, + 0xf8, 0xb0, 0x87, 0xdf, 0x8c, 0x35, 0xcb, 0x86, 0x53, 0x9d, 0xa4, 0x66, 0x4a, 0x7a, 0x71, 0x0a, + 0x48, 0x38, 0xff, 0xdc, 0x83, 0x20, 0xce, 0x98, 0x32, 0xf6, 0xd7, 0xaf, 0x70, 0x5e, 0x73, 0x8a, + 0x14, 0x72, 0x1e, 0xc1, 0x29, 0x79, 0x07, 0x08, 0xe9, 0x43, 0x46, 0xd0, 0x2f, 0xde, 0x2a, 0x4f, + 0x3d, 0x2c, 0x50, 0xb3, 0x75, 0xfc, 0x0b, 0x64, 0xae, 0x31, 0x7b, 0x61, 0xa5, 0x30, 0xa0, 0x93, + 0xd2, 0xbe, 0xc2, 0x55, 0xba, 0x6c, 0xf3, 0x62, 0x68, 0xfd, 0xac, 0x3b, 0x95, 0x49, 0x1f, 0x6b, + 0xb4, 0x85, 0xf7, 0xa6, 0x03, 0xec, 0x6e, 0x9a, 0x81, 0x09, 0x0c, 0x6a, 0xee, 0x9e, 0x4e, 0xf0, + 0xab, 0x2d, 0x7e, 0xa1, 0xe1, 0xbb, 0xc9, 0xbc, 0x41, 0xf2, 0xfa, 0x2e, 0x1b, 0xdd, 0x27, 0x34, + 0xd3, 0x60, 0x04, 0xb5, 0x01, 0xd6, 0x40, 0xf9, 0xd5, 0x02, 0x47, 0xd9, 0xd8, 0xe0, 0x8f, 0x2b, + 0xfb, 0xb7, 0xc5, 0xeb, 0x57, 0x16, 0xe5, 0x78, 0x8d, 0xf4, 0x00, 0xcd, 0x82, 0x39, 0xc6, 0x96, + 0xbd, 0xbf, 0xe3, 0x36, 0x45, 0x0e, 0x26, 0x1d, 0x63, 0xe7, 0x84, 0x3e, 0xb6, 0x9b, 0x22, 0xa3, + 0xf5, 0x8e, 0x23, 0x6d, 0xc3, 0x99, 0x7d, 0x90, 0x97, 0x10, 0x25, 0x80, 0xd4, 0x4c, 0xe2, 0x74, + 0xcc, 0xb2, 0x5c, 0x33, 0xc7, 0xea, 0x05, 0x12, 0x3f, 0x51, 0xa8, 0xfe, 0xf1, 0x1c, 0x42, 0xca, + 0xd1, 0x76, 0x28, 0x6f, 0x92, 0xa7, 0x67, 0xa9, 0x19, 0xa2, 0x44, 0x5b, 0xaa, 0xef, 0x58, 0x7f, + 0x21, 0xc0, 0x88, 0x65, 0xb9, 0x5d, 0x4d, 0x0d, 0x5a, 0xc4, 0x13, 0x52, 0x7c, 0x9f, 0x94, 0x17 + }, + new byte[] + { + 0xc7, 0x2b, 0x82, 0x61, 0x5b, 0xd0, 0x96, 0x84, 0xd3, 0x4a, 0x70, 0xa1, 0x9b, 0x59, 0x33, 0x9f, + 0xc0, 0x20, 0x14, 0x53, 0x29, 0x17, 0xc5, 0x0b, 0xc9, 0x7b, 0x97, 0x02, 0x0d, 0x3a, 0x1e, 0x7c, + 0x3f, 0x6b, 0x52, 0xe8, 0x75, 0x3d, 0xf6, 0xe4, 0x0c, 0x8a, 0x4f, 0xc3, 0x5f, 0x26, 0x65, 0x73, + 0x31, 0x23, 0x28, 0x48, 0x74, 0xaa, 0xa7, 0x36, 0x09, 0xb1, 0xe2, 0x91, 0x04, 0x51, 0x22, 0xfc, + 0x08, 0xa6, 0x05, 0xa4, 0xf1, 0x12, 0x1c, 0x19, 0xeb, 0x40, 0x37, 0xc2, 0xa0, 0x41, 0x1d, 0xd4, + 0xdc, 0x07, 0x43, 0x8f, 0x47, 0xaf, 0xd1, 0x2e, 0x98, 0xab, 0x01, 0xba, 0xf0, 0x66, 0x68, 0xac, + 0xf9, 0xe7, 0x69, 0xb6, 0xcb, 0x8d, 0x78, 0x87, 0x15, 0x03, 0xd5, 0xdf, 0xa3, 0x1a, 0x9d, 0x6a, + 0xea, 0x2f, 0x94, 0x4e, 0x9e, 0x42, 0xd7, 0xb8, 0x38, 0x92, 0xd8, 0xbb, 0xde, 0xdd, 0x9a, 0xbc, + 0xb0, 0x4c, 0x79, 0xf4, 0x58, 0x3e, 0xe9, 0x83, 0x81, 0xff, 0xe3, 0x55, 0xfd, 0x5d, 0xb2, 0xef, + 0x9c, 0x6d, 0x54, 0x99, 0x60, 0xda, 0x3b, 0xec, 0xfa, 0x11, 0xd6, 0xc4, 0x2a, 0xed, 0x4b, 0xae, + 0x13, 0xbf, 0xb9, 0x06, 0x8b, 0xe0, 0x1f, 0x7f, 0x5a, 0xad, 0x90, 0x39, 0x0f, 0xf3, 0xbd, 0x46, + 0x6c, 0x2c, 0xf2, 0xf8, 0xfe, 0xd9, 0xe6, 0x72, 0x0e, 0x89, 0xbe, 0x5e, 0xc6, 0xa8, 0xcf, 0xf5, + 0x57, 0x7e, 0x8c, 0xb7, 0xe1, 0x88, 0x7a, 0xc8, 0x1b, 0x18, 0xdb, 0x6f, 0x35, 0xd2, 0x16, 0x32, + 0x64, 0x63, 0xa5, 0x8e, 0xf7, 0xb4, 0x76, 0x95, 0x86, 0x7d, 0xe5, 0xa9, 0x5c, 0x85, 0x00, 0xc1, + 0x93, 0x4d, 0xfb, 0x30, 0xa2, 0xb5, 0x25, 0x34, 0x10, 0x77, 0x3c, 0x67, 0x71, 0x2d, 0x44, 0x45, + 0x56, 0x0a, 0x50, 0xb3, 0xcd, 0x62, 0x80, 0xce, 0x21, 0x49, 0x24, 0xca, 0xee, 0x27, 0x6e, 0xcc + }, + new byte[] + { + 0xa5, 0xb0, 0x6d, 0xb2, 0x51, 0x55, 0x1f, 0xbf, 0x3e, 0x8d, 0xdd, 0x19, 0x92, 0xea, 0x1b, 0xbd, + 0x32, 0x65, 0x29, 0xa4, 0x89, 0x67, 0xb1, 0x90, 0x68, 0x00, 0xda, 0x0d, 0xa6, 0x59, 0x54, 0xf2, + 0x10, 0x14, 0x2f, 0x45, 0xe0, 0x3b, 0x23, 0xfa, 0xd7, 0x50, 0xac, 0x93, 0xe6, 0x43, 0x01, 0x0a, + 0x5c, 0x78, 0x70, 0x98, 0x46, 0xa9, 0x7b, 0xf3, 0x95, 0x07, 0x2a, 0xd3, 0x74, 0xb3, 0x3f, 0xa3, + 0x60, 0x82, 0x39, 0x4e, 0x34, 0x48, 0xc0, 0x1c, 0xf6, 0xc2, 0x91, 0x64, 0x4d, 0x3c, 0xf8, 0x9d, + 0x35, 0x9a, 0x94, 0xe3, 0x7a, 0xf0, 0xf9, 0x6e, 0xb9, 0x12, 0xb8, 0x04, 0x2d, 0x02, 0x28, 0x13, + 0x85, 0x72, 0x80, 0x87, 0xdb, 0x2b, 0xf1, 0x4f, 0x26, 0xa2, 0xe1, 0x49, 0x7e, 0x9c, 0xcc, 0xa7, + 0xb6, 0xa0, 0xd0, 0x9b, 0x36, 0x77, 0xad, 0x8b, 0x6b, 0x4a, 0x03, 0x1d, 0x05, 0x8a, 0x06, 0x4b, + 0xaf, 0xe5, 0x31, 0xb5, 0xd4, 0xc6, 0x0c, 0x66, 0xba, 0x83, 0xfd, 0x09, 0x0f, 0xae, 0x71, 0xb7, + 0x6f, 0xdc, 0x41, 0xe8, 0x17, 0x8e, 0x40, 0x7f, 0x62, 0x30, 0xff, 0xa8, 0x84, 0x25, 0xfb, 0x16, + 0xce, 0x37, 0x44, 0xab, 0x99, 0x1e, 0xeb, 0x18, 0x3a, 0x47, 0xf7, 0x5f, 0x81, 0xcf, 0xed, 0x58, + 0x2c, 0x6c, 0xfe, 0x9e, 0x57, 0x53, 0x97, 0x20, 0xca, 0x79, 0x1a, 0x5a, 0x88, 0xf5, 0x69, 0x9f, + 0xe7, 0xd9, 0x0e, 0xbe, 0x42, 0xdf, 0x56, 0xe4, 0x4c, 0x22, 0xaa, 0x73, 0x0b, 0x15, 0xc5, 0xee, + 0xfc, 0xc7, 0xd6, 0xcb, 0xcd, 0x8c, 0xe2, 0x76, 0x21, 0xe9, 0xd1, 0xec, 0xc8, 0x7d, 0xd8, 0x8f, + 0x61, 0x7c, 0x2e, 0xbc, 0xde, 0xb4, 0x75, 0xd2, 0xc4, 0x63, 0x3d, 0xa1, 0x5e, 0x5d, 0x6a, 0x08, + 0x24, 0xc9, 0x27, 0xbb, 0xef, 0x33, 0x86, 0x5b, 0xd5, 0x38, 0x52, 0x11, 0xf4, 0xc3, 0x96, 0xc1 + }, + new byte[] + { + 0x34, 0x5a, 0xdb, 0x2c, 0x59, 0x57, 0x12, 0x2b, 0x30, 0xc2, 0xa0, 0x92, 0xbf, 0xed, 0xbc, 0x45, + 0xde, 0x27, 0x9b, 0x96, 0xd3, 0xe6, 0xc5, 0xeb, 0xd8, 0x24, 0x4b, 0xa4, 0x21, 0xcc, 0xa8, 0xd6, + 0xce, 0x3c, 0xba, 0xb1, 0x09, 0xe0, 0xd7, 0x32, 0x66, 0x4a, 0x83, 0x1d, 0x19, 0xca, 0x89, 0x67, + 0x0f, 0x42, 0x07, 0x71, 0xe9, 0xbb, 0x44, 0x5e, 0x85, 0x17, 0xc4, 0xae, 0x9c, 0x3f, 0x6b, 0x78, + 0xe7, 0x6d, 0x02, 0xa7, 0xfe, 0x33, 0xe3, 0xd9, 0x0a, 0x7d, 0xea, 0xf1, 0x18, 0x87, 0x7b, 0x62, + 0x03, 0x79, 0x52, 0x23, 0x00, 0x3e, 0x25, 0xb9, 0xdd, 0x16, 0x68, 0x3b, 0x1f, 0x90, 0xa6, 0x2a, + 0xc6, 0x29, 0x91, 0x8a, 0x9d, 0xef, 0x1e, 0x84, 0x76, 0xee, 0x4f, 0x39, 0xfb, 0x11, 0x1b, 0x0b, + 0x93, 0xad, 0x49, 0xb7, 0x05, 0xe1, 0x4d, 0xcb, 0xe8, 0x38, 0xd0, 0x55, 0xf2, 0xf7, 0x0d, 0x8b, + 0x65, 0xcd, 0xfa, 0xd4, 0x9e, 0xc9, 0x81, 0x4e, 0x14, 0xc8, 0x26, 0xb6, 0xf9, 0xaa, 0xf5, 0x80, + 0xf8, 0x6a, 0x98, 0xab, 0x58, 0xf3, 0xb8, 0x8e, 0xb5, 0x97, 0x43, 0x72, 0xa2, 0xda, 0x64, 0xc1, + 0x40, 0x5c, 0x13, 0xb0, 0xe5, 0xbd, 0x08, 0x9f, 0x1c, 0x7e, 0x8f, 0x06, 0xbe, 0x6e, 0x50, 0x1a, + 0x2f, 0x37, 0xa1, 0xfc, 0x10, 0x2e, 0x70, 0x53, 0x04, 0x20, 0x63, 0x48, 0xe2, 0xfd, 0x6f, 0x7a, + 0x75, 0x61, 0xb3, 0x35, 0x3a, 0xcf, 0x5b, 0x88, 0x82, 0x51, 0xd2, 0x47, 0xa5, 0xa9, 0x74, 0x6c, + 0x31, 0x94, 0x7c, 0x9a, 0xc0, 0xec, 0x15, 0x0e, 0x28, 0x01, 0x2d, 0xc3, 0xf0, 0xb4, 0xe4, 0x8d, + 0xdc, 0xac, 0x3d, 0x5f, 0x86, 0xc7, 0x95, 0x22, 0xf4, 0xb2, 0xa3, 0x54, 0x46, 0xd1, 0x77, 0x8c, + 0x0c, 0xff, 0xaf, 0x56, 0x5d, 0x36, 0x69, 0x7f, 0x99, 0x4c, 0xd5, 0x60, 0x73, 0xdf, 0x41, 0xf6 + }, + new byte[] + { + 0x04, 0xda, 0x79, 0x63, 0x1e, 0xbd, 0xea, 0xe3, 0x0b, 0x65, 0x25, 0x01, 0xcd, 0xa9, 0x92, 0xed, + 0x18, 0x75, 0x57, 0x30, 0x89, 0x03, 0x09, 0x6a, 0xdc, 0xc7, 0x98, 0xa4, 0x50, 0x91, 0x1f, 0xb1, + 0x0c, 0x77, 0x85, 0x66, 0xca, 0x12, 0x1c, 0x67, 0xc2, 0xe0, 0x17, 0x83, 0x59, 0x9d, 0xd5, 0x22, + 0x82, 0x56, 0xa8, 0x9f, 0xc6, 0x60, 0x48, 0xb3, 0x11, 0xfc, 0xa3, 0x28, 0xfb, 0x06, 0xdd, 0x5d, + 0xba, 0x29, 0x7a, 0x4f, 0xe8, 0xfe, 0xe9, 0x10, 0xcb, 0xf3, 0x93, 0x7b, 0x6c, 0x69, 0x54, 0xe7, + 0x44, 0xa2, 0x84, 0x1d, 0x8d, 0xce, 0xff, 0xfa, 0x1a, 0x87, 0x90, 0x74, 0xa1, 0xf8, 0x14, 0xaa, + 0xbc, 0xc0, 0xcf, 0x31, 0xb4, 0xd9, 0xbf, 0xd8, 0x5e, 0x26, 0x2d, 0xd0, 0xe4, 0x3f, 0x19, 0xd6, + 0x8c, 0x2f, 0xab, 0x39, 0x58, 0x72, 0x6e, 0xdf, 0x3b, 0xe6, 0x3c, 0xb6, 0x62, 0x88, 0xde, 0x40, + 0xb2, 0x8f, 0x9b, 0x7c, 0x95, 0x43, 0xd1, 0x9e, 0xac, 0x9c, 0xd4, 0x23, 0xe1, 0x0e, 0x4b, 0x53, + 0x33, 0x46, 0x20, 0x13, 0x34, 0x1b, 0x97, 0xf7, 0xf1, 0xc3, 0x61, 0x4a, 0x6f, 0x5a, 0x21, 0x7f, + 0x70, 0x2e, 0x55, 0x41, 0x05, 0xc5, 0xd7, 0x76, 0xe5, 0x27, 0x15, 0xec, 0x42, 0x5c, 0x4d, 0x78, + 0x35, 0x8b, 0xef, 0xd2, 0xee, 0xb5, 0xbe, 0xae, 0x02, 0x3a, 0xd3, 0x5f, 0xc4, 0x24, 0xf0, 0xf9, + 0x51, 0x4e, 0xeb, 0x00, 0x0f, 0xfd, 0xaf, 0x3e, 0xc1, 0x9a, 0x52, 0x86, 0x81, 0x80, 0x7e, 0xf6, + 0x2b, 0xcc, 0xb9, 0x7d, 0x68, 0xf2, 0xad, 0x99, 0xa7, 0x07, 0x2c, 0x73, 0x38, 0xb0, 0x6b, 0xb7, + 0x8e, 0x71, 0xa0, 0xf4, 0x3d, 0xa6, 0x0d, 0x37, 0xdb, 0x0a, 0x47, 0x36, 0x16, 0x96, 0x6d, 0x32, + 0x2a, 0x5b, 0xe2, 0x45, 0x94, 0xf5, 0xa5, 0x4c, 0xc8, 0x8a, 0x49, 0x64, 0xbb, 0x08, 0xc9, 0xb8 + } + }; + + public byte[] Decrypt(ReadOnlySpan spadInput) + { + if (spadInput.Length != 16) + { + throw new ArgumentException("spadInput must be exactly 16 bytes.", nameof(spadInput)); + } + + var spad = spadInput.ToArray(); + var nTables = SBoxInv.Length - 1; + + for (var i = 0; i < spad.Length; i++) + { + spad[i] = SBoxInv[nTables][spad[i]]; + } + + var count = (spad[15] >> 4) + 7; + var table = spad[15] + IterAdd * count; + + for (var iteration = 0; iteration < count; iteration++) + { + table -= IterAdd; + RotateRight(spad, 15, 5); + var tableIndex = table % nTables; + for (var i = 0; i < 15; i++) + { + spad[i] = SBoxInv[tableIndex][spad[i]]; + } + } + + return spad; + } + + private static void RotateRight(byte[] data, int nBytes, int nBits) + { + var prior = data[nBytes - 1]; + for (var i = 0; i < nBytes; i++) + { + var current = data[i]; + data[i] = (byte)(((current >> nBits) | ((prior & ((1 << nBits) - 1)) << (8 - nBits))) & 0xFF); + prior = current; + } + } +} diff --git a/src/NfcAime.Dll/FelicaCommandBuilder.cs b/src/NfcAime.Dll/FelicaCommandBuilder.cs new file mode 100644 index 0000000..c63e261 --- /dev/null +++ b/src/NfcAime.Dll/FelicaCommandBuilder.cs @@ -0,0 +1,38 @@ +using System; + +namespace NfcAime.Dll; + +internal static class FelicaCommandBuilder +{ + internal static byte[] BuildPollingCommand() => new byte[] { 0x06, 0x00, 0x88, 0xB4, 0x01, 0x0F }; + + internal static byte[] BuildReadWithoutEncryptionCommand(ReadOnlySpan idm) + { + if (idm.Length != 8) + { + throw new ArgumentException("IDm must be exactly 8 bytes.", nameof(idm)); + } + + // FeliCa Read Without Encryption: + // Byte 0: Length (16 bytes = 0x10) + // Byte 1: Command Code (0x06) + // Byte 2-9: IDm + // Byte 10: Number of Services (0x01) + // Byte 11-12: Service Code List (0x0B, 0x00 for Service 0x000B, Little Endian) + // Byte 13: Number of Blocks (0x01) + // Byte 14-15: Block List (0x80, 0x00 for Service 0, Block 0) + + byte[] command = new byte[16]; + command[0] = 0x10; + command[1] = 0x06; + idm.CopyTo(command.AsSpan(2, 8)); + command[10] = 0x01; + command[11] = 0x0B; + command[12] = 0x00; + command[13] = 0x01; + command[14] = 0x80; + command[15] = 0x00; + + return command; + } +} diff --git a/src/NfcAime.Dll/FelicaResponseParser.cs b/src/NfcAime.Dll/FelicaResponseParser.cs new file mode 100644 index 0000000..40c6352 --- /dev/null +++ b/src/NfcAime.Dll/FelicaResponseParser.cs @@ -0,0 +1,19 @@ +using System; + +namespace NfcAime.Dll; + +internal static class FelicaResponseParser +{ + private const int Spad0Offset = 13; + private const int Spad0Length = 16; + + internal static byte[] ParseSpad0(ReadOnlySpan response) + { + if (response.Length < Spad0Offset + Spad0Length) + { + throw new ArgumentException($"Response must be at least {Spad0Offset + Spad0Length} bytes.", nameof(response)); + } + + return response.Slice(Spad0Offset, Spad0Length).ToArray(); + } +} diff --git a/src/NfcAime.Dll/FodyWeavers.xml b/src/NfcAime.Dll/FodyWeavers.xml new file mode 100644 index 0000000..a5dcf04 --- /dev/null +++ b/src/NfcAime.Dll/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/NfcAime.Dll/FodyWeavers.xsd b/src/NfcAime.Dll/FodyWeavers.xsd new file mode 100644 index 0000000..dbeb102 --- /dev/null +++ b/src/NfcAime.Dll/FodyWeavers.xsd @@ -0,0 +1,186 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtimes to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtimes names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + Obsolete, use UnmanagedWinX86Assemblies instead + + + + + A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks. + + + + + Obsolete, use UnmanagedWinX64Assemblies instead. + + + + + A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks. + + + + + A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + Obsolete, use UnmanagedWinX86Assemblies instead + + + + + A list of unmanaged X86 (32 bit) assembly names to include, delimited with |. + + + + + Obsolete, use UnmanagedWinX64Assemblies instead + + + + + A list of unmanaged X64 (64 bit) assembly names to include, delimited with |. + + + + + A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/src/NfcAime.Dll/MainDll.cs b/src/NfcAime.Dll/MainDll.cs new file mode 100644 index 0000000..9edbe76 --- /dev/null +++ b/src/NfcAime.Dll/MainDll.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + + +namespace NfcAime.Dll { + public class MainDll { + + public static AimeReader reader; + static byte[] idm = null; + static string accessCode = null; + static AimeReader.CardKind cardKind = AimeReader.CardKind.Null; + [DllImport("kernel32.dll")] + private static extern void AllocConsole(); + //返回API版本 + [DllExport("aime_io_get_api_version", CallingConvention = CallingConvention.StdCall)] + public static ushort GetApiVersion() => 0x0100; + + [DllExport("aime_io_init", CallingConvention = CallingConvention.StdCall)] + public static int Init() + { + AllocConsole(); + + reader = new AimeReader(port: Config.ReaderCOM, baud: Config.ReaderBaud); + return 0; + } + + //卡轮询 + [DllExport("aime_io_nfc_poll", CallingConvention = CallingConvention.StdCall)] + public static int NfcPoll(byte unitNo) + { + Console.WriteLine(">> Polling..."); + (cardKind, idm, accessCode) = reader.ReadCard(); + return 0; + } + + //获取Aime AccessCode + [DllExport("aime_io_nfc_get_aime_id", CallingConvention = CallingConvention.StdCall)] + public static int GetAimeId(byte unitNo, IntPtr luid, nint luidSize) + { + + if (unitNo != 0) + { + return 1; + } + if (accessCode == null) + { + return 1; + } + + if (Config.IDmMode == 1 && cardKind == AimeReader.CardKind.Felica) + { + return 1; + } + if (cardKind == AimeReader.CardKind.Null) + { + return 1; + } + + //将卡号复制到缓存区以传递给游戏 + Marshal.Copy(AccessCodeFormatter.ToAccessCodeBytes(accessCode), 0, luid, (int)luidSize); + Console.WriteLine("# " + cardKind + " !!"); + Console.WriteLine("<< AccessCode"); + return 0; + } + + //获取FeliCa ID + [DllExport("aime_io_nfc_get_felica_id", CallingConvention = CallingConvention.StdCall)] + public static unsafe int GetFelicaId(byte unitNo, ulong* iDM) + { + if (idm == null) + { + return 1; + } + + if (cardKind == AimeReader.CardKind.Felica && Config.IDmMode == 1) //防止传入M1卡 + { + ulong idmValue = 0; + for (var i = 0; i < 8; i++) + { + idmValue = (idmValue << 8) | idm[i]; + } + + *iDM = idmValue; + Console.WriteLine("<< IDm"); + return 0; + } + return 1; + } + + //设置LED颜色 + [DllExport("aime_io_led_set_color", CallingConvention = CallingConvention.StdCall)] + public static void SetLedColour(byte unitNo, byte r, byte g, byte b) + { + } + } +} \ No newline at end of file diff --git a/src/NfcAime.Dll/MiFareHandle.cs b/src/NfcAime.Dll/MiFareHandle.cs new file mode 100644 index 0000000..acccc66 --- /dev/null +++ b/src/NfcAime.Dll/MiFareHandle.cs @@ -0,0 +1,68 @@ +using System; + +namespace NfcAime.Dll; +using NfcAime.Dll.PN532; +public static class MiFareHandle +{ + + public static readonly string[] MifareClassicKeys = + [ + "6090D00632F5","019761AA8082","574343467632","A99164400748","62742819AD7C","CC5075E42BA1", + "B9DF35A0814C","8AF9C718F23D","58CD5C3673CB","FC80E88EB88C","7A3CDAD7C023","30424C029001", + "024E4E44001F","ECBBFA57C6AD","4757698143BD","1D30972E6485","F8526D1A8D6D","1300EC8C7E80", + "F80A65A87FFA","DEB06ED4AF8E","4AD96BF28190","000390014D41","0800F9917CB0","730050555253", + "4146D4A956C4","131157FBB126","E69DD9015A43","337237F254D5","9A8389F32FBF","7B8FB4A7100B", + "C8382A233993","7B304F2A12A6","FC9418BF788B" + ]; + + public static bool TryMifareAuthenticate( + Pn532Session session, + byte tg, + byte blockNumber, + bool keyTypeA, + ReadOnlySpan key, + ReadOnlySpan uid4 + ) + { + var cmd = new byte[2 + 1 + 1 + 6 + 4]; + cmd[0] = 0x40; + cmd[1] = tg; + cmd[2] = keyTypeA ? (byte)0x60 : (byte)0x61; + cmd[3] = blockNumber; + key.CopyTo(cmd.AsSpan(4, 6)); + uid4.CopyTo(cmd.AsSpan(10, 4)); + + var response = session.SendCommand(cmd, TimeSpan.FromMilliseconds(1000)); + if (response.Kind != Pn532FrameKind.Data || response.Payload.Length < 2 || response.Payload[0] != 0x41) + { + return false; + } + + return response.Payload[1] == 0x00; + } + + public static byte[] ReadMifareBlock(Pn532Session session, byte tg, byte blockNumber) + { + var cmd = new byte[] { 0x40, tg, 0x30, blockNumber }; + var response = AimeReader.ExpectPn532ResponseCode(session.SendCommand(cmd, TimeSpan.FromMilliseconds(1200)), expectedResponseCode: 0x41); + if (response.Payload.Length < 2 || response.Payload[1] != 0x00) + { + throw new InvalidOperationException($"Mifare read block failed: status=0x{(response.Payload.Length > 1 ? response.Payload[1] : 0):X2}"); + } + + byte[] data; + if (response.Payload.Length == 2) + { + data = Array.Empty(); + } + else + { + data = new byte[response.Payload.Length - 2]; + Array.Copy(response.Payload, 2, data, 0, data.Length); + } + + var result = new byte[16]; + Array.Copy(data, 0, result, 0, 16); + return result; + } +} \ No newline at end of file diff --git a/src/NfcAime.Dll/NfcAime.Dll.csproj b/src/NfcAime.Dll/NfcAime.Dll.csproj new file mode 100644 index 0000000..6d16c89 --- /dev/null +++ b/src/NfcAime.Dll/NfcAime.Dll.csproj @@ -0,0 +1,160 @@ + + + + + + Debug + AnyCPU + {2ED77BFD-0EF1-49C4-A199-75D89EC3941E} + Library + Properties + NfcAime.Dll + NfcAime.Dll + v4.7.2 + v4.5 + 512 + 13 + disable + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + 8BAB90F1-9613-4660-B2E2-C888DDACEA57 + DllExport.dll + NfcAime.Dll + true + false + Auto + 1 + false + false + false + false + 30000 + 6 + 0 + false + + + 0 + + 0 + 0 + $(MSBuildProjectDirectory)\.\packages\DllExport.1.8.1\ + + + + .\packages\Costura.Fody.6.2.0\lib\netstandard2.0\Costura.dll + + + + + .\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll + + + + + .\packages\System.IO.Ports.10.0.7\lib\net462\System.IO.Ports.dll + + + .\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll + + + + .\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll + + + .\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8.1 + false + 1 + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + + + + + + + + + + + + + + + + $(DllExportDir)packages\DllExport.1.8.1\gcache\$(DllExportMetaXBase)\$(DllExportNamespace)\$(DllExportMetaLibName) + False + False + + + + + + + \ No newline at end of file diff --git a/src/NfcAime.Dll/PN532/CompilerSupport.cs b/src/NfcAime.Dll/PN532/CompilerSupport.cs new file mode 100644 index 0000000..a0187ce --- /dev/null +++ b/src/NfcAime.Dll/PN532/CompilerSupport.cs @@ -0,0 +1,23 @@ +using System; + +namespace System.Runtime.CompilerServices +{ + // 1. 支持 init-only setters (C# 9) + internal static class IsExternalInit {} + // 2. 支持 required 关键字 (C# 11) + [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] + internal class RequiredMemberAttribute : System.Attribute {} + // 3. 支持编译器特性检查 (C# 11+) + [System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal class CompilerFeatureRequiredAttribute : System.Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + public string FeatureName { get; } + } +} + + + diff --git a/src/NfcAime.Dll/PN532/IPn532FrameTransport.cs b/src/NfcAime.Dll/PN532/IPn532FrameTransport.cs new file mode 100644 index 0000000..5464944 --- /dev/null +++ b/src/NfcAime.Dll/PN532/IPn532FrameTransport.cs @@ -0,0 +1,13 @@ + + +using System; + +namespace NfcAime.Dll.PN532 { + public interface IPn532FrameTransport : IDisposable + { + void Open(); + void Close(); + void WriteFrame(ReadOnlySpan frame); + Pn532FrameParseResult ReadFrame(TimeSpan timeout); + } +} diff --git a/src/NfcAime.Dll/PN532/Pn532FrameKind.cs b/src/NfcAime.Dll/PN532/Pn532FrameKind.cs new file mode 100644 index 0000000..ec33d1e --- /dev/null +++ b/src/NfcAime.Dll/PN532/Pn532FrameKind.cs @@ -0,0 +1,10 @@ +namespace NfcAime.Dll.PN532 { + public enum Pn532FrameKind + { + Ack, + Nak, + Data, + ChecksumError, + Invalid + } +} diff --git a/src/NfcAime.Dll/PN532/Pn532FrameParseResult.cs b/src/NfcAime.Dll/PN532/Pn532FrameParseResult.cs new file mode 100644 index 0000000..3aab434 --- /dev/null +++ b/src/NfcAime.Dll/PN532/Pn532FrameParseResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.CompilerServices; + +namespace NfcAime.Dll.PN532 { + public sealed class Pn532FrameParseResult + { + public required Pn532FrameKind Kind { get; init; } + public byte Tfi { get; init; } + public byte[] Payload { get; init; } = Array.Empty(); + public byte[]? RawFrame { get; init; } + public string? Error { get; init; } + } +} + diff --git a/src/NfcAime.Dll/PN532/Pn532FrameParser.cs b/src/NfcAime.Dll/PN532/Pn532FrameParser.cs new file mode 100644 index 0000000..4b24fd9 --- /dev/null +++ b/src/NfcAime.Dll/PN532/Pn532FrameParser.cs @@ -0,0 +1,85 @@ +using System; + +namespace NfcAime.Dll.PN532 { + internal static class Pn532FrameParser + { + public static Pn532FrameParseResult Parse(ReadOnlySpan frame) + { + if (frame.Length < 6) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Frame too short", RawFrame = frame.ToArray() }; + } + + var ack = new ReadOnlySpan(Pn532HsuFrame.AckFrame); + var nak = new ReadOnlySpan(Pn532HsuFrame.NakFrame); + + bool isAck = frame.Length >= 6 && frame.Slice(0, 6).SequenceEqual(ack); + bool isNak = frame.Length >= 6 && frame.Slice(0, 6).SequenceEqual(nak); + + if (!isAck && !isNak) + { + if (frame[0] != Pn532HsuFrame.Preamble || frame[1] != Pn532HsuFrame.StartCode1 || frame[2] != Pn532HsuFrame.StartCode2) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Missing preamble/start codes", RawFrame = frame.ToArray() }; + } + } + + if (isAck) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Ack, RawFrame = frame.ToArray() }; + } + + if (isNak) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Nak, RawFrame = frame.ToArray() }; + } + + var length = frame[3]; + var lcs = frame[4]; + if (((length + lcs) & 0xFF) != 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.ChecksumError, Error = "LCS checksum mismatch", RawFrame = frame.ToArray() }; + } + + if (length == 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Invalid length", RawFrame = frame.ToArray() }; + } + + var expectedLength = 7 + length; + if (frame.Length < expectedLength) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Frame length mismatch", RawFrame = frame.ToArray() }; + } + + var tfi = frame[5]; + var payloadLength = length - 1; + + var payload = payloadLength == 0 ? Array.Empty() : frame.Slice(6, payloadLength).ToArray(); + var dcs = frame[6 + payloadLength]; + var sum = tfi; + for (var index = 0; index < payload.Length; index++) + { + sum += payload[index]; + } + + if (((sum + dcs) & 0xFF) != 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.ChecksumError, Error = "DCS checksum mismatch", RawFrame = frame.ToArray() }; + } + + if (frame[7 + payloadLength] != Pn532HsuFrame.Postamble) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Missing postamble", RawFrame = frame.ToArray() }; + } + + return new Pn532FrameParseResult + { + Kind = Pn532FrameKind.Data, + Tfi = tfi, + Payload = payload, + RawFrame = frame.Slice(0, expectedLength).ToArray() + }; + } + } +} diff --git a/src/NfcAime.Dll/PN532/Pn532HsuFrame.cs b/src/NfcAime.Dll/PN532/Pn532HsuFrame.cs new file mode 100644 index 0000000..5649f96 --- /dev/null +++ b/src/NfcAime.Dll/PN532/Pn532HsuFrame.cs @@ -0,0 +1,63 @@ +using System; + +namespace NfcAime.Dll.PN532 { + internal static class Pn532HsuFrame + { + public const byte Preamble = 0x00; + public const byte StartCode1 = 0x00; + public const byte StartCode2 = 0xFF; + public const byte Postamble = 0x00; + + public const byte HostToPn532Tfi = 0xD4; + public const byte Pn532ToHostTfi = 0xD5; + + public static readonly byte[] AckFrame = + [ + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00 + ]; + + public static readonly byte[] NakFrame = + [ + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00 + ]; + + public static byte[] BuildDataFrame(byte tfi, ReadOnlySpan data) + { + if (data.Length + 1 > byte.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(data), "PN532 HSU frames must fit in 255 bytes."); + } + + var length = (byte)(data.Length + 1); + var lcs = ComputeLcs(length); + var dcs = ComputeDcs(tfi, data); + + var frame = new byte[7 + length]; + frame[0] = Preamble; + frame[1] = StartCode1; + frame[2] = StartCode2; + frame[3] = length; + frame[4] = lcs; + frame[5] = tfi; + data.CopyTo(frame.AsSpan(6)); + frame[6 + data.Length] = dcs; + frame[7 + data.Length] = Postamble; + + return frame; + } + + public static byte ComputeLcs(byte length) + => (byte)((0x100 - length) & 0xFF); + + public static byte ComputeDcs(byte tfi, ReadOnlySpan data) + { + var sum = tfi; + for (var index = 0; index < data.Length; index++) + { + sum += data[index]; + } + + return (byte)((0x100 - (sum & 0xFF)) & 0xFF); + } + } +} diff --git a/src/NfcAime.Dll/PN532/Pn532Session.cs b/src/NfcAime.Dll/PN532/Pn532Session.cs new file mode 100644 index 0000000..f436daf --- /dev/null +++ b/src/NfcAime.Dll/PN532/Pn532Session.cs @@ -0,0 +1,98 @@ +using System; + +namespace NfcAime.Dll.PN532 { + public sealed class Pn532Session : IDisposable + { + private readonly IPn532FrameTransport _transport; + private readonly TimeSpan _timeout; + private readonly int _maxRetries; + + public Pn532Session(IPn532FrameTransport transport, TimeSpan timeout, int maxRetries) + { + _transport = transport; + _timeout = timeout; + _maxRetries = maxRetries; + } + + public void Open() => _transport.Open(); + + public void Close() => _transport.Close(); + + public void Dispose() => _transport.Dispose(); + + public Pn532FrameParseResult SendCommand(ReadOnlySpan payload) + => SendCommand(payload, responseTimeout: null); + + public Pn532FrameParseResult SendCommand(ReadOnlySpan payload, TimeSpan? responseTimeout) + { + var responseReadTimeout = responseTimeout ?? _timeout; + var frame = Pn532HsuFrame.BuildDataFrame(Pn532HsuFrame.HostToPn532Tfi, payload); + var maxAttempts = _maxRetries + 1; + for (var attempt = 0; attempt < maxAttempts; attempt++) + { + _transport.WriteFrame(frame); + var ack = _transport.ReadFrame(_timeout); + if (ack.Kind == Pn532FrameKind.Ack) + { + var response = _transport.ReadFrame(responseReadTimeout); + if (response.Kind == Pn532FrameKind.Data) + { + return response; + } + + if (response.Kind == Pn532FrameKind.ChecksumError) + { + Console.WriteLine($"PN532 checksum error (response); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (response.Kind == Pn532FrameKind.Invalid) + { + if (string.Equals(response.Error, "Read timeout", StringComparison.OrdinalIgnoreCase)) + { + //Console.WriteLine($"PN532 timeout (response after ACK); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + Console.WriteLine($"PN532 read error (response): {response.Error}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + // Unexpected response kind after ACK; retry rather than immediately failing. + Console.WriteLine($"PN532 unexpected response kind {response.Kind}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.Nak) + { + Console.WriteLine($"PN532 NAK received; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.ChecksumError) + { + Console.WriteLine($"PN532 checksum error; retry {attempt + 1} of {maxAttempts}."); + continue; + } + + if (ack.Kind == Pn532FrameKind.Invalid) + { + if (string.Equals(ack.Error, "Read timeout", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"PN532 timeout (waiting for ACK); retry {attempt + 1} of {maxAttempts}."); + continue; + } + + Console.WriteLine($"PN532 read error: {ack.Error}; retry {attempt + 1} of {maxAttempts}."); + continue; + } + } + + return new Pn532FrameParseResult + { + Kind = Pn532FrameKind.Invalid, + Error = "Retry limit exceeded (no valid ACK/response). Check COM port, baud rate, module mode (HSU/UART), and power." + }; + } + } +} diff --git a/src/NfcAime.Dll/PN532/ReplayFrameTransport.cs b/src/NfcAime.Dll/PN532/ReplayFrameTransport.cs new file mode 100644 index 0000000..715cbda --- /dev/null +++ b/src/NfcAime.Dll/PN532/ReplayFrameTransport.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace NfcAime.Dll.PN532 { + internal sealed class ReplayFrameTransport : IPn532FrameTransport + { + private readonly Queue _frames; + private bool _injectCorruption; + + public ReplayFrameTransport(IEnumerable frames, bool injectCorruption) + { + _frames = new Queue(); + foreach (var frame in frames) + { + if (frame == null) continue; + byte[] copy = new byte[frame.Length]; + Array.Copy(frame, copy, frame.Length); + _frames.Enqueue(copy); + } + _injectCorruption = injectCorruption; + } + + public void Open() + { + } + + public void Close() + { + } + + public void WriteFrame(ReadOnlySpan frame) + { + } + + public Pn532FrameParseResult ReadFrame(TimeSpan timeout) + { + if (_frames.Count == 0) + { + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Replay buffer empty" }; + } + + var frame = _frames.Dequeue(); + if (_injectCorruption) + { + _injectCorruption = false; + if (frame.Length > 0) + { + frame[frame.Length - 1] ^= 0xFF; + } + } + + return Pn532FrameParser.Parse(frame); + } + + public void Dispose() + { + _frames.Clear(); + } + } +} diff --git a/src/NfcAime.Dll/PN532/SerialFrameTransport.cs b/src/NfcAime.Dll/PN532/SerialFrameTransport.cs new file mode 100644 index 0000000..74ed5f5 --- /dev/null +++ b/src/NfcAime.Dll/PN532/SerialFrameTransport.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.IO.Ports; +using System.Threading; + +namespace NfcAime.Dll.PN532 { + internal sealed class SerialFrameTransport : IPn532FrameTransport + { + private readonly SerialPort _port; + private readonly TimeSpan _readChunkTimeout; + private readonly int _maxFrameBytes; + private bool _wakeupSent; + + public SerialFrameTransport(string portName, int baud, TimeSpan readChunkTimeout, int maxFrameBytes = 300) + { + var chunkTimeout = readChunkTimeout; + if (chunkTimeout <= TimeSpan.Zero || chunkTimeout > TimeSpan.FromMilliseconds(50)) + { + chunkTimeout = TimeSpan.FromMilliseconds(50); + } + + _port = new SerialPort(portName, baud) + { + ReadTimeout = (int)chunkTimeout.TotalMilliseconds, + WriteTimeout = (int)readChunkTimeout.TotalMilliseconds, + Handshake = Handshake.None, + Parity = Parity.None, + DataBits = 8, + StopBits = StopBits.One, + DtrEnable = false, + RtsEnable = false + }; + _readChunkTimeout = chunkTimeout; + _maxFrameBytes = maxFrameBytes; + } + + public void Open() + { + if (!_port.IsOpen) + { + _port.Open(); + _port.DiscardInBuffer(); + _port.DiscardOutBuffer(); + SendWakeupPattern(); + } + } + + public void Close() + { + if (_port.IsOpen) + { + _port.Close(); + } + } + + public void WriteFrame(ReadOnlySpan frame) + { + if (!_wakeupSent) + { + SendWakeupPattern(); + } + + // Clear any stale data from previous commands or wake-up echos + if (_port.IsOpen && _port.BytesToRead > 0) + { + _port.DiscardInBuffer(); + } + + var buffer = frame.ToArray(); + _port.Write(buffer, 0, buffer.Length); + } + + private void SendWakeupPattern() + { + // Simple HSU wakeup: Just long preamble. + // We avoid sending SAMConfig here to keep the buffer clean for the first real command. + var wakeup = new byte[] { 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + _port.Write(wakeup, 0, wakeup.Length); + Thread.Sleep(50); + + if (_port.IsOpen) + { + _port.DiscardInBuffer(); + } + + _wakeupSent = true; + } + + public Pn532FrameParseResult ReadFrame(TimeSpan timeout) + { + var deadline = Stopwatch.GetTimestamp() + (long)(timeout.TotalSeconds * Stopwatch.Frequency); + var buffer = new byte[_maxFrameBytes]; + var count = 0; + + while (Stopwatch.GetTimestamp() < deadline) + { + try + { + if (_port.BytesToRead == 0) + { + Thread.Sleep(1); + continue; + } + + var next = _port.ReadByte(); + if (next < 0) continue; + if (count < buffer.Length) + { + buffer[count++] = (byte)next; + } + + if (count >= 6) + { + var span = new ReadOnlySpan(buffer, 0, count); + for (int i = 0; i <= span.Length - 6; i++) + { + var window = span.Slice(i); + if (window.Length >= 6) + { + var ack = new ReadOnlySpan(Pn532HsuFrame.AckFrame); + if (window.Slice(0, 6).SequenceEqual(ack)) + { + return Pn532FrameParser.Parse(ack); + } + + var nak = new ReadOnlySpan(Pn532HsuFrame.NakFrame); + if (window.Slice(0, 6).SequenceEqual(nak)) + { + return Pn532FrameParser.Parse(nak); + } + } + + if (window.Length >= 7 && window[0] == Pn532HsuFrame.Preamble && window[1] == Pn532HsuFrame.StartCode1 && window[2] == Pn532HsuFrame.StartCode2) + { + var length = window[3]; + var expectedLength = 7 + length; + if (window.Length >= expectedLength) + { + return Pn532FrameParser.Parse(window.Slice(0, expectedLength)); + } + } + } + } + } + catch (TimeoutException) + { + Thread.Sleep(1); + } + } + + return new Pn532FrameParseResult { Kind = Pn532FrameKind.Invalid, Error = "Read timeout" }; + } + + public void Dispose() + { + Close(); + _port.Dispose(); + } + } +} diff --git a/src/NfcAime.Dll/Properties/AssemblyInfo.cs b/src/NfcAime.Dll/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c8d9bc0 --- /dev/null +++ b/src/NfcAime.Dll/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NfcAime.Dll")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NfcAime.Dll")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2ED77BFD-0EF1-49C4-A199-75D89EC3941E")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/src/NfcAime.Dll/packages.config b/src/NfcAime.Dll/packages.config new file mode 100644 index 0000000..58a11ae --- /dev/null +++ b/src/NfcAime.Dll/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file