using System.Runtime.InteropServices; using System.Security.Cryptography; using ChangeT50.Adapter; return CommandLine.Run(args); internal static class CommandLine { public static int Run(string[] args) { if (args.Length == 0 || args[0] is "-h" or "--help" or "help") { PrintUsage(); return args.Length == 0 ? 1 : 0; } var command = args[0]; var dllPath = GetOption(args, "--dll") ?? "Change.dll"; try { return command switch { "self-test" => SelfTest(dllPath), "load" => LoadOnly(dllPath), "probe-reader" => ProbeReader(dllPath), _ => UnknownCommand(command) }; } catch (Exception exception) { Console.Error.WriteLine($"FAIL: {exception.GetType().Name}: {exception.Message}"); return 2; } } private static int SelfTest(string dllPath) { var layoutErrors = ChangeAbi.ValidateManagedLayouts(); if (layoutErrors.Count != 0) { foreach (var error in layoutErrors) { Console.Error.WriteLine($"ABI: {error}"); } return 2; } var fullPath = Path.GetFullPath(dllPath); if (!File.Exists(fullPath)) { throw new FileNotFoundException("Change.dll was not found.", fullPath); } var inspection = PortableExecutableInspector.Inspect(fullPath); var missingExports = ChangeAbi.RequiredExports .Except(inspection.ExportNames, StringComparer.Ordinal) .Order(StringComparer.Ordinal) .ToArray(); Console.WriteLine("Managed ABI: PASS (Pack=8; CustomerInfo=68; UPLOADINFO=120; psamID offset=96)."); Console.WriteLine($"DLL SHA-256: {ComputeSha256(fullPath)}"); Console.WriteLine($"PE machine: 0x{inspection.Machine:x4} ({MachineName(inspection.Machine)})."); Console.WriteLine($"Exported module name: {inspection.ExportedModuleName}"); Console.WriteLine($"Exports found: {inspection.ExportNames.Count}; required: {ChangeAbi.RequiredExports.Length}."); if (inspection.Machine != ChangeAbi.ImageFileMachineI386 || !inspection.IsPe32) { Console.Error.WriteLine("FAIL: The supplied Change.dll is not PE32/x86."); return 2; } if (missingExports.Length != 0) { Console.Error.WriteLine($"FAIL: Missing exports: {string.Join(", ", missingExports)}"); return 2; } Console.WriteLine("PASS: Real Change.dll structure and all required exports match the .NET wrapper contract."); Console.WriteLine("Safe self-test only: the DLL was inspected, not loaded or executed."); return 0; } private static int LoadOnly(string dllPath) { using var library = new ChangeNativeLibrary(dllPath); Console.WriteLine("PASS: Change.dll loaded in a Windows x86 process and all 16 exports were bound."); Console.WriteLine("Safe load-only mode: no reader, card, WebService, debit or upload operation was invoked."); return 0; } private static int ProbeReader(string dllPath) { using var library = new ChangeNativeLibrary(dllPath); var openResult = library.OpenReader(); Console.WriteLine($"OpenCom returned {openResult}."); if (openResult != 0) { Console.Error.WriteLine("BLOCKED: reader connection did not open; check driver, registration, reader and PSAM."); return 3; } try { var queryResult = library.QueryCard(out _); Console.WriteLine($"CapNBQueryCard returned {queryResult}."); if (queryResult != 0) { Console.Error.WriteLine("BLOCKED: no test card was detected, or the card query failed."); return 3; } var readResult = library.ReadCard(out _); Console.WriteLine($"CapGetNBCardInfo returned {readResult}."); if (readResult != 0) { Console.Error.WriteLine("FAIL: a card was detected but its information could not be read."); return 2; } Console.WriteLine("PASS: reader opened and test card read succeeded."); Console.WriteLine("Account, card, balance and identity values are intentionally not printed."); return 0; } finally { library.CloseReader(); Console.WriteLine("Reader connection closed."); } } private static string? GetOption(IReadOnlyList args, string option) { for (var index = 1; index < args.Count; index++) { if (args[index] != option) { continue; } if (index + 1 >= args.Count) { throw new ArgumentException($"Option {option} requires a value."); } return args[index + 1]; } return null; } private static string ComputeSha256(string path) { using var stream = File.OpenRead(path); return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); } private static string MachineName(ushort machine) => machine switch { ChangeAbi.ImageFileMachineI386 => "x86/i386", 0x8664 => "x64", 0xaa64 => "ARM64", _ => "unknown" }; private static int UnknownCommand(string command) { Console.Error.WriteLine($"Unknown command: {command}"); PrintUsage(); return 1; } private static void PrintUsage() { Console.WriteLine("Change T5.0 .NET adapter test tool"); Console.WriteLine(); Console.WriteLine(" self-test --dll Inspect the real DLL and verify ABI/exports on any OS."); Console.WriteLine(" load --dll Load and bind the DLL in a Windows x86 process only."); Console.WriteLine(" probe-reader --dll Read-only reader/card probe; never debits or uploads."); } }