Skip to content

Commit ef20552

Browse files
feat(ffi): screenshot example using C# bindings (#437)
1 parent 9ce4986 commit ef20552

File tree

93 files changed

+4636
-319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+4636
-319
lines changed

ffi/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ doctest = false
1919
[dependencies]
2020
diplomat = "0.7.0"
2121
diplomat-runtime = "0.7.0"
22-
ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd"] }
22+
ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd","graphics","input"] }
2323
sspi = { workspace = true, features = ["network_client"] }
2424
thiserror.workspace = true
2525

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
obj
22
bin
3-
.vs
3+
.vs
4+
output.bmp

ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@
1010
<ItemGroup>
1111
<ProjectReference Include="../Devolutions.IronRdp/Devolutions.IronRdp.csproj" />
1212
</ItemGroup>
13-
13+
14+
<ItemGroup>
15+
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
16+
</ItemGroup>
17+
1418
</Project>
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
using System.Net;
2-
using System.Net.Security;
3-
using System.Net.Sockets;
1+
using System.Drawing;
2+
using System.Drawing.Imaging;
43

54
namespace Devolutions.IronRdp.ConnectExample
65
{
@@ -10,9 +9,9 @@ static async Task Main(string[] args)
109
{
1110
var arguments = ParseArguments(args);
1211

13-
if (arguments == null)
12+
if (arguments == null)
1413
{
15-
return;
14+
return;
1615
}
1716

1817
var serverName = arguments["--serverName"];
@@ -21,15 +20,95 @@ static async Task Main(string[] args)
2120
var domain = arguments["--domain"];
2221
try
2322
{
24-
await Connect(serverName, username, password, domain);
23+
var (res, framed) = await Connection.Connect(buildConfig(serverName, username, password, domain, 1980, 1080), serverName);
24+
var decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), res.GetDesktopSize().GetHeight());
25+
var activeState = ActiveStage.New(res);
26+
var keepLooping = true;
27+
while (keepLooping)
28+
{
29+
var readPduTask = framed.ReadPdu();
30+
Action? action = null;
31+
byte[]? payload = null;
32+
if (readPduTask == await Task.WhenAny(readPduTask, Task.Delay(1000)))
33+
{
34+
var pduReadTask = await readPduTask;
35+
action = pduReadTask.Item1;
36+
payload = pduReadTask.Item2;
37+
Console.WriteLine($"Action: {action}");
38+
}
39+
else
40+
{
41+
Console.WriteLine("Timeout");
42+
break;
43+
}
44+
var outputIterator = activeState.Process(decodedImage, action, payload);
45+
46+
while (!outputIterator.IsEmpty())
47+
{
48+
var output = outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false
49+
Console.WriteLine($"Output type: {output.GetType()}");
50+
if (output.GetType() == ActiveStageOutputType.Terminate)
51+
{
52+
Console.WriteLine("Connection terminated.");
53+
keepLooping = false;
54+
}
55+
56+
if (output.GetType() == ActiveStageOutputType.ResponseFrame)
57+
{
58+
var responseFrame = output.GetResponseFrame()!;
59+
byte[] responseFrameBytes = new byte[responseFrame.GetSize()];
60+
responseFrame.Fill(responseFrameBytes);
61+
await framed.Write(responseFrameBytes);
62+
}
63+
}
64+
}
65+
66+
saveImage(decodedImage, "output.png");
67+
2568
}
26-
catch (Exception e)
69+
catch (Exception e)
2770
{
2871
Console.WriteLine($"An error occurred: {e.Message}");
2972
}
3073
}
3174

