NeiConverter.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using ArcToolkitCLI.Commands.Options;
  7. using ArcToolkitCLI.Util;
  8. using CommandLine;
  9. namespace ArcToolkitCLI.Commands.Converters
  10. {
  11. [Verb("nei", HelpText = "Convert NEI files into CSV and back")]
  12. class NeiConverter : IConverterCommand, IInputOptions, IOutputOptions
  13. {
  14. private static readonly byte[] NEI_KEY = { 0xAA, 0xC9, 0xD2, 0x35, 0x22, 0x87, 0x20, 0xF2, 0x40, 0xC5, 0x61, 0x7C, 0x01, 0xDF, 0x66, 0x54 };
  15. private static readonly byte[] NEI_MAGIC = { 0x77, 0x73, 0x76, 0xFF };
  16. private static readonly Encoding ShiftJisEncoding = Encoding.GetEncoding(932);
  17. public int Run()
  18. {
  19. var files = Glob.EnumerateFiles(Input);
  20. if (!files.Any())
  21. {
  22. Console.WriteLine("No files specified. Run `convert help nei` for help.");
  23. return 0;
  24. }
  25. foreach (var file in files)
  26. {
  27. int result = 0;
  28. switch (file.Extension.ToLowerInvariant())
  29. {
  30. case ".nei":
  31. if ((result = ToCSV(file.FullName)) != 0)
  32. return result;
  33. break;
  34. case ".csv":
  35. if ((result = ToNei(file.FullName)) != 0)
  36. return result;
  37. break;
  38. default:
  39. Console.WriteLine($"File {file.FullName} is neither .nei nor .csv file. Skipping...");
  40. break;
  41. }
  42. }
  43. return 0;
  44. }
  45. int ToNei(string filePath)
  46. {
  47. string nameNoExt = Path.GetFileNameWithoutExtension(filePath);
  48. List<string[]> values = new List<string[]>();
  49. int cols = 0;
  50. int rows = 0;
  51. using (var tr = File.OpenText(filePath))
  52. {
  53. string line;
  54. while ((line = tr.ReadLine()) != null)
  55. {
  56. var colValues = line.Split(new []{ValueSeparator}, StringSplitOptions.None);
  57. if(colValues.Length == 0)
  58. continue;
  59. cols = Math.Max(cols, colValues.Length);
  60. values.Add(colValues);
  61. rows++;
  62. }
  63. }
  64. byte[][] encodedValues = new byte[rows * cols][];
  65. for (var rowIndex = 0; rowIndex < values.Count; rowIndex++)
  66. {
  67. var row = values[rowIndex];
  68. for (int colIndex = 0; colIndex < cols; colIndex++)
  69. {
  70. encodedValues[colIndex + rowIndex * cols] = colIndex < row.Length
  71. ? ShiftJisEncoding.GetBytes(row[colIndex])
  72. : new byte[0];
  73. }
  74. }
  75. using (var ms = new MemoryStream())
  76. {
  77. using (var bw = new BinaryWriter(ms))
  78. {
  79. bw.Write(NEI_MAGIC);
  80. bw.Write(cols);
  81. bw.Write(rows);
  82. int totalLength = 0;
  83. foreach (var encodedValue in encodedValues)
  84. {
  85. var len = encodedValue.Length;
  86. if (len != 0)
  87. len++;
  88. bw.Write(encodedValue.Length == 0 ? 0 : totalLength);
  89. bw.Write(len);
  90. totalLength += len;
  91. }
  92. for (var i = 0; i < encodedValues.Length; i++)
  93. {
  94. var encodedValue = encodedValues[i];
  95. if(encodedValue.Length == 0)
  96. continue;
  97. bw.Write(encodedValue);
  98. if (i != encodedValue.Length - 1)
  99. bw.Write((byte) 0x00);
  100. }
  101. }
  102. var data = ms.ToArray();
  103. File.WriteAllBytes(Path.Combine(Output, $"{nameNoExt}.nei"), Encryption.EncryptBytes(data, NEI_KEY));
  104. }
  105. return 0;
  106. }
  107. int ToCSV(string filePath)
  108. {
  109. string nameNoExt = Path.GetFileNameWithoutExtension(filePath);
  110. byte[] neiData = Encryption.DecryptBytes(File.ReadAllBytes(filePath), NEI_KEY);
  111. using (var ms = new MemoryStream(neiData))
  112. {
  113. using (var br = new BinaryReader(ms))
  114. {
  115. if (!br.ReadBytes(4).SequenceEqual(NEI_MAGIC))
  116. {
  117. Console.WriteLine($"File {filePath} is not a valid NEI file");
  118. return 1;
  119. }
  120. var cols = br.ReadUInt32();
  121. var rows = br.ReadUInt32();
  122. var strLengths = new int[cols * rows];
  123. for (int cell = 0; cell < cols * rows; cell++)
  124. {
  125. br.ReadInt32(); // Total length of all strings because why not
  126. strLengths[cell] = br.ReadInt32();
  127. }
  128. var values = new string[cols * rows];
  129. for (int cell = 0; cell < cols * rows; cell++)
  130. {
  131. var len = strLengths[cell];
  132. values[cell] = ShiftJisEncoding.GetString(br.ReadBytes(len), 0, Math.Max(len - 1, 0));
  133. }
  134. using (var tw = File.CreateText(Path.Combine(Output, $"{nameNoExt}.csv")))
  135. {
  136. for (int row = 0; row < rows; row++)
  137. {
  138. for (int col = 0; col < cols; col++)
  139. {
  140. tw.Write(values[row * cols + col]);
  141. if(col != cols - 1)
  142. tw.Write(ValueSeparator);
  143. }
  144. tw.WriteLine();
  145. }
  146. }
  147. }
  148. }
  149. return 0;
  150. }
  151. [Value(0, HelpText = "Input NEI or CSV files")]
  152. public IEnumerable<string> Input { get; set; }
  153. public string Output { get; set; }
  154. [Option('s', "separator", Default = ";", HelpText = "Value separator of the CSV file")]
  155. public string ValueSeparator { get; set; }
  156. }
  157. }