diff --git a/EfsTools/CommandLineOptions/NvItemFormat.cs b/EfsTools/CommandLineOptions/NvItemFormat.cs new file mode 100644 index 000000000..f2631f4d4 --- /dev/null +++ b/EfsTools/CommandLineOptions/NvItemFormat.cs @@ -0,0 +1,9 @@ +namespace EfsTools.CommandLineOptions +{ + internal enum NvItemFormat + { + Raw, + Hex, + Dec + } +} \ No newline at end of file diff --git a/EfsTools/CommandLineOptions/ReadNvItemOptions.cs b/EfsTools/CommandLineOptions/ReadNvItemOptions.cs new file mode 100644 index 000000000..18110979b --- /dev/null +++ b/EfsTools/CommandLineOptions/ReadNvItemOptions.cs @@ -0,0 +1,17 @@ +using CommandLine; + +namespace EfsTools.CommandLineOptions +{ + [Verb("readNvItem", HelpText = "Read single NV item.")] + internal class ReadNvItemOptions + { + [Option('i', "itemId", Required = true, HelpText = "NV item ID.")] + public ushort ItemId { get; set; } + + [Option('o', "outComputerFilePath", Required = false, HelpText = "Output computer file path. If not specified, output will be sent to the console.")] + public string OutComputerFilePath { get; set; } + + [Option('f', "format", Required = false, Default = NvItemFormat.Raw, HelpText = "Output format (Raw, Hex, Dec). Default is Raw. 'Raw' format is not supported for console output and will default to 'Hex'.")] + public NvItemFormat Format { get; set; } + } +} \ No newline at end of file diff --git a/EfsTools/CommandLineOptions/WriteNvItemOptions.cs b/EfsTools/CommandLineOptions/WriteNvItemOptions.cs new file mode 100644 index 000000000..fa9c1275e --- /dev/null +++ b/EfsTools/CommandLineOptions/WriteNvItemOptions.cs @@ -0,0 +1,20 @@ +using CommandLine; + +namespace EfsTools.CommandLineOptions +{ + [Verb("writeNvItem", HelpText = "Write single NV item from a file or a string payload.")] + internal class WriteNvItemOptions + { + [Option('i', "itemId", Required = true, HelpText = "NV item ID.")] + public ushort ItemId { get; set; } + + [Option('c', "inComputerFilePath", Required = false, HelpText = "Input computer file path.", SetName = "file")] + public string InComputerFilePath { get; set; } + + [Option('p', "payload", Required = false, HelpText = "Input payload string (for Hex or Dec formats).", SetName = "payload")] + public string Payload { get; set; } + + [Option('f', "format", Required = false, Default = NvItemFormat.Raw, HelpText = "Input format (Raw, Hex, Dec). Default is Raw. 'Raw' format is not supported for payload input.")] + public NvItemFormat Format { get; set; } + } +} \ No newline at end of file diff --git a/EfsTools/EfsTools.cs b/EfsTools/EfsTools.cs index 49f780985..2ba2a9559 100644 --- a/EfsTools/EfsTools.cs +++ b/EfsTools/EfsTools.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Text; using System.Threading; +using EfsTools.CommandLineOptions; using EfsTools.Items; using EfsTools.Mbn; using EfsTools.Qualcomm; @@ -365,6 +366,139 @@ public void EfsFixFileNames(string efsPath) } } + public void ReadNvItem(ushort itemId, string computerPath, NvItemFormat format) + { + using (var manager = OpenQcdmManager()) + { + var data = manager.Nv.Read(itemId); + if (data == null || data.Length == 0) + { + _logger.LogInfo($"NV item {itemId} could not be read or is empty."); + return; + } + + if (string.IsNullOrEmpty(computerPath)) + { + var outputFormat = format; + if (format == NvItemFormat.Raw) + { + _logger.LogWarning("Raw format cannot be written to the console. Defaulting to Hex."); + outputFormat = NvItemFormat.Hex; + } + + string output; + switch (outputFormat) + { + case NvItemFormat.Hex: + output = BitConverter.ToString(data).Replace("-", " "); + break; + case NvItemFormat.Dec: + output = string.Join(" ", data.Select(b => b.ToString())); + break; + default: + throw new InvalidOperationException("Unsupported format for console output."); + } + _logger.LogInfo($"NV item {itemId} [{outputFormat}]:"); + _logger.LogInfo(output); + } + else + { + switch (format) + { + case NvItemFormat.Raw: + File.WriteAllBytes(computerPath, data); + break; + case NvItemFormat.Hex: + File.WriteAllText(computerPath, BitConverter.ToString(data).Replace("-", " ")); + break; + case NvItemFormat.Dec: + File.WriteAllText(computerPath, string.Join(" ", data.Select(b => b.ToString()))); + break; + } + _logger.LogInfo($"NV item {itemId} was read and saved to '{computerPath}' in {format} format."); + } + } + } + + public void WriteNvItem(ushort itemId, string computerPath, string payload, NvItemFormat format) + { + if (string.IsNullOrEmpty(computerPath) && string.IsNullOrEmpty(payload)) + { + throw new ArgumentException("Either a file path or a payload must be provided."); + } + + if (!string.IsNullOrEmpty(computerPath) && !string.IsNullOrEmpty(payload)) + { + throw new ArgumentException("Both file path and payload cannot be provided simultaneously."); + } + + if (!string.IsNullOrEmpty(payload) && format == NvItemFormat.Raw) + { + throw new ArgumentException("Raw format is not supported for payload input. Please use Hex or Dec."); + } + + byte[] dataToWrite; + + try + { + if (!string.IsNullOrEmpty(payload)) + { + switch (format) + { + case NvItemFormat.Hex: + dataToWrite = ParseUtils.ParseHexString(payload); + break; + case NvItemFormat.Dec: + dataToWrite = ParseUtils.ParseDecString(payload); + break; + default: // Raw is already handled + throw new InvalidOperationException("Unsupported format for payload input."); + } + } + else + { + if (!File.Exists(computerPath)) + { + _logger.LogError($"File not found: {computerPath}"); + return; + } + + switch (format) + { + case NvItemFormat.Raw: + dataToWrite = File.ReadAllBytes(computerPath); + break; + case NvItemFormat.Hex: + { + var hexString = File.ReadAllText(computerPath); + dataToWrite = ParseUtils.ParseHexString(hexString); + break; + } + case NvItemFormat.Dec: + { + var decString = File.ReadAllText(computerPath); + dataToWrite = ParseUtils.ParseDecString(decString); + break; + } + default: + throw new InvalidOperationException("Unsupported format for file input."); + } + } + } + catch (Exception ex) + { + _logger.LogError($"Failed to parse input data: {ex.Message}"); + return; + } + + using (var manager = OpenQcdmManager()) + { + manager.Nv.Write(itemId, dataToWrite); + var source = string.IsNullOrEmpty(computerPath) ? "payload" : $"'{computerPath}'"; + _logger.LogInfo($"NV item {itemId} was written from {source} using {format} format."); + } + } + public void ExtractMbn(string inputMbnFilePath, string outputComputerDirectoryPath, bool noExtraData) { _logger.LogInfo(Strings.QcdmExtractingMbnFileFormat, inputMbnFilePath); diff --git a/EfsTools/Program.cs b/EfsTools/Program.cs index ecd2f62ef..1fa57e9dd 100644 --- a/EfsTools/Program.cs +++ b/EfsTools/Program.cs @@ -44,6 +44,8 @@ private static void Main(string[] args) typeof(GetModemConfigOptions), typeof(SetModemConfigOptions), typeof(ExtractMbnOptions), + typeof(ReadNvItemOptions), + typeof(WriteNvItemOptions), typeof(StartWebDavServerOptions), typeof(GetLogsOptions) ); @@ -78,6 +80,10 @@ private static void Main(string[] args) tools.ExtractMbn(opts.InputMbnFilePath, opts.OutputComputerDirectoryPath, opts.NoExtraData)); cmd.WithParsed(opts => tools.GetLog(opts.MessageMask, opts.LogMask, opts.Verbose)); + cmd.WithParsed(opts => + tools.ReadNvItem(opts.ItemId, opts.OutComputerFilePath, opts.Format)); + cmd.WithParsed(opts => + tools.WriteNvItem(opts.ItemId, opts.InComputerFilePath, opts.Payload, opts.Format)); cmd.WithParsed(opts => tools.StartWebDavServer(opts.Port, opts.LogLevel, opts.ReadOnly != 0)); cmd.WithNotParsed(errors => { }); diff --git a/EfsTools/Utils/ParseUtils.cs b/EfsTools/Utils/ParseUtils.cs new file mode 100644 index 000000000..79efc1b5e --- /dev/null +++ b/EfsTools/Utils/ParseUtils.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace EfsTools.Utils +{ + internal static class ParseUtils + { + public static byte[] ParseHexString(string hex) + { + hex = Regex.Replace(hex, @"\s+", ""); + if (hex.Length % 2 != 0) + { + throw new ArgumentException("Hex string must have an even number of characters."); + } + + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + public static byte[] ParseDecString(string dec) + { + return dec.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(byte.Parse) + .ToArray(); + } + } +} \ No newline at end of file