├── .gitignore
├── AirPlay.sln
├── AirPlay
├── AirPlay.csproj
├── AirPlayReceiver.cs
├── AirPlayService.cs
├── Crypto
│ ├── HandGarble.cs
│ ├── ModifiedMD5.cs
│ ├── OmgHax.cs
│ ├── OmgHaxHex.cs
│ └── SapHash.cs
├── DMapTagged
│ └── DMapTagged.cs
├── Decoders
│ ├── IDecoder.cs
│ └── Implementations
│ │ ├── AACDecoder.cs
│ │ ├── ALACDecoder.cs
│ │ └── PCMDecoder.cs
├── IAirPlayReceiver.cs
├── IRtspReceiver.cs
├── Listeners
│ ├── AirTunesListener.cs
│ ├── AudioListener.cs
│ ├── Bases
│ │ ├── BaseListener.cs
│ │ ├── BaseTcpListener.cs
│ │ └── BaseUdpListener.cs
│ ├── MirroringListener.cs
│ └── StreamingListener.cs
├── Models
│ ├── Audio
│ │ ├── PcmData.cs
│ │ ├── RaopBuffer.cs
│ │ └── RaopBufferEntry.cs
│ ├── Configs
│ │ ├── AirPlayReceiverConfig.cs
│ │ ├── CodecLibrariesConfig.cs
│ │ └── DumpConfig.cs
│ ├── Enums
│ │ ├── AudioFormat.cs
│ │ ├── ProtocolType.cs
│ │ ├── RequestConst.cs
│ │ ├── RequestType.cs
│ │ └── StatusCode.cs
│ ├── Mirroring
│ │ ├── H264Codec.cs
│ │ ├── H264Data.cs
│ │ └── MirroringHeader.cs
│ ├── Session.cs
│ └── TcpListeners
│ │ ├── Header.cs
│ │ ├── HeadersCollection.cs
│ │ ├── Request.cs
│ │ └── Response.cs
├── Plist
│ ├── BinaryPlistArray.cs
│ ├── BinaryPlistDictionary.cs
│ ├── BinaryPlistItem.cs
│ ├── BinaryPlistReader.cs
│ ├── BinaryPlistWriter.cs
│ ├── DataContractBinaryPlistSerializer.cs
│ ├── EndianConverter.cs
│ ├── Extensions.cs
│ ├── IPlistSerializable.cs
│ ├── TypeCacheItem.cs
│ └── UniqueValueCache.cs
├── Program.cs
├── Resources
│ ├── table_s1.bin
│ ├── table_s10.bin
│ ├── table_s2.bin
│ ├── table_s3.bin
│ ├── table_s4.bin
│ ├── table_s5.bin
│ ├── table_s6.bin
│ ├── table_s7.bin
│ ├── table_s8.bin
│ └── table_s9.bin
├── Services
│ └── SessionManager.cs
├── Utils
│ ├── Extensions.cs
│ ├── LibraryLoader.cs
│ └── Utilities.cs
├── appsettings_linux.json
├── appsettings_osx.json
└── appsettings_win.json
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # TeamCity is a build add-in
135 | _TeamCity*
136 |
137 | # DotCover is a Code Coverage Tool
138 | *.dotCover
139 |
140 | # AxoCover is a Code Coverage Tool
141 | .axoCover/*
142 | !.axoCover/settings.json
143 |
144 | # Coverlet is a free, cross platform Code Coverage Tool
145 | coverage*.json
146 | coverage*.xml
147 | coverage*.info
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | # Fody - auto-generated XML schema
362 | FodyWeavers.xsd
363 |
364 |
--------------------------------------------------------------------------------
/AirPlay.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28922.388
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirPlay", "AirPlay\AirPlay.csproj", "{2AA2D285-F679-41DF-89CE-ADCCB1CAB828}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7616FC7D-B84A-4F54-B5D4-62E2D80ADCD2}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {2AA2D285-F679-41DF-89CE-ADCCB1CAB828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {2AA2D285-F679-41DF-89CE-ADCCB1CAB828}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {2AA2D285-F679-41DF-89CE-ADCCB1CAB828}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {2AA2D285-F679-41DF-89CE-ADCCB1CAB828}.Release|Any CPU.Build.0 = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(SolutionProperties) = preSolution
22 | HideSolutionNode = FALSE
23 | EndGlobalSection
24 | GlobalSection(ExtensibilityGlobals) = postSolution
25 | SolutionGuid = {321099FB-55E0-4876-B9E3-1A236181C2E6}
26 | EndGlobalSection
27 | EndGlobal
28 |
--------------------------------------------------------------------------------
/AirPlay/AirPlay.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.2
6 |
7 |
8 |
9 | true
10 | false
11 | TRACE;DEBUG;NETCOREAPP;NETCOREAPP2_2;DUMP
12 |
13 |
14 | true
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 | Always
63 |
64 |
65 | Always
66 |
67 |
68 | Always
69 |
70 |
71 | Always
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/AirPlay/AirPlayReceiver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text.RegularExpressions;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using AirPlay.Listeners;
9 | using AirPlay.Models;
10 | using AirPlay.Models.Configs;
11 | using Makaretu.Dns;
12 | using Microsoft.Extensions.Options;
13 |
14 | namespace AirPlay
15 | {
16 | public class AirPlayReceiver : IRtspReceiver, IAirPlayReceiver
17 | {
18 | public event EventHandler OnSetVolumeReceived;
19 | public event EventHandler OnH264DataReceived;
20 | public event EventHandler OnPCMDataReceived;
21 |
22 | public const string AirPlayType = "_airplay._tcp";
23 | public const string AirTunesType = "_raop._tcp";
24 |
25 | private MulticastService _mdns = null;
26 | private AirTunesListener _airTunesListener = null;
27 | private readonly string _instance;
28 | private readonly ushort _airTunesPort;
29 | private readonly ushort _airPlayPort;
30 | private readonly string _deviceId;
31 |
32 | public AirPlayReceiver(IOptions aprConfig, IOptions codecConfig, IOptions dumpConfig)
33 | {
34 | _airTunesPort = aprConfig?.Value?.AirTunesPort ?? 5000;
35 | _airPlayPort = aprConfig?.Value?.AirPlayPort ?? 7000;
36 | _deviceId = aprConfig?.Value?.DeviceMacAddress ?? "11:22:33:44:55:66";
37 | _instance = aprConfig?.Value?.Instance ?? throw new ArgumentNullException("apr.instance");
38 |
39 | var clConfig = codecConfig?.Value ?? throw new ArgumentNullException(nameof(codecConfig));
40 | var dConfig = dumpConfig?.Value ?? throw new ArgumentNullException(nameof(dumpConfig));
41 |
42 | _airTunesListener = new AirTunesListener(this, _airTunesPort, _airPlayPort, clConfig, dConfig);
43 | }
44 |
45 | public async Task StartListeners(CancellationToken cancellationToken)
46 | {
47 | await _airTunesListener.StartAsync(cancellationToken).ConfigureAwait(false);
48 | }
49 |
50 | public Task StartMdnsAsync()
51 | {
52 | if (string.IsNullOrWhiteSpace(_deviceId))
53 | {
54 | throw new ArgumentNullException(_deviceId);
55 | }
56 |
57 | var rDeviceId = new Regex("^(([0-9a-fA-F][0-9a-fA-F]):){5}([0-9a-fA-F][0-9a-fA-F])$");
58 | var mDeviceId = rDeviceId.Match(_deviceId);
59 | if (!mDeviceId.Success)
60 | {
61 | throw new ArgumentException("Device id must be a mac address", _deviceId);
62 | }
63 |
64 | var deviceIdInstance = string.Join(string.Empty, mDeviceId.Groups[2].Captures) + mDeviceId.Groups[3].Value;
65 |
66 | _mdns = new MulticastService();
67 | var sd = new ServiceDiscovery(_mdns);
68 |
69 | foreach (var ip in MulticastService.GetIPAddresses())
70 | {
71 | Console.WriteLine($"IP address {ip}");
72 | }
73 |
74 | _mdns.NetworkInterfaceDiscovered += (s, e) =>
75 | {
76 | foreach (var nic in e.NetworkInterfaces)
77 | {
78 | Console.WriteLine($"NIC '{nic.Name}'");
79 | }
80 | };
81 |
82 | // Internally 'ServiceProfile' create the SRV record
83 | var airTunes = new ServiceProfile($"{deviceIdInstance}@{_instance}", AirTunesType, _airTunesPort);
84 | airTunes.AddProperty("ch", "2");
85 | airTunes.AddProperty("cn", "2,3");
86 | airTunes.AddProperty("et", "0,3,5");
87 | airTunes.AddProperty("md", "0,1,2");
88 | airTunes.AddProperty("sr", "44100");
89 | airTunes.AddProperty("ss", "16");
90 | airTunes.AddProperty("da", "true");
91 | airTunes.AddProperty("sv", "false");
92 | airTunes.AddProperty("ft", "0x5A7FFFF7,0x1E"); // 0x4A7FFFF7, 0xE
93 | airTunes.AddProperty("am", "AppleTV5,3");
94 | airTunes.AddProperty("pk", "29fbb183a58b466e05b9ab667b3c429d18a6b785637333d3f0f3a34baa89f45e");
95 | airTunes.AddProperty("sf", "0x4");
96 | airTunes.AddProperty("tp", "UDP");
97 | airTunes.AddProperty("vn", "65537");
98 | airTunes.AddProperty("vs", "220.68");
99 | airTunes.AddProperty("vv", "2");
100 |
101 | /*
102 | * ch 2 audio channels: stereo
103 | * cn 0,1,2,3 audio codecs
104 | * et 0,3,5 supported encryption types
105 | * md 0,1,2 supported metadata types
106 | * pw false does the speaker require a password?
107 | * sr 44100 audio sample rate: 44100 Hz
108 | * ss 16 audio sample size: 16-bit
109 | */
110 |
111 | // Internally 'ServiceProfile' create the SRV record
112 | var airPlay = new ServiceProfile(_instance, AirPlayType, _airPlayPort);
113 | airPlay.AddProperty("deviceid", _deviceId);
114 | airPlay.AddProperty("features", "0x5A7FFFF7,0x1E"); // 0x4A7FFFF7
115 | airPlay.AddProperty("flags", "0x4");
116 | airPlay.AddProperty("model", "AppleTV5,3");
117 | airPlay.AddProperty("pk", "29fbb183a58b466e05b9ab667b3c429d18a6b785637333d3f0f3a34baa89f45e");
118 | airPlay.AddProperty("pi", "aa072a95-0318-4ec3-b042-4992495877d3");
119 | airPlay.AddProperty("srcvers", "220.68");
120 | airPlay.AddProperty("vv", "2");
121 |
122 | sd.Advertise(airTunes);
123 | sd.Advertise(airPlay);
124 |
125 | _mdns.Start();
126 |
127 | return Task.CompletedTask;
128 | }
129 |
130 | public void OnSetVolume(decimal volume)
131 | {
132 | OnSetVolumeReceived?.Invoke(this, volume);
133 | }
134 |
135 | public void OnData(H264Data data)
136 | {
137 | OnH264DataReceived?.Invoke(this, data);
138 | }
139 |
140 | public void OnPCMData(PcmData data)
141 | {
142 | OnPCMDataReceived?.Invoke(this, data);
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/AirPlay/AirPlayService.cs:
--------------------------------------------------------------------------------
1 | using AirPlay.Models.Configs;
2 | using AirPlay.Utils;
3 | using Microsoft.Extensions.Hosting;
4 | using Microsoft.Extensions.Options;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 |
11 | namespace AirPlay
12 | {
13 | public class AirPlayService : IHostedService, IDisposable
14 | {
15 | private readonly IAirPlayReceiver _airPlayReceiver;
16 | private readonly DumpConfig _dConfig;
17 |
18 | private List _audiobuf;
19 |
20 | public AirPlayService(IAirPlayReceiver airPlayReceiver, IOptions dConfig)
21 | {
22 | _airPlayReceiver = airPlayReceiver ?? throw new ArgumentNullException(nameof(airPlayReceiver));
23 | _dConfig = dConfig?.Value ?? throw new ArgumentNullException(nameof(dConfig));
24 | }
25 |
26 | public async Task StartAsync(CancellationToken cancellationToken)
27 | {
28 | #if DUMP
29 | var bPath = _dConfig.Path;
30 | var fPath = Path.Combine(bPath, "frames/");
31 | var oPath = Path.Combine(bPath, "out/");
32 | var pPath = Path.Combine(bPath, "pcm/");
33 |
34 | if (!Directory.Exists(bPath))
35 | {
36 | Directory.CreateDirectory(bPath);
37 | }
38 | if (!Directory.Exists(fPath))
39 | {
40 | Directory.CreateDirectory(fPath);
41 | }
42 | if (!Directory.Exists(oPath))
43 | {
44 | Directory.CreateDirectory(oPath);
45 | }
46 | if (!Directory.Exists(pPath))
47 | {
48 | Directory.CreateDirectory(pPath);
49 | }
50 | #endif
51 |
52 | await _airPlayReceiver.StartListeners(cancellationToken);
53 | await _airPlayReceiver.StartMdnsAsync().ConfigureAwait(false);
54 |
55 | _airPlayReceiver.OnSetVolumeReceived += (s, e) =>
56 | {
57 | // SET VOLUME
58 | };
59 |
60 | // DUMP H264 VIDEO
61 | _airPlayReceiver.OnH264DataReceived += (s, e) =>
62 | {
63 | // DO SOMETHING WITH VIDEO DATA..
64 | #if DUMP
65 | using (FileStream writer = new FileStream($"{bPath}dump.h264", FileMode.Append))
66 | {
67 | writer.Write(e.Data, 0, e.Length);
68 | }
69 | #endif
70 | };
71 |
72 | _audiobuf = new List();
73 | _airPlayReceiver.OnPCMDataReceived += (s, e) =>
74 | {
75 | // DO SOMETHING WITH AUDIO DATA..
76 | #if DUMP
77 | _audiobuf.AddRange(e.Data);
78 | #endif
79 | };
80 | }
81 |
82 | public Task StopAsync(CancellationToken cancellationToken)
83 | {
84 | #if DUMP
85 | // DUMP WAV AUDIO
86 | var bPath = _dConfig.Path;
87 | using (var wr = new FileStream($"{bPath}dequeued.wav", FileMode.Create))
88 | {
89 | var header = Utilities.WriteWavHeader(2, 44100, 16, (uint)_audiobuf.Count);
90 | wr.Write(header, 0, header.Length);
91 | }
92 |
93 | using (FileStream writer = new FileStream($"{bPath}dequeued.wav", FileMode.Append))
94 | {
95 | writer.Write(_audiobuf.ToArray(), 0, _audiobuf.Count);
96 | }
97 | #endif
98 | return Task.CompletedTask;
99 | }
100 |
101 | public void Dispose()
102 | {
103 |
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/AirPlay/Crypto/ModifiedMD5.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using AirPlay.Utils;
4 |
5 | namespace AirPlay
6 | {
7 | public class ModifiedMD5
8 | {
9 | private int[] _shift = new int[] { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
10 |
11 | public void ModifiedMd5(byte[] originalblockIn, byte[] keyIn, byte[] keyOut)
12 | {
13 | var blockIn = new byte[64];
14 | long A, B, C, D, Z, tmp;
15 |
16 | Array.Copy(originalblockIn, 0, blockIn, 0, 64);
17 |
18 | using (var keyInMem = new MemoryStream(keyIn))
19 | using (var reader = new BinaryReader(keyInMem))
20 | {
21 | A = reader.ReadInt32() & 0xffffffffL;
22 | B = reader.ReadInt32() & 0xffffffffL;
23 | C = reader.ReadInt32() & 0xffffffffL;
24 | D = reader.ReadInt32() & 0xffffffffL;
25 |
26 | for (var i = 0; i < 64; i++)
27 | {
28 | int input;
29 | var j = 0;
30 | if (i < 16)
31 | {
32 | j = i;
33 | }
34 | else if (i < 32)
35 | {
36 | j = (5 * i + 1) % 16;
37 | }
38 | else if (i < 48)
39 | {
40 | j = (3 * i + 5) % 16;
41 | }
42 | else if (i < 64)
43 | {
44 | j = 7 * i % 16;
45 | }
46 |
47 | input = ((blockIn[4 * j] & 0xFF) << 24) | ((blockIn[4 * j + 1] & 0xFF) << 16) | ((blockIn[4 * j + 2] & 0xFF) << 8) | (blockIn[4 * j + 3] & 0xFF);
48 | Z = A + input + (long)((1L << 32) * Math.Abs(Math.Sin(i + 1)));
49 | if (i < 16)
50 | {
51 | Z = Rol(Z + F(B, C, D), _shift[i]);
52 | }
53 | else if (i < 32)
54 | {
55 | Z = Rol(Z + G(B, C, D), _shift[i]);
56 | }
57 | else if (i < 48)
58 | {
59 | Z = Rol(Z + H(B, C, D), _shift[i]);
60 | }
61 | else if (i < 64)
62 | {
63 | Z = Rol(Z + I(B, C, D), _shift[i]);
64 | }
65 | Z = Z + B;
66 | tmp = D;
67 | D = C;
68 | C = B;
69 | B = Z;
70 | A = tmp;
71 | if (i == 31)
72 | {
73 | Utilities.Swap(blockIn, 4 * (int)(A & 15), 4 * (int)(B & 15));
74 | Utilities.Swap(blockIn, 4 * (int)(C & 15), 4 * (int)(D & 15));
75 | Utilities.Swap(blockIn, 4 * (int)((A & (15 << 4)) >> 4), 4 * (int)((B & (15 << 4)) >> 4));
76 | Utilities.Swap(blockIn, 4 * (int)((A & (15 << 8)) >> 8), 4 * (int)((B & (15 << 8)) >> 8));
77 | Utilities.Swap(blockIn, 4 * (int)((A & (15 << 12)) >> 12), 4 * (int)((B & (15 << 12)) >> 12));
78 | }
79 | }
80 |
81 | using (var keyOutMem = new MemoryStream(keyOut))
82 | using (var writer = new BinaryWriter(keyOutMem))
83 | {
84 | keyInMem.Position = 0;
85 | writer.Write((int)(reader.ReadInt32() + A));
86 |
87 | keyInMem.Position = 4;
88 | writer.Write((int)(reader.ReadInt32() + B));
89 |
90 | keyInMem.Position = 8;
91 | writer.Write((int)(reader.ReadInt32() + C));
92 |
93 | keyInMem.Position = 12;
94 | writer.Write((int)(reader.ReadInt32() + D));
95 | }
96 | }
97 | }
98 |
99 | private long F(long B, long C, long D)
100 | {
101 | return (B & C) | (~B & D);
102 | }
103 |
104 | private long G(long B, long C, long D)
105 | {
106 | return (B & D) | (C & ~D);
107 | }
108 |
109 | private long H(long B, long C, long D)
110 | {
111 | return B ^ C ^ D;
112 | }
113 |
114 | private long I(long B, long C, long D)
115 | {
116 | return C ^ (B | ~D);
117 | }
118 |
119 | private long Rol(long input, int count)
120 | {
121 | return ((input << count) & 0xffffffffL) | (input & 0xffffffffL) >> (32 - count);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/AirPlay/Crypto/OmgHaxHex.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace AirPlay
7 | {
8 | public class OmgHaxHex
9 | {
10 | public static byte[] StaticSource1 = new byte[] { 0xFA, 0x9C, 0xAD, 0x4D, 0x4B, 0x68, 0x26, 0x8C, 0x7F, 0xF3, 0x88, 0x99, 0xDE, 0x92, 0x2E, 0x95, 0x1E };
11 | public static byte[] StaticSource2 = new byte[] { 0xEC, 0x4E, 0x27, 0x5E, 0xFD, 0xF2, 0xE8, 0x30, 0x97, 0xAE, 0x70, 0xFB, 0xE0, 0x00, 0x3F, 0x1C, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x09, 0x00, 0x0, 0x00, 0x00, 0x00, 0x00 };
12 | public static byte[] InitialSessionKey = new byte[] { 0xDC, 0xDC, 0xF3, 0xB9, 0x0B, 0x74, 0xDC, 0xFB, 0x86, 0x7F, 0xF7, 0x60, 0x16, 0x72, 0x90, 0x51 };
13 | public static byte[] DefaultSap = new byte[]
14 | {
15 | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
16 | 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
17 | 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x00, 0x00, 0x00, 0x00,
18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
19 | 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
20 | 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
21 | 0x79, 0x79, 0x79, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x53,
23 | 0x00, 0x01, 0xcc, 0x34, 0x2a, 0x5e, 0x5b, 0x1a, 0x67, 0x73, 0xc2, 0x0e, 0x21, 0xb8, 0x22, 0x4d,
24 | 0xf8, 0x62, 0x48, 0x18, 0x64, 0xef, 0x81, 0x0a, 0xae, 0x2e, 0x37, 0x03, 0xc8, 0x81, 0x9c, 0x23,
25 | 0x53, 0x9d, 0xe5, 0xf5, 0xd7, 0x49, 0xbc, 0x5b, 0x7a, 0x26, 0x6c, 0x49, 0x62, 0x83, 0xce, 0x7f,
26 | 0x03, 0x93, 0x7a, 0xe1, 0xf6, 0x16, 0xde, 0x0c, 0x15, 0xff, 0x33, 0x8c, 0xca, 0xff, 0xb0, 0x9e,
27 | 0xaa, 0xbb, 0xe4, 0x0f, 0x5d, 0x5f, 0x55, 0x8f, 0xb9, 0x7f, 0x17, 0x31, 0xf8, 0xf7, 0xda, 0x60,
28 | 0xa0, 0xec, 0x65, 0x79, 0xc3, 0x3e, 0xa9, 0x83, 0x12, 0xc3, 0xb6, 0x71, 0x35, 0xa6, 0x69, 0x4f,
29 | 0xf8, 0x23, 0x05, 0xd9, 0xba, 0x5c, 0x61, 0x5f, 0xa2, 0x54, 0xd2, 0xb1, 0x83, 0x45, 0x83, 0xce,
30 | 0xe4, 0x2d, 0x44, 0x26, 0xc8, 0x35, 0xa7, 0xa5, 0xf6, 0xc8, 0x42, 0x1c, 0x0d, 0xa3, 0xf1, 0xc7,
31 | 0x00, 0x50, 0xf2, 0xe5, 0x17, 0xf8, 0xd0, 0xfa, 0x77, 0x8d, 0xfb, 0x82, 0x8d, 0x40, 0xc7, 0x8e,
32 | 0x94, 0x1e, 0x1e, 0x1e
33 | };
34 | public static byte[] IndexMAngle = new byte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C };
35 |
36 | public static byte[][] MessageKey = new byte[4][] {
37 | new byte[144] { 0x1D, 0x24, 0x03, 0x40, 0xDC, 0xAE, 0xC7, 0xA8, 0x26, 0x7C, 0x20, 0x99, 0x5D, 0x7E, 0x89, 0x2E, 0xA2, 0x58, 0xAF, 0xBE, 0xB8, 0x07, 0x9A, 0x2F, 0x87, 0x77, 0xD3, 0xCE, 0x37, 0x3E, 0x1B, 0x16, 0x41, 0x4F, 0x4E, 0xBE, 0x62, 0x5A, 0x00, 0x77, 0xC6, 0xEB, 0xDA, 0x4B, 0x97, 0x1A, 0x61, 0x8D, 0x31, 0x32, 0x1C, 0xA2, 0x78, 0x9B, 0x66, 0x72, 0x60, 0x94, 0x44, 0x86, 0xCB, 0x09, 0xBD, 0x3A, 0x77, 0x57, 0xC1, 0x72, 0x61, 0x1D, 0x32, 0xC7, 0x85, 0xD1, 0xEF, 0xE5, 0x4D, 0x95, 0x0B, 0xF0, 0xD8, 0x18, 0xE7, 0x4A, 0xDC, 0x77, 0xCA, 0x55, 0x28, 0x32, 0x93, 0x2A, 0x7B, 0x3E, 0x3A, 0xD4, 0x97, 0xFD, 0x7D, 0x6D, 0x95, 0x71, 0x27, 0x9C, 0x77, 0x6A, 0x7C, 0xD5, 0xBF, 0x9D, 0x0E, 0xF2, 0x0F, 0x55, 0x91, 0x29, 0xCF, 0xAA, 0x58, 0x1C, 0x7A, 0xE7, 0xCB, 0x8B, 0x20, 0x07, 0x53, 0xAA, 0x59, 0x40, 0x3B, 0x03, 0xBE, 0x33, 0x47, 0x47, 0x5A, 0x4F, 0x86, 0x31, 0x8D, 0x30, 0xF9, 0x1C },
38 | new byte[144] { 0xF1, 0xA2, 0x04, 0x7A, 0xAE, 0xE9, 0xD8, 0xBF, 0xD4, 0xC0, 0x6B, 0x77, 0xC1, 0x05, 0x8C, 0x99, 0xA9, 0xFD, 0x3D, 0x44, 0xEE, 0x7B, 0x6C, 0x28, 0x42, 0x31, 0x63, 0x87, 0x6D, 0xD2, 0x6D, 0x48, 0xCC, 0x4E, 0x93, 0x31, 0x7B, 0x27, 0x14, 0xFC, 0x2D, 0x71, 0x5D, 0xE4, 0xB0, 0xF9, 0x4B, 0x82, 0x76, 0x52, 0xD5, 0x02, 0x6C, 0xB6, 0xCF, 0x57, 0xFE, 0xB2, 0xBF, 0xB7, 0x30, 0x56, 0x7B, 0x9B, 0x3E, 0x3E, 0xB0, 0x47, 0x10, 0x63, 0xE8, 0x72, 0x1C, 0x38, 0x2D, 0x79, 0xC4, 0x77, 0x3C, 0xD1, 0xED, 0x02, 0x43, 0x03, 0x5C, 0xBC, 0x57, 0x9E, 0x43, 0x02, 0x67, 0xA1, 0x9B, 0x8C, 0xF3, 0x54, 0xE4, 0x46, 0xE1, 0x1C, 0x4F, 0xDC, 0xF7, 0x9F, 0xF4, 0x49, 0x76, 0x4F, 0x13, 0x96, 0x86, 0xCF, 0xF1, 0x7A, 0x01, 0xAC, 0xE4, 0xD5, 0x32, 0x5B, 0x5D, 0x7D, 0xEE, 0xCA, 0xBF, 0x76, 0xFB, 0x50, 0xD7, 0xEC, 0x9C, 0xA3, 0xF6, 0x2E, 0xBE, 0x9B, 0xC7, 0xC8, 0x0F, 0xF2, 0xB7, 0x3B, 0xDE, 0x8A },
39 | new byte[144] { 0x18, 0x6E, 0xD3, 0x73, 0x5E, 0xE9, 0x5A, 0x8F, 0x66, 0x3F, 0xF1, 0xB8, 0x4A, 0x62, 0xD9, 0xC0, 0xD2, 0x08, 0x13, 0x61, 0xCB, 0xF3, 0xAD, 0xA6, 0x26, 0x4D, 0x3A, 0x7B, 0x06, 0xB5, 0x51, 0x56, 0xFE, 0x66, 0x0A, 0xD8, 0x3A, 0xAA, 0x47, 0x49, 0xD3, 0x7C, 0xC3, 0x68, 0x70, 0xD0, 0x96, 0x80, 0x6A, 0x05, 0x90, 0xEF, 0xAF, 0x43, 0x42, 0xC4, 0x2E, 0x50, 0x4C, 0x96, 0x13, 0xB5, 0x2E, 0x4C, 0x80, 0xA2, 0x8D, 0x23, 0xEE, 0xE2, 0x5E, 0x78, 0xF4, 0x3D, 0x65, 0xCA, 0x71, 0x4F, 0x68, 0x9E, 0x4B, 0x43, 0x58, 0x7B, 0x47, 0x96, 0x40, 0x81, 0x8A, 0x98, 0x6C, 0x04, 0x33, 0x0F, 0x2F, 0x1C, 0x33, 0x8E, 0xEA, 0xA1, 0x4F, 0xA8, 0x37, 0x93, 0x17, 0x1D, 0x8D, 0x18, 0xA9, 0x6A, 0x1B, 0x07, 0x7C, 0xB6, 0x08, 0x58, 0x1F, 0x12, 0x00, 0xFA, 0x37, 0x4D, 0x7F, 0xBA, 0xA5, 0x00, 0x6B, 0x72, 0x78, 0x9C, 0x33, 0xE8, 0x41, 0x07, 0xB7, 0xC1, 0x67, 0x9B, 0x76, 0xBB, 0xDD, 0x91, 0x3D, 0x3D },
40 | new byte[144] { 0x47, 0x69, 0x9F, 0x08, 0xB8, 0x82, 0xFB, 0xA1, 0x95, 0xE5, 0x6F, 0x41, 0x79, 0x1E, 0x0C, 0xB6, 0xA1, 0xCA, 0x11, 0x0A, 0xE2, 0x87, 0x2C, 0x7E, 0x39, 0xBC, 0x98, 0xA5, 0x1E, 0xB2, 0xFA, 0x1F, 0xEE, 0x73, 0x42, 0xD7, 0xA9, 0x09, 0x42, 0xC0, 0xEF, 0xC4, 0x44, 0x0C, 0x0F, 0x6F, 0x97, 0x09, 0x08, 0xBC, 0x66, 0x31, 0x33, 0xFF, 0xCA, 0x7E, 0xB5, 0xE9, 0x7D, 0x77, 0x98, 0xC0, 0xD2, 0x6A, 0xFD, 0x2F, 0x0B, 0x6C, 0x9D, 0xAB, 0xAA, 0x78, 0x4C, 0x76, 0xDE, 0x21, 0xBF, 0xF4, 0x3A, 0x28, 0x2A, 0xC4, 0x74, 0xB4, 0xA9, 0x1B, 0x9A, 0x38, 0x21, 0x4C, 0xEB, 0xBD, 0x72, 0x51, 0xA6, 0x15, 0xD4, 0x9E, 0x17, 0xF3, 0x94, 0x26, 0x6D, 0x07, 0x5F, 0x92, 0xAA, 0xA4, 0x4E, 0xF2, 0xCD, 0x3F, 0x02, 0x4F, 0x05, 0x35, 0xE3, 0x58, 0xDF, 0x82, 0x7E, 0x6A, 0x17, 0xF0, 0x5F, 0x6B, 0xDC, 0xE9, 0x3A, 0xCF, 0x04, 0xB3, 0x01, 0x44, 0x87, 0xD7, 0xBC, 0xAD, 0x3D, 0x74, 0x96, 0x74, 0xA3, 0x99 }
41 | };
42 |
43 | public static byte[][] MessageIv = new byte[4][] {
44 | new byte[16] { 0x57, 0x52, 0xF1, 0xB7, 0x54, 0x9D, 0x8F, 0x87, 0x0C, 0x10, 0x48, 0x5A, 0x60, 0x88, 0xCA, 0xDB },
45 | new byte[16] { 0xDF, 0x7B, 0x15, 0x63, 0xF0, 0x05, 0x58, 0x77, 0x52, 0xA9, 0x04, 0x02, 0xB9, 0xA3, 0x92, 0x95 },
46 | new byte[16] { 0x68, 0xB5, 0x46, 0x11, 0xFB, 0x04, 0xDE, 0x67, 0x6C, 0x96, 0x8E, 0xFB, 0x8C, 0x9D, 0xB0, 0xC9 },
47 | new byte[16] { 0x27, 0x07, 0x8B, 0x21, 0x23, 0x36, 0x1E, 0x7A, 0xDC, 0x9D, 0x0B, 0x11, 0x53, 0x54, 0x69, 0x0D }
48 | };
49 |
50 | public static byte[] TKey = new byte[16] { 0xd0, 0x04, 0xa9, 0x61, 0x6b, 0xa4, 0x00, 0x87, 0x68, 0x8b, 0x5f, 0x15, 0x15, 0x35, 0xd9, 0xa9 };
51 | public static byte[] XKey = new byte[16] { 0x8e, 0xba, 0x07, 0xcc, 0xb6, 0x5a, 0xf6, 0x20, 0x33, 0xcf, 0xf8, 0x42, 0xe5, 0xd5, 0x5a, 0x7d };
52 | public static byte[] ZKey = new byte[16] { 0x1a, 0x64, 0xf9, 0x60, 0x6c, 0xe3, 0x01, 0xa9, 0x54, 0x48, 0x1b, 0xd4, 0xab, 0x81, 0xfc, 0xc6 };
53 |
54 | public static byte[] TableS1;
55 | public static byte[] TableS2;
56 | public static byte[] TableS3;
57 | public static byte[] TableS4;
58 | public static int[] TableS5;
59 | public static int[] TableS6;
60 | public static int[] TableS7;
61 | public static int[] TableS8;
62 | public static int[] TableS9;
63 | public static byte[] TableS10;
64 |
65 | static OmgHaxHex()
66 | {
67 | TableS1 = ReadTableBytes("table_s1");
68 | TableS2 = ReadTableBytes("table_s2");
69 | TableS3 = ReadTableBytes("table_s3");
70 | TableS4 = ReadTableBytes("table_s4");
71 | TableS5 = ReadTableInts("table_s5");
72 | TableS6 = ReadTableInts("table_s6");
73 | TableS7 = ReadTableInts("table_s7");
74 | TableS8 = ReadTableInts("table_s8");
75 | TableS9 = ReadTableInts("table_s9");
76 | TableS10 = ReadTableBytes("table_s10");
77 | }
78 |
79 | private static byte[] ReadTableBytes(string key)
80 | {
81 | var assembly = Assembly.GetExecutingAssembly();
82 | using(var s = assembly.GetManifestResourceStream($"AirPlay.Resources.{key}.bin"))
83 | using (StreamReader reader = new StreamReader(s))
84 | {
85 | var data = reader.ReadToEnd();
86 | var hexs = data.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(h => h.Replace("0x", string.Empty).Trim()).ToArray();
87 |
88 | var result = new byte[hexs.Length];
89 | for (int i = 0; i < hexs.Length; i++)
90 | {
91 | result[i] = Convert.ToByte(hexs[i], 16);
92 | }
93 |
94 | return result;
95 | }
96 | }
97 |
98 | private static int[] ReadTableInts(string key)
99 | {
100 | var assembly = Assembly.GetExecutingAssembly();
101 | using (var s = assembly.GetManifestResourceStream($"AirPlay.Resources.{key}.bin"))
102 | using (StreamReader reader = new StreamReader(s))
103 | {
104 | var data = reader.ReadToEnd();
105 | var hexs = data.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(h => h.Replace("0x", string.Empty).Trim()).ToArray();
106 |
107 | var result = new int[hexs.Length];
108 | for (int i = 0; i < hexs.Length; i++)
109 | {
110 | result[i] = Convert.ToInt32(hexs[i], 16);
111 | }
112 |
113 | return result;
114 | }
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/AirPlay/Crypto/SapHash.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace AirPlay
5 | {
6 | public class SapHash
7 | {
8 | private HandGarble _handGarble = new HandGarble();
9 |
10 | public void Hash(byte[] blockIn, byte[] keyOut)
11 | {
12 | var buffer0 = new byte[20] { 0x96, 0x5F, 0xC6, 0x53, 0xF8, 0x46, 0xCC, 0x18, 0xDF, 0xBE, 0xB2, 0xF8, 0x38, 0xD7, 0xEC, 0x22, 0x03, 0xD1, 0x20, 0x8F };
13 | var buffer1 = new byte[210];
14 | var buffer2 = new byte[35] { 0x43, 0x54, 0x62, 0x7A, 0x18, 0xC3, 0xD6, 0xB3, 0x9A, 0x56, 0xF6, 0x1C, 0x14, 0x3F, 0x0C, 0x1D, 0x3B, 0x36, 0x83, 0xB1, 0x39, 0x51, 0x4A, 0xAA, 0x09, 0x3E, 0xFE, 0x44, 0xAF, 0xDE, 0xC3, 0x20, 0x9D, 0x42, 0x3A };
15 | var buffer3 = new byte[132];
16 | var buffer4 = new byte[21] { 0xED, 0x25, 0xD1, 0xBB, 0xBC, 0x27, 0x9F, 0x02, 0xA2, 0xA9, 0x11, 0x00, 0x0C, 0xB3, 0x52, 0xC0, 0xBD, 0xE3, 0x1B, 0x49, 0xC7 };
17 | var i0_index = new int[11] { 18, 22, 23, 0, 5, 19, 32, 31, 10, 21, 30 };
18 |
19 | byte w, x, y, z;
20 |
21 | using (var mem = new MemoryStream(blockIn))
22 | using (var reader = new BinaryReader(mem))
23 | {
24 | for (int i = 0; i < 210; i++)
25 | {
26 | mem.Position = ((i % 64) >> 2) * 4;
27 | int in_word = reader.ReadInt32();
28 | byte in_byte = (byte)((in_word >> ((3 - (i % 4)) << 3)) & 0xff);
29 | buffer1[i] = in_byte;
30 | }
31 |
32 | for (int i = 0; i < 840; i++)
33 | {
34 | x = buffer1[(int)(((i - 155) & 0xffffffffL) % 210)];
35 | y = buffer1[(int)(((i - 57) & 0xffffffffL) % 210)];
36 | z = buffer1[(int)(((i - 13) & 0xffffffffL) % 210)];
37 | w = buffer1[(int)((i & 0xffffffffL) % 210)];
38 | buffer1[i % 210] = (byte)((Rol8(y, 5) + (Rol8(z, 3) ^ w) - Rol8(x, 7)) & 0xff);
39 | }
40 |
41 | _handGarble.Garble(buffer0, buffer1, buffer2, buffer3, buffer4);
42 |
43 | for (int i = 0; i < 16; i++)
44 | {
45 | keyOut[i] = (byte)0xE1;
46 | }
47 |
48 | for (int i = 0; i < 11; i++)
49 | {
50 | if (i == 3)
51 | {
52 | keyOut[i] = 0x3d;
53 | }
54 | else
55 | {
56 | keyOut[i] = (byte)((keyOut[i] + buffer3[i0_index[i] * 4]) & 0xff);
57 | }
58 | }
59 |
60 | for (int i = 0; i < 20; i++)
61 | {
62 | keyOut[i % 16] ^= buffer0[i];
63 | }
64 |
65 | for (int i = 0; i < 35; i++)
66 | {
67 | keyOut[i % 16] ^= buffer2[i];
68 | }
69 |
70 | for (int i = 0; i < 210; i++)
71 | {
72 | keyOut[(i % 16)] ^= buffer1[i];
73 | }
74 |
75 | for (int j = 0; j < 16; j++)
76 | {
77 | for (int i = 0; i < 16; i++)
78 | {
79 | x = keyOut[(int)(((i - 7) & 0xffffffffL) % 16)];
80 | y = keyOut[i % 16];
81 | z = keyOut[(int)(((i - 37) & 0xffffffffL) % 16)];
82 | w = keyOut[(int)(((i - 177) & 0xffffffffL) % 16)];
83 | keyOut[i] = (byte)(Rol8(x, 1) ^ y ^ Rol8(z, 6) ^ Rol8(w, 5));
84 | }
85 | }
86 | }
87 | }
88 |
89 | private byte Rol8(byte input, int count)
90 | {
91 | return (byte)(((input << count) & 0xff) | (input & 0xff) >> (8 - count));
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/AirPlay/Decoders/IDecoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using AirPlay.Models.Enums;
4 |
5 | namespace AirPlay
6 | {
7 | public interface IDecoder
8 | {
9 | AudioFormat Type { get; }
10 | int GetOutputStreamLength();
11 | int Config(int sampleRate, int channels, int bitDepth, int frameLength);
12 | int DecodeFrame(byte[] input, ref byte[] output, int length);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AirPlay/Decoders/Implementations/ALACDecoder.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * I have mapped only used methods.
3 | * This code does not have all 'ALAC Decoder' functionality
4 | */
5 |
6 | using System;
7 | using System.IO;
8 | using System.Runtime.InteropServices;
9 | using AirPlay.Models.Enums;
10 | using AirPlay.Utils;
11 |
12 | namespace AirPlay
13 | {
14 | public unsafe class ALACDecoder : IDecoder, IDisposable
15 | {
16 | private IntPtr _handle;
17 | private IntPtr _decoder;
18 |
19 | private delegate IntPtr alacDecoder_InitializeDecoder(int sampleRate, int channels, int bitsPerSample, int framesPerPacket);
20 | private delegate int alacDecoder_DecodeFrame(IntPtr decoder, IntPtr inBuffer, IntPtr outBuffer, int* ioNumBytes);
21 |
22 | private alacDecoder_InitializeDecoder _alacDecoder_InitializeDecoder;
23 | private alacDecoder_DecodeFrame _alacDecoder_DecodeFrame;
24 |
25 | private int _pcm_pkt_size = 0;
26 |
27 | public AudioFormat Type => AudioFormat.ALAC;
28 |
29 | public ALACDecoder(string libraryPath)
30 | {
31 | if (!File.Exists(libraryPath))
32 | {
33 | throw new IOException("Library not found.");
34 | }
35 |
36 | // Open library
37 | _handle = LibraryLoader.DlOpen(libraryPath, 0);
38 |
39 | // Get a function pointer symbol
40 | IntPtr symAlacDecoder_InitializeDecoder = LibraryLoader.DlSym(_handle, "InitializeDecoder");
41 | IntPtr symAlacDecoder_DecodeFrame = LibraryLoader.DlSym(_handle, "Decode");
42 |
43 | // Get a delegate for the function pointer
44 | _alacDecoder_InitializeDecoder = Marshal.GetDelegateForFunctionPointer(symAlacDecoder_InitializeDecoder);
45 | _alacDecoder_DecodeFrame = Marshal.GetDelegateForFunctionPointer(symAlacDecoder_DecodeFrame);
46 | }
47 |
48 | public int Config(int sampleRate, int channels, int bitDepth, int frameLength)
49 | {
50 | _pcm_pkt_size = frameLength * channels * bitDepth / 8;
51 |
52 | _decoder = _alacDecoder_InitializeDecoder(sampleRate, channels, bitDepth, frameLength);
53 |
54 | return _decoder != IntPtr.Zero ? 0 : -1;
55 | }
56 |
57 | public int GetOutputStreamLength()
58 | {
59 | return _pcm_pkt_size;
60 | }
61 |
62 | public int DecodeFrame(byte[] input, ref byte[] output, int outputLen)
63 | {
64 | var size = Marshal.SizeOf(input[0]) * input.Length;
65 | var inputPtr = Marshal.AllocHGlobal(size);
66 | Marshal.Copy(input, 0, inputPtr, input.Length);
67 |
68 | var outSize = Marshal.SizeOf(output[0]) * output.Length;
69 | var outPtr = Marshal.AllocHGlobal(outSize);
70 |
71 | var res = _alacDecoder_DecodeFrame(_decoder, inputPtr, outPtr, &outputLen);
72 | if(res == 0)
73 | {
74 | Marshal.Copy(outPtr, output, 0, outputLen);
75 | }
76 |
77 | return res;
78 | }
79 |
80 | public void Dispose()
81 | {
82 | // Close the C++ library
83 | LibraryLoader.DlClose(_handle);
84 | Marshal.FreeBSTR(_handle);
85 | }
86 | }
87 |
88 | public struct MagicCookie
89 | {
90 | public ALACSpecificConfig config;
91 | public ALACAudioChannelLayout channelLayoutInfo; // seems to be unused
92 | }
93 |
94 | public struct ALACSpecificConfig
95 | {
96 | public uint frameLength;
97 | public byte compatibleVersion;
98 | public byte bitDepth; // max 32
99 | public byte pb; // 0 <= pb <= 255
100 | public byte mb;
101 | public byte kb;
102 | public byte numChannels;
103 | public ushort maxRun;
104 | public uint maxFrameBytes;
105 | public uint avgBitRate;
106 | public uint sampleRate;
107 | }
108 |
109 | public struct ALACAudioChannelLayout
110 | {
111 | public uint mChannelLayoutTag;
112 | public uint mChannelBitmap;
113 | public uint mNumberChannelDescriptions;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/AirPlay/Decoders/Implementations/PCMDecoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AirPlay.Models.Enums;
3 |
4 | namespace AirPlay
5 | {
6 | public class PCMDecoder : IDecoder
7 | {
8 | public AudioFormat Type => AudioFormat.PCM;
9 |
10 | public int Config(int sampleRate, int channels, int bitDepth, int frameLength)
11 | {
12 | return 0;
13 | }
14 |
15 | public int DecodeFrame(byte[] input, ref byte[] output, int length)
16 | {
17 | Array.Copy(input, 0, output, 0, input.Length);
18 | return 0;
19 | }
20 |
21 | public int GetOutputStreamLength()
22 | {
23 | return -1;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/AirPlay/IAirPlayReceiver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using AirPlay.Models;
5 |
6 | namespace AirPlay
7 | {
8 | public interface IAirPlayReceiver
9 | {
10 | event EventHandler OnSetVolumeReceived;
11 | event EventHandler OnH264DataReceived;
12 | event EventHandler OnPCMDataReceived;
13 |
14 | Task StartListeners(CancellationToken cancellationToken);
15 |
16 | Task StartMdnsAsync();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/AirPlay/IRtspReceiver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using AirPlay.Models;
5 |
6 | namespace AirPlay
7 | {
8 | public interface IRtspReceiver
9 | {
10 | void OnSetVolume(decimal volume);
11 | void OnData(H264Data data);
12 | void OnPCMData(PcmData data);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AirPlay/Listeners/Bases/BaseListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace AirPlay
6 | {
7 | public abstract class BaseListener
8 | {
9 | public abstract Task StartAsync(CancellationToken cancellationToken);
10 |
11 | public abstract Task StopAsync();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AirPlay/Listeners/Bases/BaseTcpListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Sockets;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using AirPlay.Models;
12 | using AirPlay.Models.Enums;
13 |
14 | namespace AirPlay.Listeners
15 | {
16 | public class BaseTcpListener : BaseListener
17 | {
18 | private readonly ushort _port;
19 | private readonly TcpListener _listener;
20 | private readonly ConcurrentDictionary _connections;
21 | private readonly bool _rawData = false;
22 | private readonly CancellationTokenSource _cancellationTokenSource;
23 |
24 | public BaseTcpListener(ushort port, bool rawData = false)
25 | {
26 | _port = port;
27 | _rawData = rawData;
28 | _listener = new TcpListener(IPAddress.Any, _port);
29 | _connections = new ConcurrentDictionary();
30 |
31 | _cancellationTokenSource = new CancellationTokenSource();
32 | }
33 |
34 | public override Task StartAsync(CancellationToken cancellationToken)
35 | {
36 | var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
37 |
38 | Task.Run(() => AcceptClientsAsync(source.Token), source.Token);
39 | return Task.CompletedTask;
40 | }
41 |
42 | public override Task StopAsync()
43 | {
44 | _cancellationTokenSource.Cancel();
45 | return Task.CompletedTask;
46 | }
47 |
48 | public virtual Task OnDataReceivedAsync(Request request, Response response, CancellationToken cancellationToken)
49 | {
50 | return Task.CompletedTask;
51 | }
52 |
53 | public virtual Task OnRawReceivedAsync(TcpClient client, NetworkStream stream, CancellationToken cancellationToken)
54 | {
55 | return Task.CompletedTask;
56 | }
57 |
58 | private async Task AcceptClientsAsync(CancellationToken cancellationToken)
59 | {
60 | _listener.Start();
61 |
62 | while (!cancellationToken.IsCancellationRequested)
63 | {
64 | var client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
65 | var task = HandleClientAsync(client, cancellationToken);
66 |
67 | var remoteEndpoint = client.Client.RemoteEndPoint.ToString();
68 | if (!_connections.TryAdd(remoteEndpoint, task))
69 | {
70 | client.Close();
71 | }
72 | }
73 | }
74 |
75 | private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
76 | {
77 | try
78 | {
79 | var remoteEndpoint = client.Client.RemoteEndPoint.ToString();
80 | var stream = client.GetStream();
81 | Console.WriteLine($"Client connected: {remoteEndpoint}");
82 |
83 | if (_rawData)
84 | {
85 | await OnRawReceivedAsync(client, stream, cancellationToken).ConfigureAwait(false);
86 | }
87 | else
88 | {
89 | await ReadFormattedAsync(client, stream, cancellationToken).ConfigureAwait(false);
90 | }
91 |
92 | if (client.Connected)
93 | {
94 | client.Close();
95 | }
96 |
97 | Console.WriteLine($"Client disconnected: {remoteEndpoint}");
98 |
99 | _connections.Remove(remoteEndpoint, out _);
100 | }
101 | catch (System.Exception ex)
102 | {
103 | Console.WriteLine(ex);
104 | }
105 | }
106 |
107 | private async Task ReadFormattedAsync(TcpClient client, NetworkStream stream, CancellationToken cancellationToken)
108 | {
109 | if (client.Connected && stream.CanRead)
110 | {
111 | int retBytes = 0;
112 | string raw = string.Empty;
113 |
114 | do
115 | {
116 | try
117 | {
118 | var buffer = new byte[1024];
119 | var readCount = stream.Read(buffer, 0, buffer.Length);
120 | retBytes += readCount;
121 | raw += string.Join(string.Empty, buffer.Take(readCount).Select(b => b.ToString("X2")));
122 |
123 | // Wait for other possible data
124 | await Task.Delay(10);
125 | }
126 | catch (System.IO.IOException) { }
127 | } while (client.Connected && stream.DataAvailable);
128 |
129 | // Now we have all data inside raw var
130 | // Make sure the socket is still connected (if it closed we don't really care about the message because we can not reply)
131 | if (client.Connected)
132 | {
133 | // That listener can accept only this methods: GET | POST | SETUP | GET_PARAMETER | RECORD | SET_PARAMETER | ANNOUNCE | FLUSH | TEARDOWN | OPTIONS | PAUSE
134 | // Because of the persistent connection we might receive more than one request at a time
135 | // I'm using a regex to find all request by 'magic numbers' (ex. GET, POST, SETUP, ecc)
136 |
137 | var pattern =
138 | $"^{RequestConst.GET}[.]*|" +
139 | $"^{RequestConst.POST}[.]*|" +
140 | $"^{RequestConst.SETUP}[.]*|" +
141 | $"^{RequestConst.GET_PARAMETER}[.]*|" +
142 | $"^{RequestConst.RECORD}[.]*|" +
143 | $"^{RequestConst.SET_PARAMETER}[.]*|" +
144 | $"^{RequestConst.ANNOUNCE}[.]*|" +
145 | $"^{RequestConst.FLUSH}[.]*|" +
146 | $"^{RequestConst.OPTIONS}[.]*|" +
147 | $"^{RequestConst.PAUSE}[.]*|" +
148 | $"^{RequestConst.TEARDOWN}[.]*";
149 |
150 | var r = new Regex(pattern, RegexOptions.Multiline);
151 | var m = r.Matches(raw);
152 |
153 | // Split requests and create models
154 | var requests = new List();
155 | for (int i = 0; i < m.Count; i++)
156 | {
157 | if (i + 1 < m.Count)
158 | {
159 | var hexReq = raw.Substring(m[i].Index, m[i + 1].Index - m[i].Index);
160 | var req = new Request(hexReq);
161 | requests.Add(req);
162 | }
163 | else
164 | {
165 | var hexReq = raw.Substring(m[i].Index);
166 | var req = new Request(hexReq);
167 | requests.Add(req);
168 | }
169 | }
170 |
171 | foreach (var request in requests)
172 | {
173 | var response = request.GetBaseResponse();
174 |
175 | await OnDataReceivedAsync(request, response, cancellationToken).ConfigureAwait(false);
176 | await SendResponseAsync(stream, response);
177 | }
178 | }
179 |
180 | // If we have read some bytes, leave connection open and wait for next message
181 | if (retBytes != 0)
182 | {
183 | await ReadFormattedAsync(client, stream, cancellationToken);
184 | }
185 | }
186 | }
187 |
188 | private async Task SendResponseAsync(NetworkStream stream, Response response)
189 | {
190 | var format = $"{response.GetProtocol()} {(int)response.StatusCode} {response.StatusCode.ToString().ToUpperInvariant()}\r\n";
191 |
192 | foreach (var header in response.Headers)
193 | {
194 | format += $"{header.Name}: {string.Join(",", header.Values)}\r\n";
195 | }
196 |
197 | // Ready for body
198 | format += "\r\n";
199 |
200 | var formatBuffer = Encoding.ASCII.GetBytes(format);
201 |
202 | byte[] payload;
203 | var bodyBuffer = await response.ReadAsync();
204 | if (bodyBuffer?.Any() == true)
205 | {
206 | payload = formatBuffer.Concat(bodyBuffer).ToArray();
207 | }
208 | else
209 | {
210 | payload = formatBuffer;
211 | }
212 |
213 | try
214 | {
215 | stream.Write(payload, 0, payload.Length);
216 | stream.Flush();
217 | }
218 | catch (System.IO.IOException e)
219 | {
220 | //
221 | }
222 | }
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/AirPlay/Listeners/Bases/BaseUdpListener.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace AirPlay.Listeners
7 | {
8 | public class BaseUdpListener : BaseListener
9 | {
10 | public const int CloseTimeout = 1000;
11 |
12 | private readonly Socket _cSocket;
13 | private readonly Socket _dSocket;
14 | private readonly CancellationTokenSource _cancellationTokenSource;
15 |
16 | public BaseUdpListener(ushort cPort, ushort dPort)
17 | {
18 | _cSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
19 | _dSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
20 |
21 | _cSocket.Bind(new IPEndPoint(IPAddress.Any, cPort));
22 | _dSocket.Bind(new IPEndPoint(IPAddress.Any, dPort));
23 |
24 | _cancellationTokenSource = new CancellationTokenSource();
25 | }
26 |
27 | public override Task StartAsync(CancellationToken cancellationToken)
28 | {
29 | var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
30 |
31 | Task.Run(() => OnRawCSocketAsync(_cSocket, source.Token), source.Token);
32 | Task.Run(() => OnRawDSocketAsync(_dSocket, source.Token), source.Token);
33 |
34 | return Task.CompletedTask;
35 | }
36 |
37 | public override Task StopAsync()
38 | {
39 | _cancellationTokenSource.Cancel();
40 |
41 | _cSocket.Close(CloseTimeout);
42 | _dSocket.Close(CloseTimeout);
43 |
44 | return Task.CompletedTask;
45 | }
46 |
47 | public virtual Task OnRawCSocketAsync(Socket cSocket, CancellationToken cancellationToken)
48 | {
49 | return Task.CompletedTask;
50 | }
51 |
52 | public virtual Task OnRawDSocketAsync(Socket dSocket, CancellationToken cancellationToken)
53 | {
54 | return Task.CompletedTask;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/AirPlay/Listeners/MirroringListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using AirPlay.Models;
7 | using AirPlay.Services.Implementations;
8 | using AirPlay.Utils;
9 | using Org.BouncyCastle.Crypto;
10 | using Org.BouncyCastle.Crypto.Parameters;
11 | using Org.BouncyCastle.Security;
12 |
13 | namespace AirPlay.Listeners
14 | {
15 | public class MirroringListener : BaseTcpListener
16 | {
17 | public const string AIR_PLAY_STREAM_KEY = "AirPlayStreamKey";
18 | public const string AIR_PLAY_STREAM_IV = "AirPlayStreamIV";
19 |
20 | private readonly IRtspReceiver _receiver;
21 | private readonly string _sessionId;
22 | private readonly IBufferedCipher _aesCtrDecrypt;
23 | private readonly OmgHax _omgHax = new OmgHax();
24 |
25 | private byte[] _og = new byte[16];
26 | private int _nextDecryptCount;
27 |
28 | public MirroringListener(IRtspReceiver receiver, string sessionId, ushort port) : base(port, true)
29 | {
30 | _receiver = receiver;
31 | _sessionId = sessionId;
32 |
33 | _aesCtrDecrypt = CipherUtilities.GetCipher("AES/CTR/NoPadding");
34 | }
35 |
36 | public override async Task OnRawReceivedAsync(TcpClient client, NetworkStream stream, CancellationToken cancellationToken)
37 | {
38 | // Get session by active-remove header value
39 | var session = await SessionManager.Current.GetSessionAsync(_sessionId);
40 |
41 | // If we have not decripted session AesKey
42 | if (session.DecryptedAesKey == null)
43 | {
44 | byte[] decryptedAesKey = new byte[16];
45 | _omgHax.DecryptAesKey(session.KeyMsg, session.AesKey, decryptedAesKey);
46 | session.DecryptedAesKey = decryptedAesKey;
47 | }
48 |
49 | InitAesCtrCipher(session.DecryptedAesKey, session.EcdhShared, session.StreamConnectionId);
50 |
51 | var headerBuffer = new byte[128];
52 | var readStart = 0;
53 |
54 | do
55 | {
56 | MirroringHeader header;
57 | if (stream.DataAvailable)
58 | {
59 | var ret = await stream.ReadAsync(headerBuffer, readStart, 4 - readStart);
60 | readStart += ret;
61 | if (readStart < 4)
62 | {
63 | continue;
64 | }
65 |
66 | if ((headerBuffer[0] == 80 && headerBuffer[1] == 79 && headerBuffer[2] == 83 && headerBuffer[3] == 84) || (headerBuffer[0] == 71 && headerBuffer[1] == 69 && headerBuffer[2] == 84))
67 | {
68 | // Request is POST or GET (skip)
69 | }
70 | else
71 | {
72 | do
73 | {
74 | ret = await stream.ReadAsync(headerBuffer, readStart, 128 - readStart);
75 | if (ret <= 0)
76 | {
77 | break;
78 | }
79 | readStart += ret;
80 | } while (readStart < 128);
81 |
82 | header = new MirroringHeader(headerBuffer);
83 |
84 | if (!session.Pts.HasValue)
85 | {
86 | session.Pts = header.PayloadPts;
87 | }
88 | if (!session.WidthSource.HasValue)
89 | {
90 | session.WidthSource = header.WidthSource;
91 | }
92 | if (!session.HeightSource.HasValue)
93 | {
94 | session.HeightSource = header.HeightSource;
95 | }
96 |
97 | if (header != null && stream.DataAvailable)
98 | {
99 | try
100 | {
101 | byte[] payload = (byte[])Array.CreateInstance(typeof(byte), header.PayloadSize);
102 |
103 | readStart = 0;
104 | do
105 | {
106 | ret = await stream.ReadAsync(payload, readStart, header.PayloadSize - readStart);
107 | readStart += ret;
108 | } while (readStart < header.PayloadSize);
109 |
110 | if (header.PayloadType == 0)
111 | {
112 | DecryptVideoData(payload, out byte[] output);
113 | ProcessVideo(output, session.SpsPps, session.Pts.Value, session.WidthSource.Value, session.HeightSource.Value);
114 | }
115 | else if (header.PayloadType == 1)
116 | {
117 | ProcessSpsPps(payload, out byte[] spsPps);
118 | session.SpsPps = spsPps;
119 | }
120 | else
121 | {
122 | // SKIP
123 | }
124 | }
125 | catch (Exception e)
126 | {
127 | Console.WriteLine(e);
128 | }
129 | }
130 |
131 | await Task.Delay(10);
132 |
133 | // Save current session
134 | await SessionManager.Current.CreateOrUpdateSessionAsync(_sessionId, session);
135 | }
136 | }
137 |
138 | // Fix issue #24
139 | await Task.Delay(1);
140 | readStart = 0;
141 | header = null;
142 | headerBuffer = new byte[128];
143 | } while (client.Connected && stream.CanRead && !cancellationToken.IsCancellationRequested);
144 |
145 | Console.WriteLine($"Closing mirroring connection..");
146 | }
147 |
148 | private void DecryptVideoData(byte[] videoData, out byte[] output)
149 | {
150 | if (_nextDecryptCount > 0)
151 | {
152 | for (int i = 0; i < _nextDecryptCount; i++)
153 | {
154 | videoData[i] = (byte)(videoData[i] ^ _og[(16 - _nextDecryptCount) + i]);
155 | }
156 | }
157 |
158 | int encryptlen = ((videoData.Length - _nextDecryptCount) / 16) * 16;
159 | _aesCtrDecrypt.ProcessBytes(videoData, _nextDecryptCount, encryptlen, videoData, _nextDecryptCount);
160 | Array.Copy(videoData, _nextDecryptCount, videoData, _nextDecryptCount, encryptlen);
161 |
162 | int restlen = (videoData.Length - _nextDecryptCount) % 16;
163 | int reststart = videoData.Length - restlen;
164 | _nextDecryptCount = 0;
165 | if (restlen > 0)
166 | {
167 | Array.Fill(_og, (byte)0);
168 | Array.Copy(videoData, reststart, _og, 0, restlen);
169 | _aesCtrDecrypt.ProcessBytes(_og, 0, 16, _og, 0);
170 | Array.Copy(_og, 0, videoData, reststart, restlen);
171 | _nextDecryptCount = 16 - restlen;
172 | }
173 |
174 | output = new byte[videoData.Length];
175 | Array.Copy(videoData, 0, output, 0, videoData.Length);
176 |
177 | // Release video data
178 | videoData = null;
179 | }
180 |
181 | private void InitAesCtrCipher(byte[] aesKey, byte[] ecdhShared, string streamConnectionId)
182 | {
183 | byte[] eaesKey = Utilities.Hash(aesKey, ecdhShared);
184 |
185 | byte[] skey = Encoding.UTF8.GetBytes($"{AIR_PLAY_STREAM_KEY}{streamConnectionId}");
186 | byte[] hash1 = Utilities.Hash(skey, Utilities.CopyOfRange(eaesKey, 0, 16));
187 |
188 | byte[] siv = Encoding.UTF8.GetBytes($"{AIR_PLAY_STREAM_IV}{streamConnectionId}");
189 | byte[] hash2 = Utilities.Hash(siv, Utilities.CopyOfRange(eaesKey, 0, 16));
190 |
191 | byte[] decryptAesKey = new byte[16];
192 | byte[] decryptAesIV = new byte[16];
193 | Array.Copy(hash1, 0, decryptAesKey, 0, 16);
194 | Array.Copy(hash2, 0, decryptAesIV, 0, 16);
195 |
196 | var keyParameter = ParameterUtilities.CreateKeyParameter("AES", decryptAesKey);
197 | var cipherParameters = new ParametersWithIV(keyParameter, decryptAesIV, 0, decryptAesIV.Length);
198 |
199 | _aesCtrDecrypt.Init(false, cipherParameters);
200 | }
201 |
202 | private void ProcessVideo(byte[] payload, byte[] spsPps, long pts, int widthSource, int heightSource)
203 | {
204 | int nalu_size = 0;
205 | while (nalu_size < payload.Length)
206 | {
207 | int nc_len = (payload[nalu_size + 3] & 0xFF) | ((payload[nalu_size + 2] & 0xFF) << 8) | ((payload[nalu_size + 1] & 0xFF) << 16) | ((payload[nalu_size] & 0xFF) << 24);
208 | if (nc_len > 0)
209 | {
210 | payload[nalu_size] = 0;
211 | payload[nalu_size + 1] = 0;
212 | payload[nalu_size + 2] = 0;
213 | payload[nalu_size + 3] = 1;
214 | nalu_size += nc_len + 4;
215 | }
216 | if (payload.Length - nc_len > 4)
217 | {
218 | return;
219 | }
220 | }
221 |
222 | if (spsPps.Length != 0)
223 | {
224 | var h264Data = new H264Data();
225 | h264Data.FrameType = payload[4] & 0x1f;
226 | if (h264Data.FrameType == 5)
227 | {
228 | var payloadOut = (byte[])Array.CreateInstance(typeof(byte), payload.Length + spsPps.Length);
229 |
230 | Array.Copy(spsPps, 0, payloadOut, 0, spsPps.Length);
231 | Array.Copy(payload, 0, payloadOut, spsPps.Length, payload.Length);
232 |
233 | h264Data.Data = payloadOut;
234 | h264Data.Length = payload.Length + spsPps.Length;
235 |
236 | // Release payload
237 | payload = null;
238 | }
239 | else
240 | {
241 | h264Data.Data = payload;
242 | h264Data.Length = payload.Length;
243 | }
244 |
245 | h264Data.Pts = pts;
246 | h264Data.Width = widthSource;
247 | h264Data.Height = heightSource;
248 |
249 | _receiver.OnData(h264Data);
250 | }
251 | }
252 |
253 | private void ProcessSpsPps(byte[] payload, out byte[] spsPps)
254 | {
255 | var h264 = new H264Codec();
256 |
257 | h264.Version = payload[0];
258 | h264.ProfileHigh = payload[1];
259 | h264.Compatibility = payload[2];
260 | h264.Level = payload[3];
261 | h264.Reserved6AndNal = payload[4];
262 | h264.Reserved3AndSps = payload[5];
263 | h264.LengthOfSps = (short)(((payload[6] & 255) << 8) + (payload[7] & 255));
264 |
265 | var sequence = new byte[h264.LengthOfSps];
266 | Array.Copy(payload, 8, sequence, 0, h264.LengthOfSps);
267 | h264.SequenceParameterSet = sequence;
268 | h264.NumberOfPps = payload[h264.LengthOfSps + 8];
269 | h264.LengthOfPps = (short)(((payload[h264.LengthOfSps + 9] & 2040) + payload[h264.LengthOfSps + 10]) & 255);
270 |
271 | var picture = new byte[h264.LengthOfPps];
272 | Array.Copy(payload, h264.LengthOfSps + 11, picture, 0, h264.LengthOfPps);
273 | h264.PictureParameterSet = picture;
274 |
275 | if (h264.LengthOfSps + h264.LengthOfPps < 102400)
276 | {
277 | var spsPpsLen = h264.LengthOfSps + h264.LengthOfPps + 8;
278 | spsPps = new byte[spsPpsLen];
279 |
280 | spsPps[0] = 0;
281 | spsPps[1] = 0;
282 | spsPps[2] = 0;
283 | spsPps[3] = 1;
284 |
285 | Array.Copy(h264.SequenceParameterSet, 0, spsPps, 4, h264.LengthOfSps);
286 |
287 | spsPps[h264.LengthOfSps + 4] = 0;
288 | spsPps[h264.LengthOfSps + 5] = 0;
289 | spsPps[h264.LengthOfSps + 6] = 0;
290 | spsPps[h264.LengthOfSps + 7] = 1;
291 |
292 | Array.Copy(h264.PictureParameterSet, 0, spsPps, h264.LengthOfSps + 8, h264.LengthOfPps);
293 | }
294 | else
295 | {
296 | spsPps = null;
297 | }
298 | }
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/AirPlay/Listeners/StreamingListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using AirPlay.Models;
10 | using AirPlay.Models.Enums;
11 | using AirPlay.Services.Implementations;
12 | using AirPlay.Utils;
13 | using org.whispersystems.curve25519;
14 |
15 | namespace AirPlay.Listeners
16 | {
17 | public class StreamingListener : BaseTcpListener
18 | {
19 | public const string PAIR_VERIFY_AES_KEY = "Pair-Verify-AES-Key";
20 | public const string PAIR_VERIFY_AES_IV = "Pair-Verify-AES-IV";
21 |
22 | private readonly IRtspReceiver _receiver;
23 | private readonly string _sessionId;
24 | private readonly byte[] _expandedPrivateKey;
25 |
26 | public StreamingListener(IRtspReceiver receiver, string sessionId, byte[] expandedPrivateKey, ushort port) : base(port, false)
27 | {
28 | _receiver = receiver;
29 | _sessionId = sessionId;
30 | _expandedPrivateKey = expandedPrivateKey;
31 | }
32 |
33 | public override async Task OnDataReceivedAsync(Request request, Response response, CancellationToken cancellationToken)
34 | {
35 | // INFO: We must use the same ED25519 KeyPair created inside AirTunesListener
36 | // INFO: We must use the same sessionId to retrieve
37 |
38 | var session = await SessionManager.Current.GetSessionAsync(_sessionId);
39 |
40 | if (request.Type == RequestType.GET && "/server-info".Equals(request.Path, StringComparison.OrdinalIgnoreCase))
41 | {
42 | var xml = @"
43 |
44 |
45 |
46 | deviceid
47 | 10:20:30:40:50:60
48 | features
49 | 0x5A7FFFF7,0x1E
50 | model
51 | AppleTV5,3
52 | protovers
53 | 1.0
54 | srcvers
55 | 220.68
56 |
57 | ";
58 |
59 | var bytes = Encoding.ASCII.GetBytes(xml);
60 | await response.WriteAsync(bytes);
61 | }
62 | if (request.Type == RequestType.POST && "/pair-verify".Equals(request.Path, StringComparison.OrdinalIgnoreCase))
63 | {
64 | using (var mem = new MemoryStream(request.Body))
65 | using (var reader = new BinaryReader(mem))
66 | {
67 | // Request: 68 bytes (the first 4 bytes are 01 00 00 00)
68 | // Client request packet remaining 64 bytes of content
69 | // 01 00 00 00 -> use 01 as flag to check type of verify
70 | // If flag is 1:
71 | // 32 bytes ecdh_their
72 | // 32 bytes ed_their
73 | // If flag is 0:
74 | // 64 bytes signature
75 |
76 | var flag = reader.ReadByte();
77 | if (flag > 0)
78 | {
79 | reader.ReadBytes(3);
80 | session.EcdhTheirs = reader.ReadBytes(32);
81 | session.EdTheirs = reader.ReadBytes(32);
82 |
83 | var curve25519 = Curve25519.getInstance(Curve25519.BEST);
84 | var curve25519KeyPair = curve25519.generateKeyPair();
85 |
86 | session.EcdhOurs = curve25519KeyPair.getPublicKey();
87 | var ecdhPrivateKey = curve25519KeyPair.getPrivateKey();
88 |
89 | session.EcdhShared = curve25519.calculateAgreement(ecdhPrivateKey, session.EcdhTheirs);
90 |
91 | var aesCtr128Encrypt = Utilities.InitializeChiper(session.EcdhShared);
92 |
93 | byte[] dataToSign = new byte[64];
94 | Array.Copy(session.EcdhOurs, 0, dataToSign, 0, 32);
95 | Array.Copy(session.EcdhTheirs, 0, dataToSign, 32, 32);
96 |
97 | var signature = Chaos.NaCl.Ed25519.Sign(dataToSign, _expandedPrivateKey);
98 |
99 | byte[] encryptedSignature = aesCtr128Encrypt.DoFinal(signature);
100 |
101 | byte[] output = new byte[session.EcdhOurs.Length + encryptedSignature.Length];
102 | Array.Copy(session.EcdhOurs, 0, output, 0, session.EcdhOurs.Length);
103 | Array.Copy(encryptedSignature, 0, output, session.EcdhOurs.Length, encryptedSignature.Length);
104 |
105 | response.Headers.Add("Content-Type", "application/octet-stream");
106 | await response.WriteAsync(output, 0, output.Length).ConfigureAwait(false);
107 | }
108 | else
109 | {
110 | reader.ReadBytes(3);
111 | var signature = reader.ReadBytes(64);
112 |
113 | var aesCtr128Encrypt = Utilities.InitializeChiper(session.EcdhShared);
114 |
115 | var signatureBuffer = new byte[64];
116 | signatureBuffer = aesCtr128Encrypt.ProcessBytes(signatureBuffer);
117 | signatureBuffer = aesCtr128Encrypt.DoFinal(signature);
118 |
119 | byte[] messageBuffer = new byte[64];
120 | Array.Copy(session.EcdhTheirs, 0, messageBuffer, 0, 32);
121 | Array.Copy(session.EcdhOurs, 0, messageBuffer, 32, 32);
122 |
123 | session.PairVerified = Chaos.NaCl.Ed25519.Verify(signatureBuffer, messageBuffer, session.EdTheirs);
124 |
125 | Console.WriteLine($"PairVerified: {session.PairVerified}");
126 | }
127 | }
128 | }
129 | if (request.Type == RequestType.POST && "/play".Equals(request.Path, StringComparison.OrdinalIgnoreCase))
130 | {
131 | using (var mem = new MemoryStream(request.Body))
132 | using (var reader = new StreamReader(mem, Encoding.ASCII))
133 | {
134 | var data = await reader.ReadToEndAsync().ConfigureAwait(false);
135 | var dict = data.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(kv =>
136 | {
137 | var splitted = kv.Split(": ", StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()).ToArray();
138 | return new KeyValuePair(splitted[0], splitted[1]);
139 | }).ToDictionary(k => k.Key, v => v.Value);
140 |
141 | var startAt = dict.TryGetValue("Start-Position", out string dStartAt) ? decimal.Parse(dStartAt) : 0M;
142 | var url = dict.TryGetValue("Content-Location", out string dUrl) ? dUrl : throw new ArgumentNullException(nameof(dUrl));
143 |
144 | // DO SOMETHING HERE...
145 |
146 | var from = 0;
147 |
148 | using(var client = new HttpClient())
149 | {
150 | var to = from + 1024;
151 | client.DefaultRequestHeaders.Add("Range", $"bytes={from}-{to}");
152 |
153 | var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
154 | if(result.IsSuccessStatusCode)
155 | {
156 | var length = result.Headers.TryGetValues("Content-Length", out IEnumerable cLength) ? int.Parse(cLength.FirstOrDefault() ?? "0") : 0;
157 | var accept = result.Headers.TryGetValues("Accept-Ranges", out IEnumerable cAccept) ? cAccept.FirstOrDefault() : null;
158 |
159 | var bytes = await result.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
160 |
161 | _receiver.OnData(new H264Data
162 | {
163 | Data = bytes
164 | });
165 | }
166 |
167 | from = to;
168 | }
169 | }
170 | }
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/AirPlay/Models/Audio/PcmData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models
3 | {
4 | public struct PcmData
5 | {
6 | public int Length { get; set; }
7 | public byte[] Data { get; set; }
8 | public ulong Pts { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/AirPlay/Models/Audio/RaopBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AirPlay.Listeners;
3 |
4 | namespace AirPlay.Models
5 | {
6 | public class RaopBuffer
7 | {
8 | public bool IsEmpty { get; set; }
9 | public ushort FirstSeqNum { get; set; }
10 | public ushort LastSeqNum { get; set; }
11 |
12 | public RaopBufferEntry[] Entries = new RaopBufferEntry[AudioListener.RAOP_BUFFER_LENGTH];
13 |
14 | public int BufferSize { get; set; }
15 | public byte[] Buffer { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AirPlay/Models/Audio/RaopBufferEntry.cs:
--------------------------------------------------------------------------------
1 | namespace AirPlay.Models
2 | {
3 | public struct RaopBufferEntry
4 | {
5 | public bool Available { get; set; }
6 |
7 | public byte Flags { get; set; }
8 | public byte Type { get; set; }
9 | public ushort SeqNum { get; set; }
10 | public uint TimeStamp { get; set; }
11 | public uint SSrc { get; set; }
12 |
13 | public int AudioBufferSize { get; set; }
14 | public int AudioBufferLen { get; set; }
15 | public byte[] AudioBuffer { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AirPlay/Models/Configs/AirPlayReceiverConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace AirPlay.Models.Configs
6 | {
7 | public class AirPlayReceiverConfig
8 | {
9 | public string Instance { get; set; }
10 | public ushort AirTunesPort { get; set; }
11 | public ushort AirPlayPort { get; set; }
12 | public string DeviceMacAddress { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AirPlay/Models/Configs/CodecLibrariesConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace AirPlay.Models.Configs
6 | {
7 | public class CodecLibrariesConfig
8 | {
9 | public string AACLibPath { get; set; }
10 | public string ALACLibPath { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AirPlay/Models/Configs/DumpConfig.cs:
--------------------------------------------------------------------------------
1 | namespace AirPlay.Models.Configs
2 | {
3 | public class DumpConfig
4 | {
5 | public string Path { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/AirPlay/Models/Enums/AudioFormat.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models.Enums
3 | {
4 | public enum AudioFormat
5 | {
6 | Unknown = -1,
7 | PCM = 0,
8 | ALAC = 0x40000,
9 | AAC = 0x400000,
10 | AAC_ELD = 0x1000000
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AirPlay/Models/Enums/ProtocolType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models.Enums
3 | {
4 | public enum ProtocolType
5 | {
6 | HTTP10 = 0,
7 | HTTP11 = 1,
8 | RTSP10 = 2,
9 | }
10 |
11 | public class ProtocolConst
12 | {
13 | public const string HTTP10 = "485454502F312E30";
14 | public const string HTTP11 = "485454502F312E31";
15 | public const string RTSP10 = "525453502F312E30";
16 | }
17 | }
--------------------------------------------------------------------------------
/AirPlay/Models/Enums/RequestConst.cs:
--------------------------------------------------------------------------------
1 | namespace AirPlay.Models.Enums
2 | {
3 | public class RequestConst
4 | {
5 | public const string GET = "47455420";
6 | public const string POST = "504F535420";
7 | public const string SETUP = "534554555020";
8 | public const string ANNOUNCE = "414E4E4F554E4345";
9 | public const string RECORD = "5245434F524420";
10 | public const string GET_PARAMETER = "4745545F504152414D4554455220";
11 | public const string SET_PARAMETER = "5345545F504152414D4554455220";
12 | public const string FLUSH = "464C555348";
13 | public const string TEARDOWN = "54454152444F574E";
14 | public const string OPTIONS = "4F5054494F4E53";
15 | public const string PAUSE = "5041555345";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AirPlay/Models/Enums/RequestType.cs:
--------------------------------------------------------------------------------
1 | namespace AirPlay.Models.Enums
2 | {
3 | public enum RequestType : ushort
4 | {
5 | GET = 0,
6 | POST = 1,
7 | SETUP = 2,
8 | GET_PARAMETER = 3,
9 | RECORD = 4,
10 | SET_PARAMETER = 5,
11 | ANNOUNCE = 6,
12 | FLUSH = 7,
13 | TEARDOWN = 8,
14 | OPTIONS = 9,
15 | PAUSE = 10
16 | }
17 | }
--------------------------------------------------------------------------------
/AirPlay/Models/Enums/StatusCode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models.Enums
3 | {
4 | public enum StatusCode
5 | {
6 | OK = 200,
7 | NOCONTENT = 201,
8 | BADREQUEST = 400,
9 | UNAUTHORIZED = 401,
10 | FORBIDDEN = 403,
11 | INTERNALSERVERERROR = 500
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AirPlay/Models/Mirroring/H264Codec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models
3 | {
4 | public struct H264Codec
5 | {
6 | public byte Compatibility;
7 | public short LengthOfPps;
8 | public short LengthOfSps;
9 | public byte Level;
10 | public short NumberOfPps;
11 | public byte[] PictureParameterSet;
12 | public byte ProfileHigh;
13 | public byte Reserved3AndSps;
14 | public byte Reserved6AndNal;
15 | public byte[] SequenceParameterSet;
16 | public byte Version;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/AirPlay/Models/Mirroring/H264Data.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace AirPlay.Models
3 | {
4 | public struct H264Data
5 | {
6 | public int FrameType { get; set; }
7 | public byte[] Data { get; set; }
8 | public int Length { get; set; }
9 | public long Pts { get; set; }
10 | public int Width { get; set; }
11 | public int Height { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AirPlay/Models/Mirroring/MirroringHeader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace AirPlay.Models
5 | {
6 | public class MirroringHeader
7 | {
8 | public int PayloadSize { get; }
9 | public short PayloadType { get; }
10 | public short PayloadOption { get; }
11 | public long PayloadNtp { get; }
12 | public long PayloadPts { get; }
13 | public int WidthSource { get; }
14 | public int HeightSource { get; }
15 | public int Width { get; }
16 | public int Height { get; }
17 |
18 | public MirroringHeader(byte[] header)
19 | {
20 | var mem = new MemoryStream(header);
21 | using(var reader = new BinaryReader(mem))
22 | {
23 | PayloadSize = (int)reader.ReadUInt32();
24 | PayloadType = (short)(reader.ReadUInt16() & 0xff);
25 | PayloadOption = (short)reader.ReadUInt16();
26 |
27 | if(PayloadType == 0)
28 | {
29 | PayloadNtp = (long)reader.ReadUInt64();
30 | PayloadPts = NtpToPts(PayloadNtp);
31 | }
32 | if (PayloadType == 1)
33 | {
34 | mem.Position = 40;
35 | WidthSource = (int)reader.ReadSingle();
36 | HeightSource = (int)reader.ReadSingle();
37 |
38 | mem.Position = 56;
39 | Width = (int)reader.ReadSingle();
40 | Height = (int)reader.ReadSingle();
41 | }
42 | }
43 | }
44 |
45 | private long NtpToPts(long ntp)
46 | {
47 | return (((ntp >> 32) & 0xffffffff) * 1000000) + ((ntp & 0xffffffff) * 1000 * 1000 / Int32.MaxValue);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/AirPlay/Models/Session.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AirPlay.Listeners;
3 | using AirPlay.Models.Enums;
4 |
5 | namespace AirPlay.Models
6 | {
7 | public class Session
8 | {
9 | private string _sessionId;
10 |
11 | public Session(string sessionId)
12 | {
13 | _sessionId = sessionId;
14 | }
15 |
16 | public string SessionId => _sessionId;
17 |
18 | public byte[] EcdhOurs { get; set; } = null;
19 | public byte[] EcdhTheirs { get; set; } = null;
20 | public byte[] EdTheirs { get; set; } = null;
21 | public byte[] EcdhShared { get; set; } = null;
22 |
23 | public bool? PairVerified { get; set; } = null;
24 |
25 | public byte[] KeyMsg { get; set; } = null;
26 | public byte[] AesKey { get; set; } = null;
27 | public byte[] AesIv { get; set; } = null;
28 | public string StreamConnectionId { get; set; } = null;
29 | public AudioFormat AudioFormat { get; set; } = AudioFormat.Unknown;
30 |
31 | public MirroringListener MirroringListener = null;
32 | public StreamingListener StreamingListener = null;
33 | public AudioListener AudioControlListener = null;
34 |
35 | public byte[] DecryptedAesKey { get; set; } = null;
36 | public byte[] SpsPps { get; set; } = null;
37 | public long? Pts { get; set; } = null;
38 | public int? WidthSource { get; set; } = null;
39 | public int? HeightSource { get; set; } = null;
40 |
41 | public bool? MirroringSession = null;
42 |
43 | public bool PairCompleted => EcdhShared != null && (PairVerified ?? false);
44 | public bool FairPlaySetupCompleted => KeyMsg != null && EcdhShared != null && (PairVerified ?? false);
45 | public bool FairPlayReady => KeyMsg != null && EcdhShared != null && AesKey != null && AesIv != null;
46 | public bool MirroringSessionReady => StreamConnectionId != null && MirroringSession.HasValue ? MirroringSession.Value : false;
47 | public bool AudioSessionReady => AudioFormat != AudioFormat.Unknown;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/AirPlay/Models/TcpListeners/Header.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using AirPlay.Utils;
6 |
7 | namespace AirPlay.Models
8 | {
9 | public class Header
10 | {
11 | private readonly string _hex;
12 |
13 | private bool _valid = true;
14 | private string _name;
15 | private List _values;
16 |
17 | public bool IsValid => _valid;
18 | public string Name => _name;
19 | public IEnumerable Values => _values;
20 |
21 | public Header(string hex)
22 | {
23 | _hex = hex ?? throw new ArgumentNullException(nameof(hex));
24 |
25 | Initialize();
26 | }
27 |
28 | public Header(string name, string value)
29 | {
30 | _name = name;
31 | _values = value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(v => v.Trim()).ToList();
32 | }
33 |
34 | public Header(string name, params string[] values)
35 | {
36 | _name = name;
37 | _values = values.ToList();
38 | }
39 |
40 | private void Initialize ()
41 | {
42 | // Split hex by ':' (3A)
43 | var data = _hex.Split("3A", StringSplitOptions.RemoveEmptyEntries).Select(h => h.Trim()).ToArray();
44 | if (data?.Any() == false)
45 | {
46 | _valid = false;
47 | return;
48 | }
49 |
50 | _name = ResolveName(data[0]);
51 | var hValue = data[1];
52 |
53 | _values = ResolveValues(hValue);
54 | }
55 |
56 | private string ResolveName(string hex)
57 | {
58 | var bytes = hex.HexToBytes();
59 | return Encoding.ASCII.GetString(bytes);
60 | }
61 |
62 | private List ResolveValues(string hex)
63 | {
64 | // Split hex by ',' (2C)
65 | var vals = hex.Split("2C", StringSplitOptions.RemoveEmptyEntries).ToArray();
66 |
67 | var _values = new List();
68 |
69 | foreach (var val in vals)
70 | {
71 | var bytes = val.HexToBytes();
72 | var hVal = Encoding.ASCII.GetString(bytes).Trim();
73 | _values.Add(hVal);
74 | }
75 |
76 | return _values;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/AirPlay/Models/TcpListeners/HeadersCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Linq;
6 |
7 | namespace AirPlay.Models
8 | {
9 | public class HeadersCollection : IEnumerable
10 | {
11 | private Dictionary _headers;
12 |
13 | public string this[string key]
14 | {
15 | get
16 | {
17 | return string.Join(",", _headers[key].Values);
18 | }
19 | set
20 | {
21 | _headers.Add(key, new Header(key, value));
22 | }
23 | }
24 |
25 | public ICollection Keys => _headers.Keys;
26 |
27 | public ICollection Values => _headers.Values.Select(v => string.Join(",", v.Values)).ToList();
28 |
29 | public int Count => _headers.Count;
30 |
31 | public bool IsReadOnly => throw new NotImplementedException();
32 |
33 |
34 | public HeadersCollection()
35 | {
36 | _headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
37 | }
38 |
39 | public T GetValue(string key)
40 | {
41 | var value = this[key];
42 |
43 | var typeConverter = TypeDescriptor.GetConverter(typeof(T));
44 | return (T)typeConverter.ConvertFromString(value);
45 | }
46 |
47 | public IEnumerable GetValues(string key)
48 | {
49 | var typeConverter = TypeDescriptor.GetConverter(typeof(T));
50 | var values = _headers[key].Values;
51 |
52 | var vals = new List();
53 | foreach (var value in values)
54 | {
55 | vals.Add((T)typeConverter.ConvertFromString(value));
56 | }
57 |
58 | return vals;
59 | }
60 |
61 | public void Add(string key, string value)
62 | {
63 | _headers.Add(key, new Header(key, value));
64 | }
65 |
66 | public void Add(string key, Header value)
67 | {
68 | _headers.Add(key, value);
69 | }
70 |
71 | public bool ContainsKey(string key)
72 | {
73 | return _headers.ContainsKey(key);
74 | }
75 |
76 | public void Clear()
77 | {
78 | _headers.Clear();
79 | }
80 |
81 | public IEnumerator GetEnumerator()
82 | {
83 | return _headers.Values.GetEnumerator();
84 | }
85 |
86 | IEnumerator IEnumerable.GetEnumerator()
87 | {
88 | return this.GetEnumerator();
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/AirPlay/Models/TcpListeners/Request.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 | using AirPlay.Models.Enums;
8 | using AirPlay.Utils;
9 |
10 | namespace AirPlay.Models
11 | {
12 | public class Request
13 | {
14 | public const string AIRTUNES_SERVER_VERSION = "AirTunes/220.68";
15 |
16 | private readonly string _hex;
17 |
18 | private bool _valid = true;
19 | private RequestType _type;
20 | private string _path;
21 | private ProtocolType _protocol;
22 | private byte[] _rawBody;
23 | private HeadersCollection _headers;
24 |
25 | public bool IsValid => _valid;
26 | public RequestType Type => _type;
27 | public string Path => _path;
28 | public ProtocolType Protocol => _protocol;
29 | public byte[] Body => _rawBody;
30 | public HeadersCollection Headers => _headers;
31 |
32 | public Request(string hex)
33 | {
34 | _hex = hex ?? throw new ArgumentNullException(nameof(hex));
35 | _headers = new HeadersCollection();
36 |
37 | Initialize();
38 | }
39 |
40 | public Task GetFullRawAsync()
41 | {
42 | return Task.FromResult(_hex.HexToBytes());
43 | }
44 |
45 | private void Initialize ()
46 | {
47 | // Split hex by '\r\n' (0D0A)
48 | var rows = _hex.Split("0D0A", StringSplitOptions.None).ToArray();
49 |
50 | if(rows?.Any() == false)
51 | {
52 | _valid = false;
53 | return;
54 | }
55 |
56 | var type = ResolveRequestType(rows[0]);
57 | if(!type.HasValue)
58 | {
59 | _valid = false;
60 | return;
61 | }
62 |
63 | var path = ResolvePath(rows[0]);
64 | if (string.IsNullOrWhiteSpace(path))
65 | {
66 | _valid = false;
67 | return;
68 | }
69 |
70 | var protocol = ResolveProtocol(rows[0]);
71 | if (!protocol.HasValue)
72 | {
73 | _valid = false;
74 | return;
75 | }
76 |
77 | _type = type.Value;
78 | _path = path;
79 | _protocol = protocol.Value;
80 |
81 | foreach (var header in rows.Skip(1))
82 | {
83 | if (string.IsNullOrWhiteSpace(header))
84 | {
85 | // End of headers
86 | break;
87 | }
88 |
89 | var h = ResolveHeader(header);
90 | _headers.Add(h.Name, h);
91 | }
92 |
93 | if(_headers.ContainsKey("Content-Length"))
94 | {
95 | var contentLength = _headers.GetValue("Content-Length");
96 | if(contentLength > 0)
97 | {
98 | // Some request can have body w/ '\r\n' chars
99 | // Use full hex request to extract body based on 'Content-Length'
100 | var requestBytes = _hex.HexToBytes();
101 | var bodyBytes = requestBytes.Skip(requestBytes.Length - contentLength).ToArray();
102 |
103 | if (contentLength == bodyBytes.Length)
104 | {
105 | _rawBody = bodyBytes;
106 | }
107 | else
108 | {
109 | throw new Exception("wrong body length");
110 | }
111 | }
112 | else
113 | {
114 | _rawBody = new byte[0];
115 | }
116 | }
117 | }
118 |
119 | private RequestType? ResolveRequestType (string hex)
120 | {
121 | if (hex.StartsWith(RequestConst.GET, StringComparison.OrdinalIgnoreCase))
122 | {
123 | return RequestType.GET;
124 | }
125 | if (hex.StartsWith(RequestConst.POST, StringComparison.OrdinalIgnoreCase))
126 | {
127 | return RequestType.POST;
128 | }
129 | if (hex.StartsWith(RequestConst.SETUP, StringComparison.OrdinalIgnoreCase))
130 | {
131 | return RequestType.SETUP;
132 | }
133 | if (hex.StartsWith(RequestConst.RECORD, StringComparison.OrdinalIgnoreCase))
134 | {
135 | return RequestType.RECORD;
136 | }
137 | if (hex.StartsWith(RequestConst.GET_PARAMETER, StringComparison.OrdinalIgnoreCase))
138 | {
139 | return RequestType.GET_PARAMETER;
140 | }
141 | if (hex.StartsWith(RequestConst.SET_PARAMETER, StringComparison.OrdinalIgnoreCase))
142 | {
143 | return RequestType.SET_PARAMETER;
144 | }
145 | if (hex.StartsWith(RequestConst.OPTIONS, StringComparison.OrdinalIgnoreCase))
146 | {
147 | return RequestType.OPTIONS;
148 | }
149 | if (hex.StartsWith(RequestConst.ANNOUNCE, StringComparison.OrdinalIgnoreCase))
150 | {
151 | return RequestType.ANNOUNCE;
152 | }
153 | if (hex.StartsWith(RequestConst.FLUSH, StringComparison.OrdinalIgnoreCase))
154 | {
155 | return RequestType.FLUSH;
156 | }
157 | if (hex.StartsWith(RequestConst.TEARDOWN, StringComparison.OrdinalIgnoreCase))
158 | {
159 | return RequestType.TEARDOWN;
160 | }
161 | if (hex.StartsWith(RequestConst.PAUSE, StringComparison.OrdinalIgnoreCase))
162 | {
163 | return RequestType.PAUSE;
164 | }
165 |
166 | return null;
167 | }
168 |
169 | private string ResolvePath (string hex)
170 | {
171 | var r = new Regex("20(.*)20");
172 | var m = r.Match(hex);
173 |
174 | if (m.Success)
175 | {
176 | var pathHex = m.Groups[1].Value;
177 | var pathBytes = pathHex.HexToBytes();
178 | return Encoding.ASCII.GetString(pathBytes);
179 | }
180 |
181 | return null;
182 | }
183 |
184 | private ProtocolType? ResolveProtocol(string hex)
185 | {
186 | if (hex.EndsWith(ProtocolConst.HTTP10, StringComparison.OrdinalIgnoreCase))
187 | {
188 | return ProtocolType.HTTP10;
189 | }
190 | if (hex.EndsWith(ProtocolConst.HTTP11, StringComparison.OrdinalIgnoreCase))
191 | {
192 | return ProtocolType.HTTP11;
193 | }
194 | if (hex.EndsWith(ProtocolConst.RTSP10, StringComparison.OrdinalIgnoreCase))
195 | {
196 | return ProtocolType.RTSP10;
197 | }
198 |
199 | return null;
200 | }
201 |
202 | public Response GetBaseResponse()
203 | {
204 | var response = new Response(_protocol, StatusCode.OK);
205 | response.Headers.Add("Server", AIRTUNES_SERVER_VERSION);
206 | if(_headers.ContainsKey("CSeq"))
207 | {
208 | response.Headers.Add("CSeq", _headers["CSeq"]);
209 | }
210 |
211 | return response;
212 | }
213 |
214 | private Header ResolveHeader(string hex)
215 | {
216 | return new Header(hex);
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/AirPlay/Models/TcpListeners/Response.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using AirPlay.Models.Enums;
10 |
11 | namespace AirPlay.Models
12 | {
13 | public class Response
14 | {
15 | private ProtocolType _protocol;
16 | private StatusCode _statusCode = StatusCode.OK;
17 | private MemoryStream _responseStream;
18 | private HeadersCollection _headers;
19 |
20 | public ProtocolType Protocol { get => _protocol; set => _protocol = value; }
21 | public StatusCode StatusCode { get => _statusCode; set => _statusCode = value; }
22 | public HeadersCollection Headers => _headers;
23 |
24 | public Response()
25 | {
26 | _responseStream = new MemoryStream();
27 | _headers = new HeadersCollection();
28 | }
29 |
30 | public Response(ProtocolType protocol) : this()
31 | {
32 | _protocol = protocol;
33 | }
34 |
35 | public Response(ProtocolType protocol, StatusCode statusCode) : this(protocol)
36 | {
37 | _statusCode = statusCode;
38 | }
39 |
40 | public Task WriteAsync(byte[] buffer)
41 | {
42 | return WriteAsync(buffer, 0, buffer.Length);
43 | }
44 |
45 | public Task WriteAsync(byte[] buffer, int index, int count)
46 | {
47 | using (var writer = new BinaryWriter(_responseStream, Encoding.ASCII))
48 | {
49 | writer.Write(buffer, index, count);
50 | }
51 |
52 | _headers.Add("Content-Length", count.ToString());
53 |
54 | return Task.CompletedTask;
55 | }
56 |
57 | public Task ReadAsync()
58 | {
59 | return Task.FromResult(_responseStream.ToArray());
60 | }
61 |
62 | public string GetProtocol()
63 | {
64 | switch (_protocol)
65 | {
66 | case ProtocolType.HTTP10:
67 | return "HTTP/1.0";
68 | case ProtocolType.HTTP11:
69 | return "HTTP/1.1";
70 | case ProtocolType.RTSP10:
71 | return "RTSP/1.0";
72 | default:
73 | return null;
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/AirPlay/Plist/BinaryPlistArray.cs:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------
2 | //
3 | // Copyright (c) 2011 Chad Burggraf.
4 | // Inspired by BinaryPListParser.java, copyright (c) 2005 Werner Randelshofer
5 | // http://www.java2s.com/Open-Source/Java-Document/Swing-Library/jide-common/com/jidesoft/plaf/aqua/BinaryPListParser.java.htm
6 | //
7 | //-----------------------------------------------------------------------
8 |
9 | namespace System.Runtime.Serialization.Plists
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics.CodeAnalysis;
14 | using System.Text;
15 |
16 | ///
17 | /// Represents an array value in a binary plist.
18 | ///
19 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")]
20 | internal class BinaryPlistArray
21 | {
22 | ///
23 | /// Initializes a new instance of the BinaryPlistArray class.
24 | ///
25 | /// A reference to the binary plist's object table.
26 | public BinaryPlistArray(IList objectTable)
27 | : this(objectTable, 0)
28 | {
29 | }
30 |
31 | ///
32 | /// Initializes a new instance of the BinaryPlistArray class.
33 | ///
34 | /// A reference to the binary plist's object table.
35 | /// The size of the array.
36 | public BinaryPlistArray(IList objectTable, int size)
37 | {
38 | this.ObjectReference = new List(size);
39 | this.ObjectTable = objectTable;
40 | }
41 |
42 | ///
43 | /// Gets the array's object reference collection.
44 | ///
45 | public IList ObjectReference { get; private set; }
46 |
47 | ///
48 | /// Gets a reference to the binary plist's object table.
49 | ///
50 | public IList ObjectTable { get; private set; }
51 |
52 | ///
53 | /// Converts this instance into an array.
54 | ///
55 | /// The array representation of this instance.
56 | public object[] ToArray()
57 | {
58 | object[] array = new object[this.ObjectReference.Count];
59 | int objectRef;
60 | object objectValue;
61 | BinaryPlistArray innerArray;
62 | BinaryPlistDictionary innerDict;
63 |
64 | for (int i = 0; i < array.Length; i++)
65 | {
66 | objectRef = this.ObjectReference[i];
67 |
68 | if (objectRef >= 0 && objectRef < this.ObjectTable.Count && (this.ObjectTable[objectRef] == null || this.ObjectTable[objectRef].Value != this))
69 | {
70 | objectValue = this.ObjectTable[objectRef] == null ? null : this.ObjectTable[objectRef].Value;
71 | innerDict = objectValue as BinaryPlistDictionary;
72 |
73 | if (innerDict != null)
74 | {
75 | objectValue = innerDict.ToDictionary();
76 | }
77 | else
78 | {
79 | innerArray = objectValue as BinaryPlistArray;
80 |
81 | if (innerArray != null)
82 | {
83 | objectValue = innerArray.ToArray();
84 | }
85 | }
86 |
87 | array[i] = objectValue;
88 | }
89 | }
90 |
91 | return array;
92 | }
93 |
94 | ///
95 | /// Returns the string representation of this instance.
96 | ///
97 | /// This instance's string representation.
98 | public override string ToString()
99 | {
100 | StringBuilder sb = new StringBuilder("[");
101 | int objectRef;
102 |
103 | for (int i = 0; i < this.ObjectReference.Count; i++)
104 | {
105 | if (i > 0)
106 | {
107 | sb.Append(",");
108 | }
109 |
110 | objectRef = this.ObjectReference[i];
111 |
112 | if (this.ObjectTable.Count > objectRef && (this.ObjectTable[objectRef] == null || this.ObjectTable[objectRef].Value != this))
113 | {
114 | sb.Append(this.ObjectReference[objectRef]);
115 | }
116 | else
117 | {
118 | sb.Append("*" + objectRef);
119 | }
120 | }
121 |
122 | return sb.ToString() + "]";
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/AirPlay/Plist/BinaryPlistDictionary.cs:
--------------------------------------------------------------------------------
1 | //-----------------------------------------------------------------------
2 | //
3 | // Copyright (c) 2011 Chad Burggraf.
4 | // Inspired by BinaryPListParser.java, copyright (c) 2005 Werner Randelshofer
5 | // http://www.java2s.com/Open-Source/Java-Document/Swing-Library/jide-common/com/jidesoft/plaf/aqua/BinaryPListParser.java.htm
6 | //
7 | //-----------------------------------------------------------------------
8 |
9 | namespace System.Runtime.Serialization.Plists
10 | {
11 | using System;
12 | using System.Collections;
13 | using System.Collections.Generic;
14 | using System.Diagnostics.CodeAnalysis;
15 | using System.Text;
16 |
17 | ///
18 | /// Represents a dictionary in a binary plist.
19 | ///
20 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")]
21 | internal class BinaryPlistDictionary
22 | {
23 | ///
24 | /// Initializes a new instance of the BinaryPlistDictionary class.
25 | ///
26 | /// A reference to the binary plist's object table.
27 | /// The size of the dictionary.
28 | public BinaryPlistDictionary(IList objectTable, int size)
29 | {
30 | this.KeyReference = new List(size);
31 | this.ObjectReference = new List(size);
32 | this.ObjectTable = objectTable;
33 | }
34 |
35 | ///
36 | /// Gets the dictionary's key reference collection.
37 | ///
38 | public IList KeyReference { get; private set; }
39 |
40 | ///
41 | /// Gets the dictionary's object reference collection.
42 | ///
43 | public IList ObjectReference { get; private set; }
44 |
45 | ///
46 | /// Gets a reference to the binary plist's object table.
47 | ///
48 | public IList ObjectTable { get; private set; }
49 |
50 | ///
51 | /// Converts this instance into a .
52 | ///
53 | /// A representation this instance.
54 | public Dictionary