32-
static Dictionary<string, string> ParseArguments(string[] args)
75+
private static void saveImage(DecodedImage decodedImage, string v)
76+
{
77+
int width = decodedImage.GetWidth();
78+
int height = decodedImage.GetHeight();
79+
var data = decodedImage.GetData();
80+
81+
var bytes = new byte[data.GetSize()];
82+
data.Fill(bytes);
83+
for (int i = 0; i < bytes.Length; i += 4)
84+
{
85+
byte temp = bytes[i]; // Store the original Blue value
86+
bytes[i] = bytes[i + 2]; // Move Red to Blue's position
87+
bytes[i + 2] = temp; // Move original Blue to Red's position
88+
// Green (bytes[i+1]) and Alpha (bytes[i+3]) remain unchanged
89+
}
90+
#if WINDOWS // Bitmap is only available on Windows
91+
using (var bmp = new Bitmap(width, height))
92+
{
93+
// Lock the bits of the bitmap.
94+
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
95+
ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
96+
97+
// Get the address of the first line.
98+
IntPtr ptr = bmpData.Scan0;
99+
// Copy the RGBA values back to the bitmap
100+
System.Runtime.InteropServices.Marshal.Copy(bytes, 0, ptr, bytes.Length);
101+
// Unlock the bits.
102+
bmp.UnlockBits(bmpData);
103+
104+
// Save the bitmap to the specified output path
105+
bmp.Save("./output.bmp", ImageFormat.Bmp);
106+
}
107+
#endif
108+
109+
}
110+
111+
static Dictionary<string, string>? ParseArguments(string[] args)
33112
{
34113
if (args.Length == 0 || Array.Exists(args, arg => arg == "--help"))
35114
{
@@ -38,7 +117,7 @@ static Dictionary<string, string> ParseArguments(string[] args)
38117
}
39118

40119
var arguments = new Dictionary<string, string>();
41-
string lastKey = null;
120+
string? lastKey = null;
42121
foreach (var arg in args)
43122
{
44123
if (arg.StartsWith("--"))
@@ -97,222 +176,21 @@ static void PrintHelp()
97176
Console.WriteLine(" --help Show this message and exit.");
98177
}
99178

100-
static async Task Connect(String servername, String username, String password, String domain)
101-
{
102-
Config config = buildConfig(servername, username, password, domain);
103-
104-
var stream = await CreateTcpConnection(servername, 3389);
105-
var framed = new Framed<NetworkStream>(stream);
106179

107-
ClientConnector connector = ClientConnector.New(config);
108-
109-
var ip = await Dns.GetHostAddressesAsync(servername);
110-
if (ip.Length == 0)
111-
{
112-
throw new Exception("Could not resolve server address");
113-
}
114-
115-
var socketAddrString = ip[0].ToString()+":3389";
116-
connector.WithServerAddr(socketAddrString);
117-
118-
await connectBegin(framed, connector);
119-
var (serverPublicKey, framedSsl) = await securityUpgrade(servername, framed, connector);
120-
await ConnectFinalize(servername, connector, serverPublicKey, framedSsl);
121-
}
122180

123-
private static async Task<(byte[], Framed<SslStream>)> securityUpgrade(string servername, Framed<NetworkStream> framed, ClientConnector connector)
124-
{
125-
byte[] serverPublicKey;
126-
Framed<SslStream> framedSsl;
127-
var (streamRequireUpgrade, _) = framed.GetInner();
128-
var promise = new TaskCompletionSource<byte[]>();
129-
var sslStream = new SslStream(streamRequireUpgrade, false, (sender, certificate, chain, sslPolicyErrors) =>
130-
{
131-
promise.SetResult(certificate!.GetPublicKey());
132-
return true;
133-
});
134-
await sslStream.AuthenticateAsClientAsync(servername);
135-
serverPublicKey = await promise.Task;
136-
framedSsl = new Framed<SslStream>(sslStream);
137-
connector.MarkSecurityUpgradeAsDone();
138-
139-
return (serverPublicKey, framedSsl);
140-
}
141-
142-
private static async Task connectBegin(Framed<NetworkStream> framed, ClientConnector connector)
143-
{
144-
var writeBuf = WriteBuf.New();
145-
while (!connector.ShouldPerformSecurityUpgrade())
146-
{
147-
await SingleConnectStep(connector, writeBuf, framed);
148-
}
149-
}
150-
151-
private static Config buildConfig(string servername, string username, string password, string domain)
181+
private static Config buildConfig(string servername, string username, string password, string domain, int width, int height)
152182
{
153183
ConfigBuilder configBuilder = ConfigBuilder.New();
154184

155-
configBuilder.WithUsernameAndPasswrord(username, password);
185+
configBuilder.WithUsernameAndPassword(username, password);
156186
configBuilder.SetDomain(domain);
157-
configBuilder.SetDesktopSize(800, 600);
187+
configBuilder.SetDesktopSize((ushort)height, (ushort)width);
158188
configBuilder.SetClientName("IronRdp");
159189
configBuilder.SetClientDir("C:\\");
160190
configBuilder.SetPerformanceFlags(PerformanceFlags.NewDefault());
161191

162192
return configBuilder.Build();
163193
}
164194

165-
private static async Task ConnectFinalize(string servername, ClientConnector connector, byte[] serverpubkey, Framed<SslStream> framedSsl)
166-
{
167-
var writeBuf2 = WriteBuf.New();
168-
if (connector.ShouldPerformCredssp())
169-
{
170-
await PerformCredsspSteps(connector, servername, writeBuf2, framedSsl, serverpubkey);
171-
}
172-
while (!connector.State().IsTerminal())
173-
{
174-
await SingleConnectStep(connector, writeBuf2, framedSsl);
175-
}
176-
}
177-
178-
private static async Task PerformCredsspSteps(ClientConnector connector, string serverName, WriteBuf writeBuf, Framed<SslStream> framedSsl, byte[] serverpubkey)
179-
{
180-
var credsspSequenceInitResult = CredsspSequence.Init(connector, serverName, serverpubkey, null);
181-
var credsspSequence = credsspSequenceInitResult.GetCredsspSequence();
182-
var tsRequest = credsspSequenceInitResult.GetTsRequest();
183-
TcpClient tcpClient = new TcpClient();
184-
while (true)
185-
{
186-
var generator = credsspSequence.ProcessTsRequest(tsRequest);
187-
var clientState = await ResolveGenerator(generator, tcpClient);
188-
writeBuf.Clear();
189-
var written = credsspSequence.HandleProcessResult(clientState, writeBuf);
190-
191-
if (written.GetSize().IsSome())
192-
{
193-
var actualSize = (int)written.GetSize().Get();
194-
var response = new byte[actualSize];
195-
writeBuf.ReadIntoBuf(response);
196-
await framedSsl.Write(response);
197-
}
198-
199-
var pduHint = credsspSequence.NextPduHint()!;
200-
if (pduHint == null)
201-
{
202-
break;
203-
}
204-
205-
var pdu = await framedSsl.ReadByHint(pduHint);
206-
var decoded = credsspSequence.DecodeServerMessage(pdu);
207-
208-
if (null == decoded)
209-
{
210-
break;
211-
}
212-
213-
tsRequest = decoded;
214-
}
215-
}
216-
217-
private static async Task<ClientState> ResolveGenerator(CredsspProcessGenerator generator, TcpClient tcpClient)
218-
{
219-
var state = generator.Start();
220-
NetworkStream stream = null;
221-
while (true)
222-
{
223-
if (state.IsSuspended())
224-
{
225-
var request = state.GetNetworkRequestIfSuspended()!;
226-
var protocol = request.GetProtocol();
227-
var url = request.GetUrl();
228-
var data = request.GetData();
229-
if (null == stream)
230-
{
231-
url = url.Replace("tcp://", "");
232-
var split = url.Split(":");
233-
await tcpClient.ConnectAsync(split[0], int.Parse(split[1]));
234-
stream = tcpClient.GetStream();
235-
236-
}
237-
if (protocol == NetworkRequestProtocol.Tcp)
238-
{
239-
stream.Write(Utils.Vecu8ToByte(data));
240-
var readBuf = new byte[8096];
241-
var readlen = await stream.ReadAsync(readBuf, 0, readBuf.Length);
242-
var actuallyRead = new byte[readlen];
243-
Array.Copy(readBuf, actuallyRead, readlen);
244-
state = generator.Resume(actuallyRead);
245-
}
246-
else
247-
{
248-
throw new Exception("Unimplemented protocol");
249-
}
250-
}
251-
else
252-
{
253-
var client_state = state.GetClientStateIfCompleted();
254-
return client_state;
255-
}
256-
}
257-
}
258-
259-
static async Task SingleConnectStep<T>(ClientConnector connector, WriteBuf buf, Framed<T> framed)
260-
where T : Stream
261-
{
262-
buf.Clear();
263-
264-
var pduHint = connector.NextPduHint();
265-
Written written;
266-
if (pduHint != null)
267-
{
268-
byte[] pdu = await framed.ReadByHint(pduHint);
269-
written = connector.Step(pdu, buf);
270-
}
271-
else
272-
{
273-
written = connector.StepNoInput(buf);
274-
}
275-
276-
if (written.GetWrittenType() == WrittenType.Nothing)
277-
{
278-
return;
279-
}
280-
281-
// will throw if size is not set
282-
var size = written.GetSize().Get();
283-
284-
var response = new byte[size];
285-
buf.ReadIntoBuf(response);
286-
287-
await framed.Write(response);
288-
}
289-
290-
static async Task<NetworkStream> CreateTcpConnection(String servername, int port)
291-
{
292-
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync(servername);
293-
IPAddress ipAddress = ipHostInfo.AddressList[0];
294-
IPEndPoint ipEndPoint = new(ipAddress, port);
295-
296-
TcpClient client = new TcpClient();
297-
298-
await client.ConnectAsync(ipEndPoint);
299-
NetworkStream stream = client.GetStream();
300-
301-
return stream;
302-
}
303-
304195
}
305-
306-
public static class Utils
307-
{
308-
public static byte[] Vecu8ToByte(VecU8 vecU8)
309-
{
310-
var len = vecU8.GetSize();
311-
byte[] buffer = new byte[len];
312-
vecU8.Fill(buffer);
313-
return buffer;
314-
}
315-
}
316-
}
317-
318-
196+
}

0 commit comments

Comments
 (0)