Repository navigation
Expand file tree
/
Copy pathProgram.cs
More file actions
157 lines (139 loc) · 6.1 KB
/
Copy pathProgram.cs
File metadata and controls
157 lines (139 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using Microsoft.VisualBasic.ApplicationServices;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Windows.Forms;
namespace nplus
{
static class Program
{
[STAThread]
static void Main(string[] args)
{
// MUST run before anything touches the Scintilla type: extracts the
// embedded native DLLs and tells Scintilla.NET where to find them.
NativeBootstrap.Initialize();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Route through a single-instance harness so that "Open with..." (or any
// launch while nplus is already running) forwards the file paths to the
// existing window instead of spawning a second copy.
new SingleInstanceApp().Run(args);
}
}
internal sealed class SingleInstanceApp : WindowsFormsApplicationBase
{
public SingleInstanceApp()
{
IsSingleInstance = true;
EnableVisualStyles = true;
ShutdownStyle = ShutdownMode.AfterMainFormCloses;
}
protected override void OnCreateMainForm()
{
// First launch — create the editor with whatever file args we got from CLI.
MainForm = new EditorForm(CommandLineArgs?.ToArray() ?? Array.Empty<string>());
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
{
base.OnStartupNextInstance(e);
// A second nplus.exe was launched (e.g. via Open With...). The framework
// routes its args here on the original instance's UI thread.
e.BringToForeground = true;
if (MainForm is EditorForm editor && e.CommandLine != null && e.CommandLine.Count > 0)
{
editor.OpenFilesFromPaths(e.CommandLine.ToArray());
}
}
}
/// <summary>
/// Makes the single-file build work: the native Scintilla.dll / Lexilla.dll
/// are shipped as embedded resources inside nplus.exe (a single-file bundle
/// can't expose them on disk for Scintilla.NET to LoadLibrary). At startup we
/// write them out to a per-user folder and point Scintilla.NET at it via
/// <c>ScintillaNET.ScintillaNativeLibrary.SatelliteDirectory</c>, which it
/// probes before any of its file-system fallbacks.
/// </summary>
internal static class NativeBootstrap
{
private static readonly string[] NativeDlls = { "Scintilla.dll", "Lexilla.dll" };
private const string ResourcePrefix = "nplus.native.";
internal static void Initialize()
{
Assembly asm = Assembly.GetExecutingAssembly();
// Version-stamp the extraction dir so upgraded exes don't reuse stale DLLs.
string version = asm.GetName().Version?.ToString() ?? "0";
string targetDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"nplus", "native", version);
Directory.CreateDirectory(targetDir);
foreach (string name in NativeDlls)
{
ExtractIfNeeded(asm, ResourcePrefix + name, Path.Combine(targetDir, name));
}
// Referencing ScintillaNativeLibrary (a standalone static class) does NOT
// trigger the Scintilla control's native-loading static ctor, so this is
// safe to set here and is honored on first use of the editor.
ScintillaNET.ScintillaNativeLibrary.SatelliteDirectory = targetDir;
}
private static void ExtractIfNeeded(Assembly asm, string resourceName, string destPath)
{
byte[] expected;
using (Stream src = asm.GetManifestResourceStream(resourceName))
{
if (src == null)
{
throw new InvalidOperationException(
"Embedded native resource not found: " + resourceName);
}
using var ms = new MemoryStream();
src.CopyTo(ms);
expected = ms.ToArray();
}
// Skip rewriting only when the on-disk copy is byte-for-byte the embedded
// one. A length-only check would let a same-user attacker swap in a
// matching-size malicious Scintilla.dll/Lexilla.dll that we'd then load via
// LoadLibrary. Verifying the full hash makes the cached DLL exactly the one
// we shipped, and still skips the write (and the clobber of an instance that
// has it mapped) on the fast path.
if (File.Exists(destPath) && FileMatchesHash(destPath, expected))
{
return;
}
try
{
using FileStream dst = new FileStream(
destPath, FileMode.Create, FileAccess.Write, FileShare.Read);
dst.Write(expected, 0, expected.Length);
}
catch (IOException)
{
// Another instance already extracted / has it loaded. Tolerate that only
// if what's on disk is the genuine DLL; otherwise surface the failure
// rather than load a file we couldn't validate or replace.
if (!File.Exists(destPath) || !FileMatchesHash(destPath, expected)) throw;
}
}
// True if the file's SHA-256 equals the hash of <paramref name="expected"/>.
private static bool FileMatchesHash(string path, byte[] expected)
{
try
{
byte[] want = SHA256.HashData(expected);
byte[] got;
using (FileStream fs = new FileStream(
path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
got = SHA256.HashData(fs);
}
return CryptographicOperations.FixedTimeEquals(want, got);
}
catch
{
return false;
}
}
}
}