using System.Runtime.InteropServices; namespace ChangeT50.Adapter; /// /// Native Change.dll ABI assumptions reconstructed from the supplied vendor document. /// public static class ChangeAbi { public const ushort ImageFileMachineI386 = 0x014c; public const int NativePack = 8; public static readonly string[] RequiredExports = [ "CapDBConnect", "CapDBDiscon", "CapDownBlacklist", "CapDownBlacklistCount", "CapDownCustomerInfo", "CapGetCustomerByNum", "CapGetCustomerCount", "CapGetCustomerList", "CapGetDepCount", "CapGetDepList", "CapGetNBCardInfo", "CapNBQueryCard", "CapSetNBCardInfo", "CapUpload", "CloseCom", "OpenCom" ]; public static IReadOnlyList ValidateManagedLayouts() { var errors = new List(); CheckSize(68, errors); CheckSize(120, errors); CheckSize(320, errors); CheckSize(268, errors); var psamIdOffset = Marshal.OffsetOf(nameof(UploadInfoNative.PsamId)).ToInt32(); if (psamIdOffset != 96) { errors.Add($"UploadInfoNative.PsamId offset is {psamIdOffset}; expected 96."); } return errors; } public static void EnsureNativeLoadEnvironment() { if (!OperatingSystem.IsWindows()) { throw new PlatformNotSupportedException( "Change.dll is a Windows PE32 library. Native loading must run on Windows."); } if (RuntimeInformation.ProcessArchitecture != Architecture.X86) { throw new BadImageFormatException( $"Change.dll requires an x86 process; current process is {RuntimeInformation.ProcessArchitecture}."); } var layoutErrors = ValidateManagedLayouts(); if (layoutErrors.Count != 0) { throw new TypeLoadException(string.Join(" ", layoutErrors)); } } private static void CheckSize(int expected, ICollection errors) where T : struct { var actual = Marshal.SizeOf(); if (actual != expected) { errors.Add($"{typeof(T).Name} size is {actual}; expected {expected}."); } } } [StructLayout(LayoutKind.Sequential, Pack = ChangeAbi.NativePack)] public unsafe struct CustomerInfoNative { public int CardClass; public int CustomerId; public int CardNumber; public int CardSerialNumber; public int Status; public int SubType; public int TotalAmountCents; public int MainWalletBalanceCents; public int MainWalletOperationCount; public int SubsidyWalletBalanceCents; public int SubsidyWalletOperationCount; public fixed byte CardAsn[24]; } [StructLayout(LayoutKind.Sequential, Pack = ChangeAbi.NativePack)] public unsafe struct UploadInfoNative { public int TerminalId; public int CustomerId; public int CardNumber; public int CardSerialNumber; public int CardClass; public fixed byte CardAsn[24]; public int TotalAmountCents; public int ManagementFeeCents; public int WalletNumber; public int OperationAmountCents; public int RemainingBalanceCents; public int OperationCount; public fixed byte TradeTime[24]; public long PsamId; public int PsamTradeNumber; public int Tac; public int Result; } [StructLayout(LayoutKind.Sequential, Pack = ChangeAbi.NativePack)] public unsafe struct DownInfoNative { public fixed byte Department[200]; public fixed byte StudentNumber[60]; public fixed byte Name[40]; public int Sex; public int CustomerId; public int CardNumber; public int Status; public int CardSerialNumber; } [StructLayout(LayoutKind.Sequential, Pack = ChangeAbi.NativePack)] public unsafe struct DownDepartmentInfoNative { public fixed byte DepartmentCode1[6]; public fixed byte DepartmentCode2[6]; public fixed byte DepartmentCode3[8]; public fixed byte DepartmentCode4[8]; public fixed byte DepartmentName1[60]; public fixed byte DepartmentName2[60]; public fixed byte DepartmentName3[60]; public fixed byte DepartmentName4[60]; }