Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.IO.Ports;
- using System.Threading;
- using System.Diagnostics;
- using System.IO;
- namespace SpdReaderWriter {
- /// <summary>
- /// Defines Device class, properties, and methods to handle the communication with the device
- /// </summary>
- internal class Device {
- /// <summary>
- /// Defines device's state
- /// </summary>
- public bool IsConnected {
- get => this._isConnected;
- set => this._isConnected = value;
- }
- private bool _isConnected = false;
- public string PortName;
- public int EepromAddress;
- public int SpdSize;
- public object PortLock = _PortLock;
- private static readonly object _PortLock = new object();
- public int BytesToRead {
- get => (int)this.Sp.BytesToRead;
- }
- /// <summary>
- /// Serial port default connection settings
- /// </summary>
- private readonly SerialPort Sp = new SerialPort {
- BaudRate = 115200,
- NewLine = "\r",
- ReadTimeout = 1000,
- WriteTimeout = 1000,
- ReadBufferSize = 32,
- WriteBufferSize = 32,
- //Parity = Parity.None,
- };
- /// <summary>
- /// Initializes the SPD reader/writer device
- /// </summary>
- /// <param name="PortName">Serial port name</param>
- public Device(string PortName) {
- this.PortName = PortName;
- this.Connect();
- }
- /// <summary>
- /// Initializes the SPD reader/writer device
- /// </summary>
- /// <param name = "PortName" >Serial port name</param>
- /// <param name="eepromAddress">EEPROM address on the i2c bus</param>
- public Device(string PortName, int eepromAddress = 0x50) {
- this.PortName = PortName;
- this.EepromAddress = eepromAddress;
- this.Connect();
- }
- /// <summary>
- /// Initializes the SPD reader/writer device
- /// </summary>
- /// <param name="PortName">Serial port name</param>
- /// <param name="eepromAddress">EEPROM address on the device's i2c bus</param>
- /// <param name="SpdSize">Total EEPROM size</param>
- public Device(string PortName, int eepromAddress = 0x50, int SpdSize = Eeprom.DDR4_SPD_SIZE) {
- this.PortName = PortName;
- this.EepromAddress = eepromAddress;
- this.SpdSize = SpdSize;
- this.Connect();
- }
- /// <summary>
- /// Attempts to establish a connection with the SPD reader/writer device
- /// </summary>
- /// <returns><see langword="true" /> if the connection is established</returns>
- public bool Connect() {
- return Connect(this);
- }
- /// <summary>
- /// Disconnects the SPD reader/writer device
- /// </summary>
- /// <returns></returns>
- public bool Disconnect() {
- return Disconnect(this);
- }
- /// <summary>
- /// Tests if the device responds to a test command
- /// </summary>
- /// <returns><see langword="true" /> if the device responds properly</returns>
- public bool Test() {
- return Test(this);
- }
- /// <summary>
- /// Reads a byte from the device
- /// </summary>
- /// <returns>A single byte value received from the device</returns>
- public int ReadByte() {
- return this.Sp.ReadByte();
- }
- /// <summary>
- /// Scans the device for I2C bus devices
- /// </summary>
- /// <param name="startAddress">First address</param>
- /// <param name="endAddress">Last address</param>
- /// <returns>An array of addresses on the device's I2C bus</returns>
- public int[] Scan(int startAddress = 0x50, int endAddress = 0x57) {
- return Scan(this, startAddress, endAddress);
- }
- /// <summary>
- /// Probes specified EEPROM address
- /// </summary>
- /// <returns><see langword="true" /> if EEPROM is detected at the specified address</returns>
- public bool Probe() {
- if (this.EepromAddress != 0) {
- return Probe(this, this.EepromAddress);
- }
- return false;
- }
- /// <summary>
- /// Probes specified EEPROM address
- /// </summary>
- /// <param name="address">EEPROM address</param>
- /// <returns><see langword="true" /> if EEPROM is detected at the specified address</returns>
- public bool Probe(int address) {
- return Probe(this, address);
- }
- /// <summary>
- /// Clears serial port buffers from unneeded data to prevent unwanted behavior and delays
- /// </summary>
- /// <returns><see langword="true" /> when the buffer is empty</returns>
- public bool ClearBuffer() {
- return ClearBuffer(this);
- }
- /// <summary>
- /// Executes commands on the device.
- /// </summary>
- /// <param name="Command">Space separated commands to be executed on the device</param>
- public void ExecuteCommand(string Command) {
- this.ClearBuffer();
- foreach (string cmd in Command.Split(' ')) {
- this.Sp.WriteLine(cmd);
- }
- }
- /// <summary>
- /// Gets a response from the device
- /// </summary>
- /// <returns>A byte array the device has sent</returns>
- public byte[] GetResponse() {
- int retryCount = 0;
- int retryLimit = 1000;
- Queue<byte> _response = new Queue<byte>();
- while (this.BytesToRead < 1) {
- retryCount++;
- if (retryCount == retryLimit) {
- break;
- }
- Wait();
- }
- while (this.BytesToRead != 0) {
- _response.Enqueue((byte)this.ReadByte());
- }
- this.ClearBuffer();
- return _response.ToArray();
- }
- /// <summary>
- /// Gets a single byte from the device's response
- /// </summary>
- /// <param name="offset">A byte offset to get from the device's response</param>
- /// <returns></returns>
- public byte GetResponse(int offset) {
- return GetResponse()[offset];
- }
- /// <summary>
- /// Finds devices connected to computer by sending a test command to every serial port device detected
- /// </summary>
- /// <returns>An array of serial port names the device is connected to</returns>
- public static string[] Find() {
- Stack<string> _result = new Stack<string>();
- foreach (string _portName in SerialPort.GetPortNames()) {
- Device _device = new Device(_portName);
- if (_device.Test()) {
- _result.Push(_portName);
- _device.Disconnect();
- }
- }
- return _result.ToArray();
- }
- /// <summary>
- /// Attempts to establish a connection with the device
- /// </summary>
- /// <param name="device">Device</param>
- /// <returns><see langword="true" /> if the connection is established</returns>
- private static bool Connect(Device device) {
- if (!device.IsConnected) {
- device.Sp.PortName = device.PortName;
- try {
- device.Sp.Open();
- device.IsConnected = true;
- if (device.Test()) {
- return device.IsConnected;
- }
- }
- catch (Exception) {
- return false;
- }
- }
- return device.IsConnected;
- }
- /// <summary>
- /// Disconnect from the device
- /// </summary>
- /// <param name="device">Device instance</param>
- /// <returns><see langword="true" /> once the device is disconnected</returns>
- private static bool Disconnect(Device device) {
- if (device.Sp.IsOpen) {
- device.Sp.Close();
- device.IsConnected = false;
- }
- if (!device.IsConnected) {
- return true;
- }
- return false;
- }
- /// <summary>
- /// Tests if the device is able to communicate
- /// </summary>
- /// <param name="device">Device</param>
- /// <returns><see langword="true" /> if the device responds to a test command</returns>
- private static bool Test(Device device) {
- lock (device.PortLock) {
- if (device.IsConnected) {
- char welcomeString = '!';
- int retryCount = 0;
- int retryLimit = 1000; // If the device didn't respond after this many tries, it's probably dead
- while (device.BytesToRead == 0) {
- device.ExecuteCommand("t"); // This is executed inside the loop, because it takes a few tries to get the device to respond when the device has just been plugged in
- if (retryCount > retryLimit) {
- return false;
- }
- Wait(10);
- retryCount++;
- }
- try {
- if (device.GetResponse(0) == welcomeString) {
- return true;
- }
- }
- catch {
- return false;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Scans for EEPROM addresses on the device's I2C bus
- /// </summary>
- /// <param name="device">Device</param>
- /// <param name="startAddress">First address (cannot be lower than 0x50)</param>
- /// <param name="endAddress">Last address (cannot be higher than 0x57)</param>
- /// <returns>An array of EEPROM addresses present on the device's I2C bus</returns>
- private static int[] Scan(Device device, int startAddress = 0x50, int endAddress = 0x57) {
- Queue<int> addresses = new Queue<int>();
- lock (device.PortLock) {
- if (device.IsConnected) {
- device.ExecuteCommand($"s {startAddress} {endAddress}");
- byte[] response = device.GetResponse();
- foreach (int location in response) {
- if (location != 0) {
- // An accessible EEPROM address was found
- addresses.Enqueue(location);
- }
- }
- }
- }
- return addresses.ToArray();
- }
- /// <summary>
- /// Tests if the EEPROM is present on the device's I2C bus
- /// </summary>
- /// <param name="device">Device instance</param>
- /// <param name="address">EEPROM address</param>
- /// <returns><see langword="true" /> if the address is accessible</returns>
- private static bool Probe(Device device, int address) {
- lock (device.PortLock) {
- if (device.IsConnected) {
- device.ExecuteCommand($"p {address}");
- if (device.GetResponse(0) == address) {
- // Valid address confirmed
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Clears serial port buffers from unneeded data to prevent unwanted behavior and delays
- /// </summary>
- /// <param name="device">Device instance</param>
- /// <returns><see langword="true" /> when the buffer is empty</returns>
- private static bool ClearBuffer(Device device) {
- while (device.Sp.BytesToRead > 0 || device.Sp.BytesToWrite > 0) {
- device.Sp.DiscardInBuffer();
- device.Sp.DiscardOutBuffer();
- Wait();
- }
- return true;
- }
- /// <summary>
- /// Delays execution
- /// </summary>
- private static void Wait(int timeout = 1) {
- Thread.Sleep(timeout);
- }
- }
- /// <summary>
- /// Defines EEPROM class, properties, and methods to handle all EEPROM operations
- /// </summary>
- internal class Eeprom {
- /// <summary>
- /// Defines SPD sizes
- /// </summary>
- public const int DDR3_SPD_SIZE = 0x100; // 256 bytes
- public const int DDR4_SPD_SIZE = 0x200; // 512 bytes
- /// <summary>
- /// Reads a single byte from the EEPROM
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="offset">Byte offset</param>
- /// <returns>Byte value at <paramref name="offset"/> </returns>
- public static byte ReadByte(Device device, int offset) {
- lock (device.PortLock) {
- device.ExecuteCommand($"r {device.EepromAddress} {offset}");
- }
- return device.GetResponse(0);
- }
- /// <summary>
- /// Reads bytes from the EEPROM
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="offset">Byte position to start reading from</param>
- /// <param name="count">Total number of bytes to read from <paramref name="offset" /> </param>
- /// <returns>A byte array containing byte values</returns>
- public static byte[] ReadByte(Device device, int offset, int count = 1) {
- byte[] output = new byte[count];
- for (int i = 0; i < count; i++) {
- output[i] = ReadByte(device, i + offset);
- }
- return output;
- }
- /// <summary>
- /// Write a byte to the EEPROM
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="offset">Byte position</param>
- /// <param name="value">Byte value</param>
- /// <returns><see langword="true" /> if <paramref name="value"/> is written at <paramref name="offset"/> </returns>
- public static bool WriteByte(Device device, int offset, byte value) {
- lock (device.PortLock) {
- device.ExecuteCommand($"w {device.EepromAddress} {offset} {value}");
- if (device.GetResponse(0) == 0) { // The device responds with 0 upon successful write
- return true;
- }
- return false;
- }
- }
- /// <summary>
- /// Write a byte to the EEPROM. The value is written only if differs from the one already saved at the same address.
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="offset">Byte position</param>
- /// <param name="value">Byte value</param>
- /// <returns><see langword="true" /> if byte read at <paramref name="offset"/> matches <paramref name="value"/> value</returns>
- public static bool UpdateByte(Device device, int offset, byte value) {
- return VerifyByte(device, offset, value) || WriteByte(device, offset, value);
- }
- /// <summary>
- /// Verifies if the offset content matches the input specified
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="offset">Byte position</param>
- /// <param name="value">Byte value</param>
- /// <returns><see langword="true" /> if byte at <paramref name="offset"/> matches <paramref name="value"/> value</returns>
- public static bool VerifyByte(Device device, int offset, byte value) {
- return ReadByte(device, offset) == value;
- }
- /// <summary>
- /// Enables software write protection on the specified EEPROM block
- /// </summary>
- /// <param name="device">SPD reader/writer device instance</param>
- /// <param name="block">Block number to be write protected</param>
- /// <returns><see langword="true" /> when the write protection has been enabled</returns>
- public static bool SetWriteProtection(Device device, int block) {
- lock (device.PortLock) {
- //block = (block > 3) ? 0 : block; // Let the device handle incorrect block numbers
- device.ExecuteCommand($"e {block}"); // WP commands don't use address, all devices on the bus will respond simultaneously
- if (device.GetResponse(0) == 0) {
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// Enables software write protection on all 4 EEPROM blocks
- /// </summary>
- /// <param name="device">Device instance</param>
- /// <returns><see langword="true" /> when the write protection has been enabled on all blocks</returns>
- public static bool SetWriteProtection(Device device) {
- for (int i = 0; i <= 3; i++) {
- if (!SetWriteProtection(device, i)) {
- return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Clears EEPROM write protection
- /// </summary>
- /// <param name="device">Device instance</param>
- /// <returns><see langword="true" /> if the write protection has been disabled</returns>
- public static bool ClearWriteProtection(Device device) {
- lock (device.PortLock) {
- device.ExecuteCommand("c"); // WP commands don't use address, all devices on the bus will respond simultaneously
- if (device.GetResponse(0) == 0) {
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// Prints bytes in a grid pattern
- /// </summary>
- /// <param name="pos">Byte offset</param>
- /// <param name="b">Byte value</param>
- /// <param name="bpr">Bytes per row</param>
- /// <param name="showOffset">Show or hide offset at the beginning of new line</param>
- public static void DisplayByte(int pos, byte b, int bpr = 16, bool showOffset = true, bool color = true) {
- ConsoleColor _defaultForeColor = Console.ForegroundColor; // Text color
- ConsoleColor _defaultBackgroundColor = Console.BackgroundColor;
- byte[] hexRow = new byte[bpr];
- string[] strRow = new string[bpr];
- // Top row (offsets)
- if (pos == 0 && showOffset) {
- Console.Write(" "); // Indentation
- for (int i = 0; i < bpr; i++) {
- Console.Write($"{i:x2} ");
- }
- }
- // Contents
- if (pos % bpr == 0) {
- Console.Write(Environment.NewLine);
- if (showOffset) {
- Console.Write("{0:x3}: ", pos); // Row offsets
- }
- }
- if (color) {
- Console.BackgroundColor = ConsoleColor.Black;
- Console.ForegroundColor = (ConsoleColor)((b >> 4) == 0 ? (int)ConsoleColor.DarkGray : (b >> 4)); // Set color (bitshift right by 4 bits to use first 8 darker shade colors, black is replaced with dark grey)
- }
- Console.Write($"{b:X2}"); // Bytes
- if (pos % bpr != bpr - 1) {
- Console.Write(" ");
- }
- Console.ForegroundColor = _defaultForeColor; // Reset foreground (text) color
- Console.BackgroundColor = _defaultBackgroundColor; // Reset background color
- }
- /// <summary>
- /// Calculates CRC16/XMODEM checksum
- /// </summary>
- /// <param name="input">A byte array to be checked</param>
- /// <returns>A calculated checksum</returns>
- public static ushort Crc16(byte[] input) {
- ushort[] table = new ushort[256];
- ushort initialValue = 0;
- ushort crc = initialValue;
- for (int i = 0; i < table.Length; ++i) {
- ushort temp = 0;
- ushort a = (ushort)(i << 8);
- for (int j = 0; j < 8; ++j) {
- if (((temp ^ a) & 0x8000) != 0) {
- temp = (ushort)((temp << 1) ^ 0x1021);
- }
- else {
- temp <<= 1;
- }
- a <<= 1;
- }
- table[i] = temp;
- }
- for (int i = 0; i < input.Length; ++i) {
- crc = (ushort)((crc << 8) ^ table[((crc >> 8) ^ (0xff & input[i]))]);
- }
- return crc;
- }
- }
- class Program {
- static void Main(string[] args) {
- Welcome();
- if (args.Length > 0) {
- #if DEBUG
- // Display command line arguments in console title
- if (Debugger.IsAttached) {
- Console.Title = ($"{AppDomain.CurrentDomain.FriendlyName} ");
- foreach (string cmd in args) {
- Console.Title += ($"{cmd} ");
- }
- }
- #endif
- ParseCommand(args);
- }
- else {
- ShowHelp();
- }
- #if DEBUG
- // Wait for input to prevent application from closing automatically when debugging
- if (Debugger.IsAttached) {
- Console.WriteLine("\nPress [enter] to quit.\n");
- Console.ReadLine();
- }
- #endif
- }
- static void Welcome() {
- string[] header = {
- "Welcome to DDR4 SPD reader/writer!",
- "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
- "Commercial use is strictly prohibited!",
- ""
- };
- foreach (string line in header) {
- Console.WriteLine(line);
- }
- }
- static void ShowHelp() {
- string[] help = {
- "Command line parameters:",
- "",
- "{0} /help",
- "{0} /find",
- "{0} /scan <PORT>",
- "{0} /read <PORT> <ADDRESS#> <filepath> /silent",
- "{0} /write <PORT> <ADDRESS#> <FILEPATH> /silent",
- "{0} /writeforce <PORT> <ADDRESS#> <FILEPATH> /silent",
- "{0} /enablewriteprotection <PORT>",
- "{0} /enablewriteprotection <PORT> <block#>",
- "{0} /disablewriteprotection <PORT>",
- "",
- "Parameters in CAPS are mandatory!",
- "Parameter <filepath> is optional when /read switch is used, output will be printed to console only.",
- "Switch /silent is optional, progress won't be shown with this switch.",
- "For additional help, visit: https://forums.evga.com/m3053544.aspx"
- };
- foreach (string line in help) {
- Console.WriteLine(line, AppDomain.CurrentDomain.FriendlyName);
- }
- }
- static void ParseCommand(string[] args) {
- string mode = args[0];
- if (mode == "/help") {
- ShowHelp();
- return;
- }
- try {
- // Find
- if (mode == "/find") {
- // Find
- string[] devices = Device.Find();
- if (devices.Length > 0) {
- foreach (string portName in devices) {
- Console.WriteLine($"Found Device on Serial Port: {portName}\n");
- }
- }
- else {
- throw new Exception("Nothing found");
- }
- return;
- }
- // Other functions that require additional parameters
- if (mode != "/find" && args.Length >= 2) {
- // Init
- string portName = args[1];
- if (!portName.StartsWith("COM")) {
- throw new Exception("Port name should start with \"COM\" followed by a number.");
- }
- Device reader = new Device(portName);
- if (!reader.Connect()) {
- throw new Exception($"Could not connect to the device on port {portName}.");
- }
- if (!reader.Test()) {
- throw new Exception($"The device on port {portName} does not respond.");
- }
- // Scan
- if (mode == "/scan" && args.Length == 2) {
- int[] addresses = reader.Scan();
- if (addresses.Length == 0) {
- throw new Exception("No EEPROM devices found.");
- }
- foreach (int location in addresses) {
- Console.WriteLine($"Found EEPROM at address: {location}");
- }
- reader.Disconnect();
- return;
- }
- // Turn on write protection
- if (mode == "/enablewriteprotection") {
- Stack<int> block = new Stack<int>();
- if (args.Length == 3) { // Block # was specified
- try {
- block.Push(Int32.Parse(args[2]));
- }
- catch {
- throw new Exception("Block number should be specified in decimal notation.");
- }
- }
- else { // No block number specified, protect all
- for (int i = 3; i != -1; i--) { // Push from 3 to 0, so that the stack pops in correct numeric order
- block.Push(i);
- }
- }
- while (block.Count > 0) {
- int blocknumber = block.Pop();
- if (Eeprom.SetWriteProtection(reader, blocknumber)) {
- Console.WriteLine($"Block {blocknumber} is now read-only");
- }
- else {
- throw new Exception($"Unable to set write protection for block {blocknumber}. Either SA0 is not connected to HV, or the block is already read-only.");
- }
- }
- return;
- }
- // Disable write protection
- if (mode == "/disablewriteprotection") {
- if (Eeprom.ClearWriteProtection(reader)) {
- Console.WriteLine("Write protection successfully disabled.");
- }
- else {
- throw new Exception("Unable to clear write protection");
- }
- return;
- }
- int address;
- try {
- address = Int32.Parse(args[2]);
- }
- catch {
- throw new Exception("EEPROM address should be specified in decimal notation.");
- }
- reader.EepromAddress = address;
- reader.SpdSize = Eeprom.DDR4_SPD_SIZE;
- if (!reader.Probe()) {
- throw new Exception($"EEPROM is not present at address {reader.EepromAddress}.");
- }
- string filePath = (args.Length >= 4) ? args[3] : "";
- bool silent = (args.Length >= 5 && args[4] == "/silent") ? true : false;
- // Read SPD
- if (mode == "/read") {
- Console.Write($"Reading EEPROM at address {reader.EepromAddress}");
- if (filePath != "") {
- Console.WriteLine($" to {filePath}");
- }
- Console.WriteLine("\n");
- int startTick = Environment.TickCount;
- byte[] spdDump = Eeprom.ReadByte(reader, 0, reader.SpdSize);
- for (int i = 0; i < spdDump.Length; i++) {
- if (!silent) {
- Eeprom.DisplayByte(i, spdDump[i], 16);
- }
- }
- Console.Write("\n\nRead {0} {1} from EEPROM at address {2} on port {3} in {4} ms",
- spdDump.Length,
- (spdDump.Length > 1) ? "bytes" : "byte",
- reader.EepromAddress,
- reader.PortName,
- Environment.TickCount - startTick
- );
- if (filePath != "") {
- try {
- File.WriteAllBytes(filePath, spdDump);
- }
- catch {
- throw new Exception($"Unable to write to {filePath}");
- }
- Console.Write($" to file \"{filePath}\"");
- }
- reader.Disconnect();
- return;
- }
- // Write SPD to EEPROM
- if (mode.StartsWith("/write")) {
- if (filePath.Length < 1) {
- throw new Exception("File path is mandatory for write mode.");
- }
- if (!File.Exists(filePath)) {
- throw new Exception($"File \"{filePath}\" not found.");
- }
- byte[] inputFile;
- try {
- inputFile = File.ReadAllBytes(filePath);
- }
- catch {
- throw new Exception($"Unable to read {filePath}");
- }
- Console.WriteLine(
- "Writing \"{0}\" ({1} {2}) to EEPROM at address {3}\n",
- filePath,
- inputFile.Length,
- (inputFile.Length > 1) ? "bytes" : "byte",
- reader.EepromAddress);
- if (inputFile.Length > reader.SpdSize) {
- throw new Exception($"File \"{filePath}\" is larger than {reader.SpdSize} bytes.");
- }
- int bytesWritten = 0;
- int startTick = Environment.TickCount;
- byte b;
- for (int i = 0; i != inputFile.Length; i++) {
- b = inputFile[i];
- bool writeResult = mode.EndsWith("force")
- ? Eeprom.WriteByte(reader, i, inputFile[i])
- : Eeprom.UpdateByte(reader, i, inputFile[i]);
- if (!writeResult) {
- throw new Exception($"Could not write byte {i} to EEPROM at address {reader.EepromAddress} on port {reader.PortName}.");
- }
- bytesWritten++;
- if (!silent) {
- Eeprom.DisplayByte(i, b);
- }
- }
- reader.Disconnect();
- Console.WriteLine(
- "\n\nWritten {0} {1} to EEPROM at address {2} on port {3} in {4} ms",
- bytesWritten,
- (bytesWritten > 1) ? "bytes" : "byte",
- reader.EepromAddress,
- reader.PortName,
- Environment.TickCount - startTick);
- return;
- }
- }
- }
- catch (Exception e) {
- //Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine(e.Message);
- //Console.ForegroundColor = ConsoleColor.Gray;
- return;
- }
- Console.WriteLine("Unknown command line parameters.\n");
- ShowHelp();
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement