Skip to content

Commit f1a9431

Browse files
author
arch
committed
init commit
0 parents  commit f1a9431

14 files changed

Lines changed: 875 additions & 0 deletions

Entrypoint.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace VirtualDesktopTimecodeServer
2+
{
3+
using System;
4+
using System.Threading.Tasks;
5+
using System.Windows;
6+
7+
public class TimecodeDriver
8+
{
9+
public static int StartTimecodeServer(string debugLogFile)
10+
{
11+
12+
if (Application.Current == null)
13+
{
14+
return 1;
15+
}
16+
17+
Application.Current.Dispatcher.Invoke(async () => await RunTimecodeServer(debugLogFile));
18+
19+
return 0;
20+
}
21+
22+
private static async Task RunTimecodeServer(string debugLogFile)
23+
{
24+
const int UPDATE_LOOP_TIME_IN_MILLLISECONDS = 25;
25+
var virtualDesktopPlaybackSettings = VirtualDesktopPlaybackSettings.Instance;
26+
//var virtualDesktopUiSettings = VirtualDesktopUiSettings.Instance;
27+
var timecodeServer = VirtualDesktopTimecodeServer.Instance;
28+
29+
timecodeServer.SetServerLoopTimeInMilliseconds(UPDATE_LOOP_TIME_IN_MILLLISECONDS);
30+
31+
while (Application.Current.MainWindow is not null)
32+
{
33+
await Task.Delay(TimeSpan.FromMilliseconds(UPDATE_LOOP_TIME_IN_MILLLISECONDS));
34+
35+
virtualDesktopPlaybackSettings.Update();
36+
//virtualDesktopUiSettings.Update();
37+
38+
timecodeServer.SetVideoPath(virtualDesktopPlaybackSettings.VideoPath);
39+
timecodeServer.SetIsPlaying(virtualDesktopPlaybackSettings.IsPlaying);
40+
timecodeServer.SetVideoPositionInSeconds(virtualDesktopPlaybackSettings.VideoPositionInSecondsString);
41+
}
42+
43+
await Task.CompletedTask;
44+
}
45+
}
46+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 michael-mueller-git
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MethodRedirect/Extensions.cs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Reflection;
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
7+
namespace MethodRedirect
8+
{
9+
static class Extensions
10+
{
11+
/// <summary>
12+
/// Redirect origin method calls to the specified target method.
13+
/// Use redirection when there is no need to call the origin method when redirected to the target method.
14+
/// </summary>
15+
/// <param name="origin"></param>
16+
/// <param name="target"></param>
17+
/// <param name="verbose"></param>
18+
/// <returns>
19+
/// A MethodRedirection operation result object
20+
/// </returns>
21+
public static MethodOperation RedirectTo(this MethodInfo origin, MethodInfo target, bool verbose = false)
22+
{
23+
IntPtr ori = GetMethodAddress(origin);
24+
IntPtr tar = GetMethodAddress(target);
25+
26+
// The function pointer gives the value located at the method address...
27+
// This can be used for validation that the method address matches
28+
Debug.Assert(Marshal.ReadIntPtr(ori) == origin.MethodHandle.GetFunctionPointer());
29+
Debug.Assert(Marshal.ReadIntPtr(tar) == target.MethodHandle.GetFunctionPointer());
30+
31+
if (verbose)
32+
{
33+
Console.WriteLine("\nPlatform : {0}", IntPtr.Size == 4 ? "x86" : "x64");
34+
Console.WriteLine("IntPtr.Size : {0}", IntPtr.Size);
35+
36+
Console.WriteLine("\nFrom origin method : {0}", origin.Name);
37+
OutputMethodDetails(origin, ori);
38+
39+
Console.WriteLine("\nTo target method : {0}", target.Name);
40+
OutputMethodDetails(target, tar);
41+
}
42+
43+
return Redirect(ori, tar, verbose);
44+
}
45+
46+
/// <summary>
47+
/// Show the details of the method address evaluation
48+
/// </summary>
49+
/// <param name="mi"></param>
50+
/// <param name="address">
51+
/// The unconditional jmp address to the JIT-compiled method
52+
/// </param>
53+
private static void OutputMethodDetails(MethodInfo mi, IntPtr address)
54+
{
55+
IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address
56+
IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address
57+
58+
// MethodTable address > MethodDescriptor address
59+
int offset = (int)((long)mt - (long)md);
60+
61+
Console.WriteLine("Method is virtual : {0}", mi.IsVirtual);
62+
Console.WriteLine("MethodDescriptor (MD) : \t\t{0}", md.ToString("x").PadLeft(8, '0'));
63+
Console.WriteLine("MethodTable (MT) : \t\t{0}", mt.ToString("x").PadLeft(8, '0'));
64+
Console.WriteLine("Offset (MT - MD) : \t\t{0}", offset.ToString("x").PadLeft(8, '0'));
65+
66+
if (mi.IsVirtual)
67+
{
68+
// The fixed-size portion of the MethodTable structure depends on the process type:
69+
// For 32-bit process (IntPtr.Size == 4), the fixed-size portion is 40 (0x28) bytes
70+
// For 64-bit process (IntPtr.Size == 8), the fixed-size portion is 64 (0x40) bytes
71+
offset = IntPtr.Size == 4 ? 0x28 : 0x40;
72+
73+
// First method slot = MethodTable address + fixed-size offset
74+
// This is the address of the first method of any type (i.e. ToString)
75+
IntPtr ms = mt + offset;
76+
77+
Console.WriteLine("MethodTable offset : \t\t{0}", offset.ToString("x").PadLeft(8, '0'));
78+
Console.WriteLine("First method slot : {0}\t{1}",
79+
Marshal.ReadIntPtr(ms).ToString("x").PadLeft(8, '0'),
80+
ms.ToString("x").PadLeft(8, '0'));
81+
82+
// Get the slot number of the virtual method entry from the MethodDesc data structure
83+
//
84+
// a. Get the value at the address of the MethodDescriptor
85+
//
86+
// MethodDesc data structure
87+
// ---------------------------
88+
// MethodDescriptor -> | Token Remainder | 2 bytes
89+
// | Chunck Index | 1 bytes
90+
// MethodTableSlot -> | Stub (op code + target) | 5 bytes
91+
// | Slot Number | 2 bytes => 3 bytes
92+
// | Flags | 2 bytes => 1 byte
93+
// | CodeOrIL | 4 bytes
94+
// ---------------------------
95+
//
96+
// b. Right shift the value by 8 bytes (32 bits)
97+
// c. Mask the slot number field to get its value
98+
99+
long shift = Marshal.ReadInt64(md) >> 32;
100+
ushort mask = 0xfff; // The slot number can be larger than 255, so using a mask of 3 bytes (instead of 2)
101+
int slot = (int)(shift & mask);
102+
103+
Console.WriteLine("\nMethodDesc data : {0}", Marshal.ReadInt64(md).ToString("x").PadLeft(8, '0'));
104+
Console.WriteLine("Right-shift 32 bits : {0}", shift.ToString("x").PadLeft(8, '0'));
105+
Console.WriteLine("Mask : {0}", mask.ToString("x").PadLeft(8, '0'));
106+
Console.WriteLine("Method slot number : {0}", slot);
107+
108+
Console.WriteLine("\nMethodDesc data : {0}", Convert.ToString(Marshal.ReadInt64(md), 2).PadLeft(32, '0'));
109+
Console.WriteLine("Right-shift 32 bits : {0}", Convert.ToString(shift, 2).PadLeft(32, '0'));
110+
Console.WriteLine("Mask : {0}", Convert.ToString(mask, 2).PadLeft(32, '0'));
111+
Console.WriteLine("Method slot number : {0}\n", Convert.ToString(slot, 2).PadLeft(32, '0'));
112+
113+
Console.WriteLine("Relative offset : {0}", (IntPtr.Size * slot).ToString("x").PadLeft(8, '0'));
114+
}
115+
116+
offset = (int)((long)address - (long)mt);
117+
118+
Console.WriteLine("Jitted method (JM) : {0}\t{1}", address.ToString("x").PadLeft(8, '0'),
119+
Marshal.ReadIntPtr(address).ToString("x").PadLeft(8, '0'));
120+
121+
Console.WriteLine("Offset (JM - MT) : {0}", offset);
122+
}
123+
124+
/// <summary>
125+
/// Obtain the unconditional jump address to the JIT-compiled method
126+
/// </summary>
127+
/// <param name="mi"></param>
128+
/// <remarks>
129+
/// Before JIT compilation:
130+
/// - call to PreJITStub to initiate compilation.
131+
/// - the CodeOrIL field contains the Relative Virtual Address (IL RVA) of the method implementation in IL.
132+
///
133+
/// After on-demand JIT compilation:
134+
/// - CRL changes the call to the PreJITStub for an unconditional jump to the JITed method.
135+
/// - the CodeOrIL field contains the Virtual Address (VA) of the JIT-compiled method.
136+
/// </remarks>
137+
/// <returns>The JITed method address</returns>
138+
private static IntPtr GetMethodAddress(MethodInfo mi)
139+
{
140+
const ushort SLOT_NUMBER_MASK = 0xfff; // 3 bytes mask
141+
const int MT_OFFSET_32BIT = 0x28; // 40 bytes
142+
const int MT_OFFSET_64BIT = 0x40; // 64 bytes
143+
144+
IntPtr address;
145+
146+
// JIT compilation of the method
147+
RuntimeHelpers.PrepareMethod(mi.MethodHandle);
148+
149+
IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address
150+
IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address
151+
152+
if (mi.IsVirtual)
153+
{
154+
// The fixed-size portion of the MethodTable structure depends on the process type:
155+
// For 32-bit process (IntPtr.Size == 4), the fixed-size portion is 40 (0x28) bytes
156+
// For 64-bit process (IntPtr.Size == 8), the fixed-size portion is 64 (0x40) bytes
157+
int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;
158+
159+
// First method slot = MethodTable address + fixed-size offset
160+
// This is the address of the first method of any type (i.e. ToString)
161+
IntPtr ms = Marshal.ReadIntPtr(mt + offset);
162+
163+
// Get the slot number of the virtual method entry from the MethodDesc data structure
164+
// Remark: the slot number is represented on 3 bytes
165+
long shift = Marshal.ReadInt64(md) >> 32;
166+
int slot = (int)(shift & SLOT_NUMBER_MASK);
167+
168+
// Get the virtual method address relative to the first method slot
169+
address = ms + (slot * IntPtr.Size);
170+
}
171+
else
172+
{
173+
// Bypass default MethodDescriptor padding (8 bytes)
174+
// Reach the CodeOrIL field which contains the address of the JIT-compiled code
175+
address = md + 8;
176+
}
177+
178+
return address;
179+
}
180+
181+
private static MethodRedirection Redirect(IntPtr ori, IntPtr tar, bool verbose)
182+
{
183+
// Must create the token before address is assigned
184+
var token = new MethodRedirection(ori);
185+
186+
if (verbose)
187+
{
188+
Console.WriteLine("\nRedirect...");
189+
Console.WriteLine("From {0} [{1}] => To {2} [{3}]",
190+
ori.ToString("x").PadLeft(8, '0'),
191+
Marshal.ReadIntPtr(ori).ToString("x").PadLeft(8, '0'),
192+
tar.ToString("x").PadLeft(8, '0'),
193+
Marshal.ReadIntPtr(tar).ToString("x").PadLeft(8, '0'));
194+
}
195+
196+
// Redirect origin method to target method
197+
Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1);
198+
199+
return token;
200+
}
201+
}
202+
}
203+

