SevenZipSfx.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. namespace SevenZip
  2. {
  3. #if SFX
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Globalization;
  7. using System.IO;
  8. using System.Reflection;
  9. using System.Text;
  10. using System.Xml;
  11. using System.Xml.Schema;
  12. using SfxSettings = System.Collections.Generic.Dictionary<string, string>;
  13. /// <summary>
  14. /// Sfx module choice enumeration
  15. /// </summary>
  16. public enum SfxModule
  17. {
  18. /// <summary>
  19. /// Default module (leave this if unsure)
  20. /// </summary>
  21. Default,
  22. /// <summary>
  23. /// The simple sfx module by Igor Pavlov with no adjustable parameters
  24. /// </summary>
  25. Simple,
  26. /// <summary>
  27. /// The installer sfx module by Igor Pavlov
  28. /// </summary>
  29. Installer,
  30. /// <summary>
  31. /// The extended installer sfx module by Oleg Scherbakov
  32. /// </summary>
  33. Extended,
  34. /// <summary>
  35. /// The custom sfx module. First you must specify the module file name.
  36. /// </summary>
  37. Custom
  38. }
  39. /// <summary>
  40. /// The class for making 7-zip based self-extracting archives.
  41. /// </summary>
  42. public class SevenZipSfx
  43. {
  44. private static Dictionary<SfxModule, List<string>> SfxSupportedModuleNames
  45. {
  46. get
  47. {
  48. var result = new Dictionary<SfxModule, List<string>>
  49. {
  50. {SfxModule.Simple, new List<string>(2) {"7z.sfx", "7zCon.sfx"}},
  51. {SfxModule.Installer, new List<string>(2) {"7zS.sfx", "7zSD.sfx"}}
  52. };
  53. if (Environment.Is64BitProcess)
  54. {
  55. result.Add(SfxModule.Default, new List<string>(1) { "7zxSD_All_x64.sfx" });
  56. result.Add(SfxModule.Extended, new List<string>(4) { "7zxSD_All_x64.sfx", "7zxSD_Deflate_x64", "7zxSD_LZMA_x64", "7zxSD_PPMd_x64" });
  57. }
  58. else
  59. {
  60. result.Add(SfxModule.Default, new List<string>(1) { "7zxSD_All.sfx" });
  61. result.Add(SfxModule.Extended, new List<string>(4) { "7zxSD_All.sfx", "7zxSD_Deflate", "7zxSD_LZMA", "7zxSD_PPMd" });
  62. }
  63. return result;
  64. }
  65. }
  66. private string _moduleFileName;
  67. private Dictionary<SfxModule, List<string>> _sfxCommands;
  68. /// <summary>
  69. /// Initializes a new instance of the SevenZipSfx class.
  70. /// </summary>
  71. public SevenZipSfx()
  72. {
  73. SfxModule = SfxModule.Default;
  74. CommonInit();
  75. }
  76. /// <summary>
  77. /// Initializes a new instance of the SevenZipSfx class.
  78. /// </summary>
  79. /// <param name="module">The sfx module to use as a front-end.</param>
  80. public SevenZipSfx(SfxModule module)
  81. {
  82. if (module == SfxModule.Custom)
  83. {
  84. throw new ArgumentException("You must specify the custom module executable.", nameof(module));
  85. }
  86. SfxModule = module;
  87. CommonInit();
  88. }
  89. /// <summary>
  90. /// Initializes a new instance of the SevenZipSfx class.
  91. /// </summary>
  92. /// <param name="moduleFileName"></param>
  93. public SevenZipSfx(string moduleFileName)
  94. {
  95. SfxModule = SfxModule.Custom;
  96. ModuleFileName = moduleFileName;
  97. CommonInit();
  98. }
  99. /// <summary>
  100. /// Gets the sfx module type.
  101. /// </summary>
  102. public SfxModule SfxModule { get; private set; }
  103. /// <summary>
  104. /// Gets or sets the custom sfx module file name
  105. /// </summary>
  106. public string ModuleFileName
  107. {
  108. get => _moduleFileName;
  109. set
  110. {
  111. if (!File.Exists(value))
  112. {
  113. throw new ArgumentException("The specified file does not exist.");
  114. }
  115. _moduleFileName = value;
  116. SfxModule = SfxModule.Custom;
  117. var sfxName = Path.GetFileName(value);
  118. foreach (var mod in SfxSupportedModuleNames.Keys)
  119. {
  120. if (SfxSupportedModuleNames[mod].Contains(sfxName))
  121. {
  122. SfxModule = mod;
  123. }
  124. }
  125. }
  126. }
  127. private void CommonInit()
  128. {
  129. LoadCommandsFromResource("Configs");
  130. }
  131. private static string GetResourceString(string str)
  132. {
  133. return "SevenZip.sfx." + str;
  134. }
  135. /// <summary>
  136. /// Gets the sfx module enum by the list of supported modules
  137. /// </summary>
  138. /// <param name="name"></param>
  139. /// <returns></returns>
  140. private static SfxModule GetModuleByName(string name)
  141. {
  142. if (name.IndexOf("7z.sfx", StringComparison.Ordinal) > -1)
  143. {
  144. return SfxModule.Simple;
  145. }
  146. if (name.IndexOf("7zS.sfx", StringComparison.Ordinal) > -1)
  147. {
  148. return SfxModule.Installer;
  149. }
  150. if (name.IndexOf("7zxSD_All.sfx", StringComparison.Ordinal) > -1)
  151. {
  152. return SfxModule.Extended;
  153. }
  154. throw new SevenZipSfxValidationException("The specified configuration is unsupported.");
  155. }
  156. /// <summary>
  157. /// Loads the commands for each supported sfx module configuration
  158. /// </summary>
  159. /// <param name="xmlDefinitions">The resource name for xml definitions</param>
  160. private void LoadCommandsFromResource(string xmlDefinitions)
  161. {
  162. using (var cfg = Assembly.GetExecutingAssembly().GetManifestResourceStream(
  163. GetResourceString(xmlDefinitions + ".xml")))
  164. {
  165. if (cfg == null)
  166. {
  167. throw new SevenZipSfxValidationException("The configuration \"" + xmlDefinitions +
  168. "\" does not exist.");
  169. }
  170. using (var schm = Assembly.GetExecutingAssembly().GetManifestResourceStream(
  171. GetResourceString(xmlDefinitions + ".xsd")))
  172. {
  173. if (schm == null)
  174. {
  175. throw new SevenZipSfxValidationException("The configuration schema \"" + xmlDefinitions +
  176. "\" does not exist.");
  177. }
  178. var sc = new XmlSchemaSet();
  179. using (var scr = XmlReader.Create(schm))
  180. {
  181. sc.Add(null, scr);
  182. var settings = new XmlReaderSettings {ValidationType = ValidationType.Schema, Schemas = sc};
  183. var validationErrors = "";
  184. settings.ValidationEventHandler +=
  185. ((s, t) =>
  186. {
  187. validationErrors += string.Format(CultureInfo.InvariantCulture, "[{0}]: {1}\n",
  188. t.Severity.ToString(), t.Message);
  189. });
  190. using (var rdr = XmlReader.Create(cfg, settings))
  191. {
  192. _sfxCommands = new Dictionary<SfxModule, List<string>>();
  193. rdr.Read();
  194. rdr.Read();
  195. rdr.Read();
  196. rdr.Read();
  197. rdr.Read();
  198. rdr.ReadStartElement("sfxConfigs");
  199. rdr.Read();
  200. do
  201. {
  202. var mod = GetModuleByName(rdr["modules"]);
  203. rdr.ReadStartElement("config");
  204. rdr.Read();
  205. if (rdr.Name == "id")
  206. {
  207. var cmds = new List<string>();
  208. _sfxCommands.Add(mod, cmds);
  209. do
  210. {
  211. cmds.Add(rdr["command"]);
  212. rdr.Read();
  213. rdr.Read();
  214. } while (rdr.Name == "id");
  215. rdr.ReadEndElement();
  216. rdr.Read();
  217. }
  218. else
  219. {
  220. _sfxCommands.Add(mod, null);
  221. }
  222. } while (rdr.Name == "config");
  223. }
  224. if (!string.IsNullOrEmpty(validationErrors))
  225. {
  226. throw new SevenZipSfxValidationException(
  227. "\n" + validationErrors.Substring(0, validationErrors.Length - 1));
  228. }
  229. _sfxCommands.Add(SfxModule.Default, _sfxCommands[SfxModule.Extended]);
  230. }
  231. }
  232. }
  233. }
  234. /// <summary>
  235. /// Validates the sfx scenario commands.
  236. /// </summary>
  237. /// <param name="settings">The sfx settings dictionary to validate.</param>
  238. private void ValidateSettings(SfxSettings settings)
  239. {
  240. if (SfxModule == SfxModule.Custom)
  241. {
  242. return;
  243. }
  244. var commands = _sfxCommands[SfxModule];
  245. if (commands == null)
  246. {
  247. return;
  248. }
  249. var invalidCommands = new List<string>();
  250. foreach (var command in settings.Keys)
  251. {
  252. if (!commands.Contains(command))
  253. {
  254. invalidCommands.Add(command);
  255. }
  256. }
  257. if (invalidCommands.Count > 0)
  258. {
  259. var invalidText = new StringBuilder("\nInvalid commands:\n");
  260. foreach (var str in invalidCommands)
  261. {
  262. invalidText.Append(str);
  263. }
  264. throw new SevenZipSfxValidationException(invalidText.ToString());
  265. }
  266. }
  267. /// <summary>
  268. /// Gets the stream containing the sfx settings.
  269. /// </summary>
  270. /// <param name="settings">The sfx settings dictionary.</param>
  271. /// <returns></returns>
  272. private static Stream GetSettingsStream(SfxSettings settings)
  273. {
  274. var ms = new MemoryStream();
  275. var buf = Encoding.UTF8.GetBytes(@";!@Install@!UTF-8!" + '\n');
  276. ms.Write(buf, 0, buf.Length);
  277. foreach (var command in settings.Keys)
  278. {
  279. buf =
  280. Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}=\"{1}\"\n", command,
  281. settings[command]));
  282. ms.Write(buf, 0, buf.Length);
  283. }
  284. buf = Encoding.UTF8.GetBytes(@";!@InstallEnd@!");
  285. ms.Write(buf, 0, buf.Length);
  286. return ms;
  287. }
  288. private SfxSettings GetDefaultSettings()
  289. {
  290. switch (SfxModule)
  291. {
  292. default:
  293. return null;
  294. case SfxModule.Installer:
  295. var settings = new Dictionary<string, string> {{"Title", "7-Zip self-extracting archive"}};
  296. return settings;
  297. case SfxModule.Default:
  298. case SfxModule.Extended:
  299. settings = new Dictionary<string, string>
  300. {
  301. {"GUIMode", "0"},
  302. {"InstallPath", "."},
  303. {"GUIFlags", "128+8"},
  304. {"ExtractPathTitle", "7-Zip self-extracting archive"},
  305. {"ExtractPathText", "Specify the path where to extract the files:"}
  306. };
  307. return settings;
  308. }
  309. }
  310. /// <summary>
  311. /// Writes the whole to the other one.
  312. /// </summary>
  313. /// <param name="src">The source stream to read from.</param>
  314. /// <param name="dest">The destination stream to write to.</param>
  315. private static void WriteStream(Stream src, Stream dest)
  316. {
  317. if (src == null)
  318. {
  319. throw new ArgumentNullException(nameof(src));
  320. }
  321. if (dest == null)
  322. {
  323. throw new ArgumentNullException(nameof(dest));
  324. }
  325. src.Seek(0, SeekOrigin.Begin);
  326. var buf = new byte[32768];
  327. int bytesRead;
  328. while ((bytesRead = src.Read(buf, 0, buf.Length)) > 0)
  329. {
  330. dest.Write(buf, 0, bytesRead);
  331. }
  332. }
  333. /// <summary>
  334. /// Makes the self-extracting archive.
  335. /// </summary>
  336. /// <param name="archive">The archive stream.</param>
  337. /// <param name="sfxFileName">The name of the self-extracting executable.</param>
  338. public void MakeSfx(Stream archive, string sfxFileName)
  339. {
  340. using (Stream sfxStream = File.Create(sfxFileName))
  341. {
  342. MakeSfx(archive, GetDefaultSettings(), sfxStream);
  343. }
  344. }
  345. /// <summary>
  346. /// Makes the self-extracting archive.
  347. /// </summary>
  348. /// <param name="archive">The archive stream.</param>
  349. /// <param name="sfxStream">The stream to write the self-extracting executable to.</param>
  350. public void MakeSfx(Stream archive, Stream sfxStream)
  351. {
  352. MakeSfx(archive, GetDefaultSettings(), sfxStream);
  353. }
  354. /// <summary>
  355. /// Makes the self-extracting archive.
  356. /// </summary>
  357. /// <param name="archive">The archive stream.</param>
  358. /// <param name="settings">The sfx settings.</param>
  359. /// <param name="sfxFileName">The name of the self-extracting executable.</param>
  360. public void MakeSfx(Stream archive, SfxSettings settings, string sfxFileName)
  361. {
  362. using (Stream sfxStream = File.Create(sfxFileName))
  363. {
  364. MakeSfx(archive, settings, sfxStream);
  365. }
  366. }
  367. /// <summary>
  368. /// Makes the self-extracting archive.
  369. /// </summary>
  370. /// <param name="archive">The archive stream.</param>
  371. /// <param name="settings">The sfx settings.</param>
  372. /// <param name="sfxStream">The stream to write the self-extracting executable to.</param>
  373. public void MakeSfx(Stream archive, SfxSettings settings, Stream sfxStream)
  374. {
  375. if (!sfxStream.CanWrite)
  376. {
  377. throw new ArgumentException("The specified output stream can not write.", "sfxStream");
  378. }
  379. ValidateSettings(settings);
  380. using (var sfx = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetResourceString(SfxSupportedModuleNames[SfxModule][0])))
  381. {
  382. WriteStream(sfx, sfxStream);
  383. }
  384. if (SfxModule == SfxModule.Custom || _sfxCommands[SfxModule] != null)
  385. {
  386. using (var set = GetSettingsStream(settings))
  387. {
  388. WriteStream(set, sfxStream);
  389. }
  390. }
  391. WriteStream(archive, sfxStream);
  392. }
  393. /// <summary>
  394. /// Makes the self-extracting archive.
  395. /// </summary>
  396. /// <param name="archiveFileName">The archive file name.</param>
  397. /// <param name="sfxFileName">The name of the self-extracting executable.</param>
  398. public void MakeSfx(string archiveFileName, string sfxFileName)
  399. {
  400. using (Stream sfxStream = File.Create(sfxFileName))
  401. {
  402. using (
  403. Stream archive = new FileStream(archiveFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  404. )
  405. {
  406. MakeSfx(archive, GetDefaultSettings(), sfxStream);
  407. }
  408. }
  409. }
  410. /// <summary>
  411. /// Makes the self-extracting archive.
  412. /// </summary>
  413. /// <param name="archiveFileName">The archive file name.</param>
  414. /// <param name="sfxStream">The stream to write the self-extracting executable to.</param>
  415. public void MakeSfx(string archiveFileName, Stream sfxStream)
  416. {
  417. using (Stream archive = new FileStream(archiveFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  418. )
  419. {
  420. MakeSfx(archive, GetDefaultSettings(), sfxStream);
  421. }
  422. }
  423. /// <summary>
  424. /// Makes the self-extracting archive.
  425. /// </summary>
  426. /// <param name="archiveFileName">The archive file name.</param>
  427. /// <param name="settings">The sfx settings.</param>
  428. /// <param name="sfxFileName">The name of the self-extracting executable.</param>
  429. public void MakeSfx(string archiveFileName, SfxSettings settings, string sfxFileName)
  430. {
  431. using (Stream sfxStream = File.Create(sfxFileName))
  432. {
  433. using (
  434. Stream archive = new FileStream(archiveFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  435. )
  436. {
  437. MakeSfx(archive, settings, sfxStream);
  438. }
  439. }
  440. }
  441. /// <summary>
  442. /// Makes the self-extracting archive.
  443. /// </summary>
  444. /// <param name="archiveFileName">The archive file name.</param>
  445. /// <param name="settings">The sfx settings.</param>
  446. /// <param name="sfxStream">The stream to write the self-extracting executable to.</param>
  447. public void MakeSfx(string archiveFileName, SfxSettings settings, Stream sfxStream)
  448. {
  449. using (Stream archive = new FileStream(archiveFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  450. )
  451. {
  452. MakeSfx(archive, settings, sfxStream);
  453. }
  454. }
  455. }
  456. #endif
  457. }