MethodRedirect/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Nicolas
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MethodRedirect/MethodOperation.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
3+
namespace MethodRedirect
4+
{
5+
/// <summary>
6+
/// Base method operation result
7+
/// </summary>
8+
public abstract class MethodOperation : IDisposable
9+
{
10+
public abstract void Restore();
11+
12+
public void Dispose()
13+
{
14+
Restore();
15+
}
16+
}
17+
18+
/// <summary>
19+
/// Result of a method redirection (Origin => *)
20+
/// </summary>
21+
public class MethodRedirection : MethodOperation
22+
{
23+
public MethodToken Origin { get; private set; }
24+
25+
public MethodRedirection(IntPtr address)
26+
{
27+
Origin = new MethodToken(address);
28+
}
29+
30+
public override void Restore()
31+
{
32+
Origin.Restore();
33+
}
34+
35+
public override string ToString()
36+
{
37+
return Origin.ToString();
38+
}
39+
}
40+
}
41+

MethodRedirect/MethodToken.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace MethodRedirect
5+
{
6+
public struct MethodToken : IDisposable
7+
{
8+
public IntPtr Address { get; private set; }
9+
public IntPtr Value { get; private set; }
10+
11+
public MethodToken(IntPtr address)
12+
{
13+
// On token creation, preserve the address and the current value at this address
14+
Address = address;
15+
Value = Marshal.ReadIntPtr(address);
16+
}
17+
18+
public void Restore()
19+
{
20+
// Restore the value at the address
21+
Marshal.Copy(new IntPtr[] { Value }, 0, Address, 1);
22+
}
23+
24+
public override string ToString()
25+
{
26+
IntPtr met = Address;
27+
IntPtr tar = Marshal.ReadIntPtr(Address);
28+
IntPtr ori = Value;
29+
30+
return "Method address = " + met.ToString("x").PadLeft(8, '0') + "\n" +
31+
"Target address = " + tar.ToString("x").PadLeft(8, '0') + "\n" +
32+
"Origin address = " + ori.ToString("x").PadLeft(8, '0');
33+
}
34+
35+
public void Dispose()
36+
{
37+
Restore();
38+
}
39+
}
40+
}
41+

MethodRedirect/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# MethodRedirect
2+
3+
MethodRedirect is a `MethodInfo` extension written in C# that can be used to redirect a method call to another using reflection.
4+
5+
This implementation uses marshalling to modify the address of the corresponding Method Descriptor without the need to use unsafe block.
6+
7+
Source: [github.com/spinico/MethodRedirect](https://github.com/spinico/MethodRedirect).

0 commit comments

Comments
 (0)