├── .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 ToDictionary() 55 | { 56 | Dictionary dictionary = new Dictionary(); 57 | int keyRef, objectRef; 58 | object keyValue, objectValue; 59 | BinaryPlistArray innerArray; 60 | BinaryPlistDictionary innerDict; 61 | 62 | for (int i = 0; i < this.KeyReference.Count; i++) 63 | { 64 | keyRef = this.KeyReference[i]; 65 | objectRef = this.ObjectReference[i]; 66 | 67 | if (keyRef >= 0 && keyRef < this.ObjectTable.Count && (this.ObjectTable[keyRef] == null || this.ObjectTable[keyRef].Value != this) && 68 | objectRef >= 0 && objectRef < this.ObjectTable.Count && (this.ObjectTable[objectRef] == null || this.ObjectTable[objectRef].Value != this)) 69 | { 70 | keyValue = this.ObjectTable[keyRef] == null ? null : this.ObjectTable[keyRef].Value; 71 | objectValue = this.ObjectTable[objectRef] == null ? null : this.ObjectTable[objectRef].Value; 72 | innerDict = objectValue as BinaryPlistDictionary; 73 | 74 | if (innerDict != null) 75 | { 76 | objectValue = innerDict.ToDictionary(); 77 | } 78 | else 79 | { 80 | innerArray = objectValue as BinaryPlistArray; 81 | 82 | if (innerArray != null) 83 | { 84 | objectValue = innerArray.ToArray(); 85 | } 86 | } 87 | 88 | dictionary[keyValue] = objectValue; 89 | } 90 | } 91 | 92 | return dictionary; 93 | } 94 | 95 | /// 96 | /// Returns the string representation of this instance. 97 | /// 98 | /// This instance's string representation. 99 | public override string ToString() 100 | { 101 | StringBuilder sb = new StringBuilder("{"); 102 | int keyRef, objectRef; 103 | 104 | for (int i = 0; i < this.KeyReference.Count; i++) 105 | { 106 | if (i > 0) 107 | { 108 | sb.Append(","); 109 | } 110 | 111 | keyRef = this.KeyReference[i]; 112 | objectRef = this.ObjectReference[i]; 113 | 114 | if (keyRef < 0 || keyRef >= this.ObjectTable.Count) 115 | { 116 | sb.Append("#" + keyRef); 117 | } 118 | else if (this.ObjectTable[keyRef] != null && this.ObjectTable[keyRef].Value == this) 119 | { 120 | sb.Append("*" + keyRef); 121 | } 122 | else 123 | { 124 | sb.Append(this.ObjectTable[keyRef]); 125 | } 126 | 127 | sb.Append(":"); 128 | 129 | if (objectRef < 0 || objectRef >= this.ObjectTable.Count) 130 | { 131 | sb.Append("#" + objectRef); 132 | } 133 | else if (this.ObjectTable[objectRef] != null && this.ObjectTable[objectRef].Value == this) 134 | { 135 | sb.Append("*" + objectRef); 136 | } 137 | else 138 | { 139 | sb.Append(this.ObjectTable[objectRef]); 140 | } 141 | } 142 | 143 | return sb.ToString() + "}"; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /AirPlay/Plist/BinaryPlistItem.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | 12 | /// 13 | /// Represents an item in a binary plist's object table. 14 | /// 15 | internal class BinaryPlistItem 16 | { 17 | private List byteValue, marker; 18 | 19 | /// 20 | /// Initializes a new instance of the BinaryPlistItem class. 21 | /// 22 | public BinaryPlistItem() 23 | { 24 | this.byteValue = new List(); 25 | this.marker = new List(); 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of the BinaryPlistItem class. 30 | /// 31 | /// The value of the object the item represents. 32 | public BinaryPlistItem(object value) 33 | : this() 34 | { 35 | this.Value = value; 36 | } 37 | 38 | /// 39 | /// Gets the item's byte value collection. 40 | /// 41 | public IList ByteValue 42 | { 43 | get { return this.byteValue; } 44 | } 45 | 46 | /// 47 | /// Gets or sets a value indicating whether this item represents an array. 48 | /// 49 | public bool IsArray { get; set; } 50 | 51 | /// 52 | /// Gets or sets a value indicating whether this item represents a dictionary. 53 | /// 54 | public bool IsDictionary { get; set; } 55 | 56 | /// 57 | /// Gets the item's marker value collection. 58 | /// 59 | public IList Marker 60 | { 61 | get { return this.marker; } 62 | } 63 | 64 | /// 65 | /// Gets the item's size, which is a sum of the and lengths. 66 | /// 67 | public int Size 68 | { 69 | get { return this.Marker.Count + this.ByteValue.Count; } 70 | } 71 | 72 | /// 73 | /// Gets or sets the object value this item represents. 74 | /// 75 | public object Value { get; set; } 76 | 77 | /// 78 | /// Sets the to the given collection. 79 | /// 80 | /// The collection to set. 81 | public void SetByteValue(IEnumerable buffer) 82 | { 83 | this.byteValue.Clear(); 84 | 85 | if (buffer != null) 86 | { 87 | this.byteValue.AddRange(buffer); 88 | } 89 | } 90 | 91 | /// 92 | /// Gets the string representation of this instance. 93 | /// 94 | /// The string representation of this instance. 95 | public override string ToString() 96 | { 97 | return this.Value != null ? this.Value.ToString() : "null"; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /AirPlay/Plist/DataContractBinaryPlistSerializer.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | using System.Diagnostics.CodeAnalysis; 13 | using System.Globalization; 14 | using System.IO; 15 | using System.Linq; 16 | using System.Reflection; 17 | using System.Runtime.Serialization; 18 | 19 | /// 20 | /// Serializes data contracts to and from the binary plist format. 21 | /// 22 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")] 23 | public sealed class DataContractBinaryPlistSerializer 24 | { 25 | private Type rootType; 26 | private bool isDictionary; 27 | private Dictionary typeCache; 28 | 29 | /// 30 | /// Initializes a new instance of the DataContractBinaryPlistSerializer class. 31 | /// 32 | /// The type of the instances that are serialized or de-serialized. 33 | public DataContractBinaryPlistSerializer(Type type) 34 | { 35 | if (type == null) 36 | { 37 | throw new ArgumentNullException("type", "type cannot be null."); 38 | } 39 | 40 | this.isDictionary = typeof(IDictionary).IsAssignableFrom(type); 41 | 42 | if (!this.isDictionary && type.IsCollection()) 43 | { 44 | throw new ArgumentException("root type cannot be a collection unless it is an IDictionary implementation.", "type"); 45 | } 46 | 47 | if (type.IsPrimitiveOrEnum()) 48 | { 49 | throw new ArgumentException("type must be an implementation of IDictionary or a complex object type.", "type"); 50 | } 51 | 52 | this.rootType = type; 53 | this.typeCache = new Dictionary(); 54 | } 55 | 56 | /// 57 | /// Reads an object from the specified stream. 58 | /// 59 | /// The stream to read from. 60 | /// The de-serialized object. 61 | public object ReadObject(Stream stream) 62 | { 63 | return this.GetReadablePlistObject(this.rootType, new BinaryPlistReader().ReadObject(stream)); 64 | } 65 | 66 | /// 67 | /// Writes the complete contents of the given object to the specified stream. 68 | /// 69 | /// The stream to write to. 70 | /// The object to write. 71 | public void WriteObject(Stream stream, object graph) 72 | { 73 | this.WriteObject(stream, graph, true); 74 | } 75 | 76 | /// 77 | /// Writes the complete contents of the given object to the specified stream. 78 | /// 79 | /// The stream to write to. 80 | /// The object to write. 81 | /// A value indicating whether to close the stream after the write operation completes. 82 | public void WriteObject(Stream stream, object graph, bool closeStream) 83 | { 84 | if (stream == null) 85 | { 86 | throw new ArgumentNullException("stream", "stream cannot be null."); 87 | } 88 | 89 | if (graph == null) 90 | { 91 | throw new ArgumentNullException("graph", "graph cannot be null."); 92 | } 93 | 94 | Type type = graph.GetType(); 95 | 96 | if (!this.rootType.IsAssignableFrom(type)) 97 | { 98 | throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The object specified is of type {0}, which is not assignable to this instance's root type, {1}.", type, this.rootType)); 99 | } 100 | 101 | IDictionary dict = this.GetWritablePlistObject(type, graph) as IDictionary; 102 | 103 | if (dict == null) 104 | { 105 | throw new ArgumentException("The root object must be assignable to IDictionary, a complex type with an explicit or implied data contract, or assignable to IPlistSerializable.", "graph"); 106 | } 107 | 108 | new BinaryPlistWriter().WriteObject(stream, dict, closeStream); 109 | } 110 | 111 | /// 112 | /// Gets the readable plist value of the given object identified by the specified type. 113 | /// 114 | /// The type the object is expected to have after being de-serialized. 115 | /// The raw plist object value. 116 | /// A readable plist object value. 117 | private object GetReadablePlistObject(Type type, object obj) 118 | { 119 | object result = null; 120 | IDictionary plistDict = obj as IDictionary; 121 | 122 | if (obj != null) 123 | { 124 | if (typeof(IPlistSerializable).IsAssignableFrom(type)) 125 | { 126 | if (plistDict != null) 127 | { 128 | IPlistSerializable serResult = (IPlistSerializable)Activator.CreateInstance(type); 129 | serResult.FromPlistDictionary(plistDict); 130 | } 131 | } 132 | else if (typeof(IDictionary).IsAssignableFrom(type)) 133 | { 134 | if (plistDict != null) 135 | { 136 | Type keyType = typeof(object), valueType = typeof(object); 137 | 138 | if (type.IsGenericType) 139 | { 140 | Type[] args = type.GetGenericArguments(); 141 | keyType = args[0]; 142 | valueType = args[1]; 143 | } 144 | 145 | IDictionary dictResult = (IDictionary)(type.IsInterface ? 146 | Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(keyType, valueType)) : 147 | Activator.CreateInstance(type)); 148 | 149 | foreach (object key in plistDict.Keys) 150 | { 151 | if (!type.IsGenericType) 152 | { 153 | keyType = key.GetType(); 154 | valueType = plistDict[key] != null ? plistDict[key].GetType() : typeof(object); 155 | } 156 | 157 | dictResult[this.GetReadablePlistObject(keyType, key)] = this.GetReadablePlistObject(valueType, plistDict[key]); 158 | } 159 | 160 | result = dictResult; 161 | } 162 | } 163 | else if (type.IsCollection()) 164 | { 165 | IEnumerable plistColl = obj as IEnumerable; 166 | 167 | if (plistColl != null) 168 | { 169 | Type valueType = typeof(object); 170 | bool isArray = false; 171 | IList listResult; 172 | 173 | if (type.IsGenericType) 174 | { 175 | valueType = type.GetGenericArguments()[0]; 176 | } 177 | else if (typeof(Array).IsAssignableFrom(type)) 178 | { 179 | valueType = type.GetElementType(); 180 | isArray = true; 181 | } 182 | 183 | if (isArray) 184 | { 185 | listResult = new ArrayList(); 186 | } 187 | else 188 | { 189 | // TODO: The default DataContractSerializer uses an informal protocal requiring a method named "Add()" 190 | // rather than requiring concrete collection types to implement IList. 191 | listResult = (IList)(type.IsInterface ? 192 | Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType)) : 193 | Activator.CreateInstance(type)); 194 | } 195 | 196 | foreach (object value in plistColl) 197 | { 198 | listResult.Add(this.GetReadablePlistObject(valueType, value)); 199 | } 200 | 201 | result = isArray ? ((ArrayList)listResult).ToArray() : listResult; 202 | } 203 | } 204 | else if (type.IsPrimitiveOrEnum()) 205 | { 206 | result = obj; 207 | } 208 | else 209 | { 210 | if (plistDict != null) 211 | { 212 | if (!this.typeCache.ContainsKey(type)) 213 | { 214 | this.typeCache[type] = new TypeCacheItem(type); 215 | } 216 | 217 | TypeCacheItem cache = this.typeCache[type]; 218 | result = Activator.CreateInstance(type); 219 | 220 | for (int i = 0; i < cache.Fields.Count; i++) 221 | { 222 | FieldInfo field = cache.Fields[i]; 223 | DataMemberAttribute member = cache.FieldMembers[i]; 224 | 225 | if (plistDict.Contains(member.Name)) 226 | { 227 | field.SetValue(result, this.GetReadablePlistObject(field.FieldType, plistDict[member.Name])); 228 | } 229 | } 230 | 231 | for (int i = 0; i < cache.Properties.Count; i++) 232 | { 233 | PropertyInfo property = cache.Properties[i]; 234 | DataMemberAttribute member = cache.PropertyMembers[i]; 235 | 236 | if (plistDict.Contains(member.Name)) 237 | { 238 | property.SetValue(result, this.GetReadablePlistObject(property.PropertyType, plistDict[member.Name]), null); 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | return result; 246 | } 247 | 248 | /// 249 | /// Gets the writable plist value of the given object identified by the specified type. 250 | /// 251 | /// The of the object. 252 | /// The object to get the plist value of. 253 | /// The plist value of the given object. 254 | private object GetWritablePlistObject(Type type, object obj) 255 | { 256 | object result = null; 257 | 258 | if (obj != null) 259 | { 260 | if (typeof(IPlistSerializable).IsAssignableFrom(type)) 261 | { 262 | result = ((IPlistSerializable)obj).ToPlistDictionary(); 263 | } 264 | else if (typeof(IDictionary).IsAssignableFrom(type)) 265 | { 266 | IDictionary dict = obj as IDictionary; 267 | Dictionary resultDict = new Dictionary(); 268 | 269 | foreach (object key in dict.Keys) 270 | { 271 | object value = dict[key]; 272 | resultDict[this.GetWritablePlistObject(key.GetType(), key)] = this.GetWritablePlistObject(value.GetType(), value); 273 | } 274 | 275 | result = resultDict; 276 | } 277 | else if (type.IsCollection()) 278 | { 279 | IEnumerable coll = obj as IEnumerable; 280 | List resultColl = new List(); 281 | 282 | foreach (object value in coll) 283 | { 284 | resultColl.Add(this.GetWritablePlistObject(value.GetType(), value)); 285 | } 286 | 287 | result = resultColl; 288 | } 289 | else if (type.IsPrimitiveOrEnum()) 290 | { 291 | result = obj; 292 | } 293 | else 294 | { 295 | if (!this.typeCache.ContainsKey(type)) 296 | { 297 | this.typeCache[type] = new TypeCacheItem(type); 298 | } 299 | 300 | TypeCacheItem cache = this.typeCache[type]; 301 | Dictionary resultDict = new Dictionary(); 302 | 303 | for (int i = 0; i < cache.Fields.Count; i++) 304 | { 305 | FieldInfo field = cache.Fields[i]; 306 | DataMemberAttribute member = cache.FieldMembers[i]; 307 | object fieldValue = field.GetValue(obj); 308 | 309 | if (member.EmitDefaultValue || !field.FieldType.IsDefaultValue(fieldValue)) 310 | { 311 | resultDict[member.Name] = this.GetWritablePlistObject(field.FieldType, fieldValue); 312 | } 313 | } 314 | 315 | for (int i = 0; i < cache.Properties.Count; i++) 316 | { 317 | PropertyInfo property = cache.Properties[i]; 318 | DataMemberAttribute member = cache.PropertyMembers[i]; 319 | object propertyValue = property.GetValue(obj, null); 320 | 321 | if (member.EmitDefaultValue || !property.PropertyType.IsDefaultValue(propertyValue)) 322 | { 323 | resultDict[member.Name] = this.GetWritablePlistObject(property.PropertyType, propertyValue); 324 | } 325 | } 326 | 327 | result = resultDict; 328 | } 329 | } 330 | 331 | return result; 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /AirPlay/Plist/EndianConverter.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Modifications copyright (c) 2011 Chad Burggraf. 4 | // Original copyright (c) 2009 Cor Schols 5 | // Original source: http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/c878e72e-d42e-417d-b4f6-1935ad96d8ae/ 6 | // 7 | //----------------------------------------------------------------------- 8 | 9 | namespace System.Runtime.Serialization.Plists 10 | { 11 | using System; 12 | 13 | /// 14 | /// Converts the endian-ness of primitive number types. 15 | /// 16 | [CLSCompliant(false)] 17 | public static class EndianConverter 18 | { 19 | /// 20 | /// Swaps the endian-ness of the given value. 21 | /// 22 | /// The value to swap the endian-ness of. 23 | /// The resulting value. 24 | public static short SwapEndian(this short value) 25 | { 26 | return SwapInt16(value); 27 | } 28 | 29 | /// 30 | /// Swaps the endian-ness of the given value. 31 | /// 32 | /// The value to swap the endian-ness of. 33 | /// The resulting value. 34 | public static ushort SwapEndian(this ushort value) 35 | { 36 | return SwapUInt16(value); 37 | } 38 | 39 | /// 40 | /// Swaps the endian-ness of the given value. 41 | /// 42 | /// The value to swap the endian-ness of. 43 | /// The resulting value. 44 | public static int SwapEndian(this int value) 45 | { 46 | return SwapInt32(value); 47 | } 48 | 49 | /// 50 | /// Swaps the endian-ness of the given value. 51 | /// 52 | /// The value to swap the endian-ness of. 53 | /// The resulting value. 54 | public static uint SwapEndian(this uint value) 55 | { 56 | return SwapUInt32(value); 57 | } 58 | 59 | /// 60 | /// Swaps the endian-ness of the given value. 61 | /// 62 | /// The value to swap the endian-ness of. 63 | /// The resulting value. 64 | public static long SwapEndian(this long value) 65 | { 66 | return SwapInt64(value); 67 | } 68 | 69 | /// 70 | /// Swaps the endian-ness of the given value. 71 | /// 72 | /// The value to swap the endian-ness of. 73 | /// The resulting value. 74 | public static ulong SwapEndian(this ulong value) 75 | { 76 | return SwapUInt64(value); 77 | } 78 | 79 | /// 80 | /// Swaps the endian-ness of the given value. 81 | /// 82 | /// The value to swap the endian-ness of. 83 | /// The resulting value. 84 | public static short SwapInt16(short value) 85 | { 86 | return (short)(((value & 0xff) << 8) | ((value >> 8) & 0xff)); 87 | } 88 | 89 | /// 90 | /// Swaps the endian-ness of the given value. 91 | /// 92 | /// The value to swap the endian-ness of. 93 | /// The resulting value. 94 | public static int SwapInt32(int value) 95 | { 96 | return (int)(((SwapInt16((short)value) & 0xffff) << 0x10) | (SwapInt16((short)(value >> 0x10)) & 0xffff)); 97 | } 98 | 99 | /// 100 | /// Swaps the endian-ness of the given value. 101 | /// 102 | /// The value to swap the endian-ness of. 103 | /// The resulting value. 104 | public static long SwapInt64(long value) 105 | { 106 | return (long)(((SwapInt32((int)value) & 0xffffffffL) << 0x20) | (SwapInt32((int)(value >> 0x20)) & 0xffffffffL)); 107 | } 108 | 109 | /// 110 | /// Swaps the endian-ness of the given value. 111 | /// 112 | /// The value to swap the endian-ness of. 113 | /// The resulting value. 114 | public static uint SwapUInt32(uint value) 115 | { 116 | return (uint)(((SwapUInt16((ushort)value) & 0xffff) << 0x10) | (SwapUInt16((ushort)(value >> 0x10)) & 0xffff)); 117 | } 118 | 119 | /// 120 | /// Swaps the endian-ness of the given value. 121 | /// 122 | /// The value to swap the endian-ness of. 123 | /// The resulting value. 124 | public static ushort SwapUInt16(ushort value) 125 | { 126 | return (ushort)(((value & 0xff) << 8) | ((value >> 8) & 0xff)); 127 | } 128 | 129 | /// 130 | /// Swaps the endian-ness of the given value. 131 | /// 132 | /// The value to swap the endian-ness of. 133 | /// The resulting value. 134 | public static ulong SwapUInt64(ulong value) 135 | { 136 | return (ulong)(((SwapUInt32((uint)value) & 0xffffffffL) << 0x20) | (SwapUInt32((uint)(value >> 0x20)) & 0xffffffffL)); 137 | } 138 | 139 | /// 140 | /// Gets the big-endian value of the given value if the current system is little-endian. 141 | /// If the current system is big-endian, returns the value as-is. 142 | /// 143 | /// The value to swap if necessary. 144 | /// The resulting value. 145 | public static ushort ToBigEndianConditional(this ushort value) 146 | { 147 | if (BitConverter.IsLittleEndian) 148 | { 149 | return value.SwapEndian(); 150 | } 151 | 152 | return value; 153 | } 154 | 155 | /// 156 | /// Gets the big-endian value of the given value if the current system is little-endian. 157 | /// If the current system is big-endian, returns the value as-is. 158 | /// 159 | /// The value to swap if necessary. 160 | /// The resulting value. 161 | public static short ToBigEndianConditional(this short value) 162 | { 163 | if (BitConverter.IsLittleEndian) 164 | { 165 | return value.SwapEndian(); 166 | } 167 | 168 | return value; 169 | } 170 | 171 | /// 172 | /// Gets the big-endian value of the given value if the current system is little-endian. 173 | /// If the current system is big-endian, returns the value as-is. 174 | /// 175 | /// The value to swap if necessary. 176 | /// The resulting value. 177 | public static uint ToBigEndianConditional(this uint value) 178 | { 179 | if (BitConverter.IsLittleEndian) 180 | { 181 | return value.SwapEndian(); 182 | } 183 | 184 | return value; 185 | } 186 | 187 | /// 188 | /// Gets the big-endian value of the given value if the current system is little-endian. 189 | /// If the current system is big-endian, returns the value as-is. 190 | /// 191 | /// The value to swap if necessary. 192 | /// The resulting value. 193 | public static int ToBigEndianConditional(this int value) 194 | { 195 | if (BitConverter.IsLittleEndian) 196 | { 197 | return value.SwapEndian(); 198 | } 199 | 200 | return value; 201 | } 202 | 203 | /// 204 | /// Gets the big-endian value of the given value if the current system is little-endian. 205 | /// If the current system is big-endian, returns the value as-is. 206 | /// 207 | /// The value to swap if necessary. 208 | /// The resulting value. 209 | public static ulong ToBigEndianConditional(this ulong value) 210 | { 211 | if (BitConverter.IsLittleEndian) 212 | { 213 | return value.SwapEndian(); 214 | } 215 | 216 | return value; 217 | } 218 | 219 | /// 220 | /// Gets the big-endian value of the given value if the current system is little-endian. 221 | /// If the current system is big-endian, returns the value as-is. 222 | /// 223 | /// The value to swap if necessary. 224 | /// The resulting value. 225 | public static long ToBigEndianConditional(this long value) 226 | { 227 | if (BitConverter.IsLittleEndian) 228 | { 229 | return value.SwapEndian(); 230 | } 231 | 232 | return value; 233 | } 234 | 235 | /// 236 | /// Gets the little-endian value of the given value if the current system is big-endian. 237 | /// If the current system is little-endian, returns the value as-is. 238 | /// 239 | /// The value to swap if necessary. 240 | /// The resulting value. 241 | public static ushort ToLittleEndianConditional(this ushort value) 242 | { 243 | if (BitConverter.IsLittleEndian) 244 | { 245 | return value; 246 | } 247 | 248 | return value.SwapEndian(); 249 | } 250 | 251 | /// 252 | /// Gets the little-endian value of the given value if the current system is big-endian. 253 | /// If the current system is little-endian, returns the value as-is. 254 | /// 255 | /// The value to swap if necessary. 256 | /// The resulting value. 257 | public static short ToLittleEndianConditional(this short value) 258 | { 259 | if (BitConverter.IsLittleEndian) 260 | { 261 | return value; 262 | } 263 | 264 | return value.SwapEndian(); 265 | } 266 | 267 | /// 268 | /// Gets the little-endian value of the given value if the current system is big-endian. 269 | /// If the current system is little-endian, returns the value as-is. 270 | /// 271 | /// The value to swap if necessary. 272 | /// The resulting value. 273 | public static uint ToLittleEndianConditional(this uint value) 274 | { 275 | if (BitConverter.IsLittleEndian) 276 | { 277 | return value; 278 | } 279 | 280 | return value.SwapEndian(); 281 | } 282 | 283 | /// 284 | /// Gets the little-endian value of the given value if the current system is big-endian. 285 | /// If the current system is little-endian, returns the value as-is. 286 | /// 287 | /// The value to swap if necessary. 288 | /// The resulting value. 289 | public static int ToLittleEndianConditional(this int value) 290 | { 291 | if (BitConverter.IsLittleEndian) 292 | { 293 | return value; 294 | } 295 | 296 | return value.SwapEndian(); 297 | } 298 | 299 | /// 300 | /// Gets the little-endian value of the given value if the current system is big-endian. 301 | /// If the current system is little-endian, returns the value as-is. 302 | /// 303 | /// The value to swap if necessary. 304 | /// The resulting value. 305 | public static ulong ToLittleEndianConditional(this ulong value) 306 | { 307 | if (BitConverter.IsLittleEndian) 308 | { 309 | return value; 310 | } 311 | 312 | return value.SwapEndian(); 313 | } 314 | 315 | /// 316 | /// Gets the little-endian value of the given value if the current system is big-endian. 317 | /// If the current system is little-endian, returns the value as-is. 318 | /// 319 | /// The value to swap if necessary. 320 | /// The resulting value. 321 | public static long ToLittleEndianConditional(this long value) 322 | { 323 | if (BitConverter.IsLittleEndian) 324 | { 325 | return value; 326 | } 327 | 328 | return value.SwapEndian(); 329 | } 330 | } 331 | } -------------------------------------------------------------------------------- /AirPlay/Plist/Extensions.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections; 11 | 12 | /// 13 | /// Extensions and helpers for plist serialization. 14 | /// 15 | internal static class Extensions 16 | { 17 | /// 18 | /// Gets the specified type's concrete type of it is an instance of . 19 | /// If the type is not null-able, it is returned as-is. 20 | /// 21 | /// The type to get the concrete type of. 22 | /// The type's concrete type. 23 | public static Type GetConcreteTypeIfNullable(this Type type) 24 | { 25 | if (type == null) 26 | { 27 | throw new ArgumentNullException("type", "type cannot be null."); 28 | } 29 | 30 | if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 31 | { 32 | return type.GetGenericArguments()[0]; 33 | } 34 | 35 | return type; 36 | } 37 | 38 | /// 39 | /// Gets a value indicating whether the given string is all ASCII. 40 | /// 41 | /// The string to check. 42 | /// True if the string contains only ASCII characters, false otherwise. 43 | public static bool IsAscii(this string value) 44 | { 45 | if (!string.IsNullOrEmpty(value)) 46 | { 47 | foreach (char c in value) 48 | { 49 | if (c > 127) 50 | { 51 | return false; 52 | } 53 | } 54 | } 55 | 56 | return true; 57 | } 58 | 59 | /// 60 | /// Gets a value indicating whether the specified type is a collection type. 61 | /// 62 | /// The type to check. 63 | /// True if the type is a collection type, false otherwise. 64 | public static bool IsCollection(this Type type) 65 | { 66 | if (type == null) 67 | { 68 | throw new ArgumentNullException("type", "type cannot be null."); 69 | } 70 | 71 | return (typeof(Array).IsAssignableFrom(type) 72 | || typeof(IEnumerable).IsAssignableFrom(type)) 73 | && !typeof(string).IsAssignableFrom(type) 74 | && !typeof(byte[]).IsAssignableFrom(type); 75 | } 76 | 77 | /// 78 | /// Gets a value indicating whether the given value is the default value for the specified type. 79 | /// 80 | /// The type to check the value against. 81 | /// The value to check. 82 | /// True if the value is the default value, false otherwise. 83 | public static bool IsDefaultValue(this Type type, object value) 84 | { 85 | if (type == null) 86 | { 87 | throw new ArgumentNullException("type", "type cannot be null."); 88 | } 89 | 90 | TypeCode typeCode = Type.GetTypeCode(type); 91 | 92 | if (typeCode != TypeCode.Empty && typeCode != TypeCode.Object && value == null) 93 | { 94 | throw new ArgumentException("Cannot pass a null value when the specified type is non-nullable.", "value"); 95 | } 96 | 97 | if (!type.IsAssignableFrom(value.GetType())) 98 | { 99 | throw new ArgumentException("The specified object value is not assignable to the specified type.", "value"); 100 | } 101 | 102 | switch (Type.GetTypeCode(type)) 103 | { 104 | case TypeCode.Boolean: 105 | return (bool)value == false; 106 | case TypeCode.Byte: 107 | case TypeCode.Char: 108 | case TypeCode.Int16: 109 | case TypeCode.Int32: 110 | case TypeCode.Int64: 111 | case TypeCode.SByte: 112 | case TypeCode.UInt16: 113 | case TypeCode.UInt32: 114 | case TypeCode.UInt64: 115 | return (long)value == 0; 116 | case TypeCode.DateTime: 117 | return (DateTime)value == DateTime.MinValue; 118 | case TypeCode.DBNull: 119 | return true; 120 | case TypeCode.Decimal: 121 | case TypeCode.Double: 122 | case TypeCode.Single: 123 | return (double)value == 0; 124 | default: 125 | return value == null; 126 | } 127 | } 128 | 129 | /// 130 | /// Gets a value indicating whether the specified type is an enum or primitive or semi-primitive (e.g., string) type. 131 | /// 132 | /// The type to check. 133 | /// True if the type is an enum or primitive type, false otherwise. 134 | public static bool IsPrimitiveOrEnum(this Type type) 135 | { 136 | if (type == null) 137 | { 138 | throw new ArgumentNullException("type", "type cannot be null."); 139 | } 140 | 141 | bool result = true; 142 | 143 | if (!type.IsEnum 144 | && Type.GetTypeCode(type) == TypeCode.Object 145 | && !typeof(Guid).IsAssignableFrom(type) 146 | && !typeof(TimeSpan).IsAssignableFrom(type) 147 | && !typeof(byte[]).IsAssignableFrom(type) 148 | && !typeof(Uri).IsAssignableFrom(type)) 149 | { 150 | Type concrete = type.GetConcreteTypeIfNullable(); 151 | 152 | if (concrete != type) 153 | { 154 | result = IsPrimitiveOrEnum(concrete); 155 | } 156 | else 157 | { 158 | result = false; 159 | } 160 | } 161 | 162 | return result; 163 | } 164 | 165 | /// 166 | /// Converts the given value into its binary representation as a string. 167 | /// 168 | /// The value to convert. 169 | /// The value's binary representation as a string. 170 | public static string ToBinaryString(this byte value) 171 | { 172 | return Convert.ToString(value, 2); 173 | } 174 | 175 | /// 176 | /// Converts the given value into its binary representation as a string. 177 | /// 178 | /// The value to convert. 179 | /// The value's binary representation as a string. 180 | public static string ToBinaryString(this int value) 181 | { 182 | return Convert.ToString(value, 2); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /AirPlay/Plist/IPlistSerializable.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections; 11 | using System.Diagnostics.CodeAnalysis; 12 | 13 | /// 14 | /// Defines the interface for proxy serialization with and . 15 | /// 16 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")] 17 | public interface IPlistSerializable 18 | { 19 | /// 20 | /// Populates this instance from the given plist representation. 21 | /// Note that nested objects found in the graph during 22 | /// are represented as nested instances here. 23 | /// 24 | /// The plist representation of this instance. 25 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")] 26 | void FromPlistDictionary(IDictionary plist); 27 | 28 | /// 29 | /// Gets a plist friendly representation of this instance. 30 | /// The returned dictionary may contain nested implementations of . 31 | /// 32 | /// A plist friendly representation of this instance. 33 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "The spelling is correct.")] 34 | IDictionary ToPlistDictionary(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AirPlay/Plist/TypeCacheItem.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Reflection; 13 | 14 | /// 15 | /// Represents a cached type used during serialization by a . 16 | /// 17 | internal sealed class TypeCacheItem 18 | { 19 | private Type type; 20 | private bool hasCustomContract; 21 | 22 | /// 23 | /// Initializes a new instance of the TypeCacheItem class. 24 | /// 25 | /// The type to cache. 26 | public TypeCacheItem(Type type) 27 | { 28 | if (type == null) 29 | { 30 | throw new ArgumentNullException("type", "type cannot be null."); 31 | } 32 | 33 | this.type = type; 34 | this.hasCustomContract = type.GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0; 35 | this.InitializeFields(); 36 | this.InitializeProperties(); 37 | } 38 | 39 | /// 40 | /// Gets the collection of concrete or simulated s for the type's fields. 41 | /// 42 | public IList FieldMembers { get; private set; } 43 | 44 | /// 45 | /// Gets a collection of the type's fields. 46 | /// 47 | public IList Fields { get; private set; } 48 | 49 | /// 50 | /// Gets a collection of the type's properties. 51 | /// 52 | public IList Properties { get; private set; } 53 | 54 | /// 55 | /// Gets a collection of concrete or simulated s for the type's properties. 56 | /// 57 | public IList PropertyMembers { get; private set; } 58 | 59 | /// 60 | /// Initializes this instance's field-related properties. 61 | /// 62 | private void InitializeFields() 63 | { 64 | this.FieldMembers = new List(); 65 | this.Fields = new List(); 66 | 67 | var fields = this.hasCustomContract ? 68 | this.type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : 69 | this.type.GetFields(BindingFlags.Instance | BindingFlags.Public); 70 | 71 | var tuples = from f in fields 72 | let attr = f.GetCustomAttributes(false) 73 | let member = attr.OfType().FirstOrDefault() 74 | where !f.IsLiteral && attr.OfType().Count() == 0 75 | select new 76 | { 77 | Info = f, 78 | Member = member 79 | }; 80 | 81 | foreach (var tuple in tuples.Where(t => !this.hasCustomContract || t.Member != null)) 82 | { 83 | DataMemberAttribute member = tuple.Member != null ? 84 | tuple.Member : 85 | new DataMemberAttribute() 86 | { 87 | EmitDefaultValue = true, 88 | IsRequired = false 89 | }; 90 | 91 | member.Name = !string.IsNullOrEmpty(member.Name) ? member.Name : tuple.Info.Name; 92 | 93 | this.FieldMembers.Add(member); 94 | this.Fields.Add(tuple.Info); 95 | } 96 | } 97 | 98 | /// 99 | /// Initializes this instance's property-related properties. 100 | /// 101 | private void InitializeProperties() 102 | { 103 | this.Properties = new List(); 104 | this.PropertyMembers = new List(); 105 | 106 | var properties = this.hasCustomContract ? 107 | this.type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : 108 | this.type.GetProperties(BindingFlags.Instance | BindingFlags.Public); 109 | 110 | var tuples = from p in properties 111 | let attr = p.GetCustomAttributes(false) 112 | let member = attr.OfType().FirstOrDefault() 113 | where p.CanRead && p.CanWrite && attr.OfType().Count() == 0 114 | select new 115 | { 116 | Info = p, 117 | Member = member 118 | }; 119 | 120 | foreach (var tuple in tuples.Where(t => !this.hasCustomContract || t.Member != null)) 121 | { 122 | DataMemberAttribute member = tuple.Member != null ? 123 | tuple.Member : 124 | new DataMemberAttribute() 125 | { 126 | EmitDefaultValue = true, 127 | IsRequired = false 128 | }; 129 | 130 | member.Name = !string.IsNullOrEmpty(member.Name) ? member.Name : tuple.Info.Name; 131 | 132 | this.PropertyMembers.Add(member); 133 | this.Properties.Add(tuple.Info); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /AirPlay/Plist/UniqueValueCache.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011 Chad Burggraf. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | namespace System.Runtime.Serialization.Plists 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | 12 | /// 13 | /// Provides a cache of unique primitive values when writing a binary plist. 14 | /// 15 | internal sealed class UniqueValueCache 16 | { 17 | private Dictionary booleans = new Dictionary(); 18 | private Dictionary integers = new Dictionary(); 19 | private Dictionary floats = new Dictionary(); 20 | private Dictionary doubles = new Dictionary(); 21 | private Dictionary dates = new Dictionary(); 22 | private Dictionary strings = new Dictionary(); 23 | 24 | /// 25 | /// Gets a value indicating whether the cache contains the given value. 26 | /// 27 | /// The value to check for. 28 | /// True if the cache contains the value, false otherwise. 29 | public bool Contains(bool value) 30 | { 31 | return this.booleans.ContainsKey(value); 32 | } 33 | 34 | /// 35 | /// Gets a value indicating whether the cache contains the given value. 36 | /// 37 | /// The value to check for. 38 | /// True if the cache contains the value, false otherwise. 39 | public bool Contains(long value) 40 | { 41 | return this.integers.ContainsKey(value); 42 | } 43 | 44 | /// 45 | /// Gets a value indicating whether the cache contains the given value. 46 | /// 47 | /// The value to check for. 48 | /// True if the cache contains the value, false otherwise. 49 | public bool Contains(float value) 50 | { 51 | return this.floats.ContainsKey(value); 52 | } 53 | 54 | /// 55 | /// Gets a value indicating whether the cache contains the given value. 56 | /// 57 | /// The value to check for. 58 | /// True if the cache contains the value, false otherwise. 59 | public bool Contains(double value) 60 | { 61 | return this.doubles.ContainsKey(value); 62 | } 63 | 64 | /// 65 | /// Gets a value indicating whether the cache contains the given value. 66 | /// 67 | /// The value to check for. 68 | /// True if the cache contains the value, false otherwise. 69 | public bool Contains(DateTime value) 70 | { 71 | return this.dates.ContainsKey(value); 72 | } 73 | 74 | /// 75 | /// Gets a value indicating whether the cache contains the given value. 76 | /// 77 | /// The value to check for. 78 | /// True if the cache contains the value, false otherwise. 79 | public bool Contains(string value) 80 | { 81 | return this.strings.ContainsKey(value); 82 | } 83 | 84 | /// 85 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 86 | /// 87 | /// The value to get the index of. 88 | /// The index of the value. 89 | public int GetIndex(bool value) 90 | { 91 | return this.booleans[value]; 92 | } 93 | 94 | /// 95 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 96 | /// 97 | /// The value to get the index of. 98 | /// The index of the value. 99 | public int GetIndex(long value) 100 | { 101 | return this.integers[value]; 102 | } 103 | 104 | /// 105 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 106 | /// 107 | /// The value to get the index of. 108 | /// The index of the value. 109 | public int GetIndex(float value) 110 | { 111 | return this.floats[value]; 112 | } 113 | 114 | /// 115 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 116 | /// 117 | /// The value to get the index of. 118 | /// The index of the value. 119 | public int GetIndex(double value) 120 | { 121 | return this.doubles[value]; 122 | } 123 | 124 | /// 125 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 126 | /// 127 | /// The value to get the index of. 128 | /// The index of the value. 129 | public int GetIndex(DateTime value) 130 | { 131 | return this.dates[value]; 132 | } 133 | 134 | /// 135 | /// Gets the index in the object table for the given value, assuming it has already been added to the cache. 136 | /// 137 | /// The value to get the index of. 138 | /// The index of the value. 139 | public int GetIndex(string value) 140 | { 141 | return this.strings[value]; 142 | } 143 | 144 | /// 145 | /// Sets the index in the object table for the given value. 146 | /// 147 | /// The value to set the index for. 148 | /// The index to set. 149 | public void SetIndex(bool value, int index) 150 | { 151 | this.booleans[value] = index; 152 | } 153 | 154 | /// 155 | /// Sets the index in the object table for the given value. 156 | /// 157 | /// The value to set the index for. 158 | /// The index to set. 159 | public void SetIndex(long value, int index) 160 | { 161 | this.integers[value] = index; 162 | } 163 | 164 | /// 165 | /// Sets the index in the object table for the given value. 166 | /// 167 | /// The value to set the index for. 168 | /// The index to set. 169 | public void SetIndex(float value, int index) 170 | { 171 | this.floats[value] = index; 172 | } 173 | 174 | /// 175 | /// Sets the index in the object table for the given value. 176 | /// 177 | /// The value to set the index for. 178 | /// The index to set. 179 | public void SetIndex(double value, int index) 180 | { 181 | this.doubles[value] = index; 182 | } 183 | 184 | /// 185 | /// Sets the index in the object table for the given value. 186 | /// 187 | /// The value to set the index for. 188 | /// The index to set. 189 | public void SetIndex(string value, int index) 190 | { 191 | this.strings[value] = index; 192 | } 193 | 194 | /// 195 | /// Sets the index in the object table for the given value. 196 | /// 197 | /// The value to set the index for. 198 | /// The index to set. 199 | public void SetIndex(DateTime value, int index) 200 | { 201 | this.dates[value] = index; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /AirPlay/Program.cs: -------------------------------------------------------------------------------- 1 | using AirPlay.Models.Configs; 2 | using AirPlay.Utils; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using System; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Runtime.InteropServices; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | 15 | namespace AirPlay 16 | { 17 | public class Program 18 | { 19 | public static IConfigurationRoot Configuration; 20 | 21 | public static async Task Main(string[] args) 22 | { 23 | // DUMP WAV AUDIO 24 | //var dir = Directory.GetFiles("dump/pcm"); 25 | //var _audiobuf = dir.SelectMany(d => File.ReadAllBytes(d)).ToList(); 26 | //using (var wr = new FileStream("dump/dequeued.wav", FileMode.Create)) 27 | //{ 28 | // var header = Utilities.WriteWavHeader(2, 44100, 16, (uint)_audiobuf.Count); 29 | // wr.Write(header, 0, header.Length); 30 | //} 31 | 32 | //using (FileStream _writer = new FileStream("dump/dequeued.wav", FileMode.Append)) 33 | //{ 34 | // _writer.Write(_audiobuf.ToArray(), 0, _audiobuf.Count); 35 | //} 36 | 37 | var builder = new HostBuilder() 38 | .ConfigureAppConfiguration((hostingContext, config) => 39 | { 40 | config.SetBasePath(Directory.GetCurrentDirectory()); 41 | 42 | var os = "win"; 43 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 44 | { 45 | os = "osx"; 46 | } 47 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 48 | { 49 | os = "linux"; 50 | } 51 | 52 | config.AddJsonFile($"appsettings_{os}.json", optional: false, reloadOnChange: false); 53 | config.AddEnvironmentVariables(); 54 | if (args != null) 55 | { 56 | config.AddCommandLine(args); 57 | } 58 | }) 59 | .ConfigureServices((hostContext, services) => 60 | { 61 | services.AddOptions(); 62 | 63 | services.Configure(hostContext.Configuration.GetSection("AirPlayReceiver")); 64 | services.Configure(hostContext.Configuration.GetSection("CodecLibraries")); 65 | services.Configure(hostContext.Configuration.GetSection("Dump")); 66 | 67 | services.AddSingleton(); 68 | 69 | services.AddHostedService(); 70 | }) 71 | .ConfigureLogging((hostContext, logging) => 72 | { 73 | logging.AddConfiguration(hostContext.Configuration.GetSection("Logging")); 74 | logging.AddConsole(); 75 | }); 76 | 77 | await builder.RunConsoleAsync(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /AirPlay/Resources/table_s5.bin: -------------------------------------------------------------------------------- 1 | 0x21aa8423, 0x2fa1892a, 0x3dbc9e31, 0x33b79338, 0x1986b007, 0x178dbd0e, 0x590aa15, 0xb9ba71c, 0x51f2ec6b, 0x5ff9e162, 0x4de4f679, 0x43effb70, 0x69ded84f, 0x67d5d546, 0x75c8c25d, 0x7bc3cf54, 0xc11a54b3, 0xcf1159ba, 0xdd0c4ea1, 0xd30743a8, 0xf9366097, 0xf73d6d9e, 0xe5207a85, 0xeb2b778c, 0xb1423cfb, 0xbf4931f2, 0xad5426e9, 0xa35f2be0, 0x896e08df, 0x876505d6, 0x957812cd, 0x9b731fc4, 0xfad13f18, 0xf4da3211, 0xe6c7250a, 0xe8cc2803, 0xc2fd0b3c, 0xccf60635, 0xdeeb112e, 0xd0e01c27, 0x8a895750, 0x84825a59, 0x969f4d42, 0x9894404b, 0xb2a56374, 0xbcae6e7d, 0xaeb37966, 0xa0b8746f, 0x1a61ef88, 0x146ae281, 0x677f59a, 0x87cf893, 0x224ddbac, 0x2c46d6a5, 0x3e5bc1be, 0x3050ccb7, 0x6a3987c0, 0x64328ac9, 0x762f9dd2, 0x782490db, 0x5215b3e4, 0x5c1ebeed, 0x4e03a9f6, 0x4008a4ff, 0x8c5ce955, 0x8257e45c, 0x904af347, 0x9e41fe4e, 0xb470dd71, 0xba7bd078, 0xa866c763, 0xa66dca6a, 0xfc04811d, 0xf20f8c14, 0xe0129b0f, 0xee199606, 0xc428b539, 0xca23b830, 0xd83eaf2b, 0xd635a222, 0x6cec39c5, 0x62e734cc, 0x70fa23d7, 0x7ef12ede, 0x54c00de1, 0x5acb00e8, 0x48d617f3, 0x46dd1afa, 0x1cb4518d, 0x12bf5c84, 0xa24b9f, 0xea94696, 0x249865a9, 0x2a9368a0, 0x388e7fbb, 0x368572b2, 0x5727526e, 0x592c5f67, 0x4b31487c, 0x453a4575, 0x6f0b664a, 0x61006b43, 0x731d7c58, 0x7d167151, 0x277f3a26, 0x2974372f, 0x3b692034, 0x35622d3d, 0x1f530e02, 0x1158030b, 0x3451410, 0xd4e1919, 0xb79782fe, 0xb99c8ff7, 0xab8198ec, 0xa58a95e5, 0x8fbbb6da, 0x81b0bbd3, 0x93adacc8, 0x9da6a1c1, 0xc7cfeab6, 0xc9c4e7bf, 0xdbd9f0a4, 0xd5d2fdad, 0xffe3de92, 0xf1e8d39b, 0xe3f5c480, 0xedfec989, 0x605d5ecf, 0x6e5653c6, 0x7c4b44dd, 0x724049d4, 0x58716aeb, 0x567a67e2, 0x446770f9, 0x4a6c7df0, 0x10053687, 0x1e0e3b8e, 0xc132c95, 0x218219c, 0x282902a3, 0x26220faa, 0x343f18b1, 0x3a3415b8, 0x80ed8e5f, 0x8ee68356, 0x9cfb944d, 0x92f09944, 0xb8c1ba7b, 0xb6cab772, 0xa4d7a069, 0xaadcad60, 0xf0b5e617, 0xfebeeb1e, 0xeca3fc05, 0xe2a8f10c, 0xc899d233, 0xc692df3a, 0xd48fc821, 0xda84c528, 0xbb26e5f4, 0xb52de8fd, 0xa730ffe6, 0xa93bf2ef, 0x830ad1d0, 0x8d01dcd9, 0x9f1ccbc2, 0x9117c6cb, 0xcb7e8dbc, 0xc57580b5, 0xd76897ae, 0xd9639aa7, 0xf352b998, 0xfd59b491, 0xef44a38a, 0xe14fae83, 0x5b963564, 0x559d386d, 0x47802f76, 0x498b227f, 0x63ba0140, 0x6db10c49, 0x7fac1b52, 0x71a7165b, 0x2bce5d2c, 0x25c55025, 0x37d8473e, 0x39d34a37, 0x13e26908, 0x1de96401, 0xff4731a, 0x1ff7e13, 0xcdab33b9, 0xc3a03eb0, 0xd1bd29ab, 0xdfb624a2, 0xf587079d, 0xfb8c0a94, 0xe9911d8f, 0xe79a1086, 0xbdf35bf1, 0xb3f856f8, 0xa1e541e3, 0xafee4cea, 0x85df6fd5, 0x8bd462dc, 0x99c975c7, 0x97c278ce, 0x2d1be329, 0x2310ee20, 0x310df93b, 0x3f06f432, 0x1537d70d, 0x1b3cda04, 0x921cd1f, 0x72ac016, 0x5d438b61, 0x53488668, 0x41559173, 0x4f5e9c7a, 0x656fbf45, 0x6b64b24c, 0x7979a557, 0x7772a85e, 0x16d08882, 0x18db858b, 0xac69290, 0x4cd9f99, 0x2efcbca6, 0x20f7b1af, 0x32eaa6b4, 0x3ce1abbd, 0x6688e0ca, 0x6883edc3, 0x7a9efad8, 0x7495f7d1, 0x5ea4d4ee, 0x50afd9e7, 0x42b2cefc, 0x4cb9c3f5, 0xf6605812, 0xf86b551b, 0xea764200, 0xe47d4f09, 0xce4c6c36, 0xc047613f, 0xd25a7624, 0xdc517b2d, 0x8638305a, 0x88333d53, 0x9a2e2a48, 0x94252741, 0xbe14047e, 0xb01f0977, 0xa2021e6c, 0xac091365 -------------------------------------------------------------------------------- /AirPlay/Resources/table_s6.bin: -------------------------------------------------------------------------------- 1 | 0x5ee7493, 0xce07f9e, 0x17f26289, 0x1efc6984, 0x21d658a7, 0x28d853aa, 0x33ca4ebd, 0x3ac445b0, 0x4d9e2cfb, 0x449027f6, 0x5f823ae1, 0x568c31ec, 0x69a600cf, 0x60a80bc2, 0x7bba16d5, 0x72b41dd8, 0x950ec443, 0x9c00cf4e, 0x8712d259, 0x8e1cd954, 0xb136e877, 0xb838e37a, 0xa32afe6d, 0xaa24f560, 0xdd7e9c2b, 0xd4709726, 0xcf628a31, 0xc66c813c, 0xf946b01f, 0xf048bb12, 0xeb5aa605, 0xe254ad08, 0x3e350f28, 0x373b0425, 0x2c291932, 0x2527123f, 0x1a0d231c, 0x13032811, 0x8113506, 0x11f3e0b, 0x76455740, 0x7f4b5c4d, 0x6459415a, 0x6d574a57, 0x527d7b74, 0x5b737079, 0x40616d6e, 0x496f6663, 0xaed5bff8, 0xa7dbb4f5, 0xbcc9a9e2, 0xb5c7a2ef, 0x8aed93cc, 0x83e398c1, 0x98f185d6, 0x91ff8edb, 0xe6a5e790, 0xefabec9d, 0xf4b9f18a, 0xfdb7fa87, 0xc29dcba4, 0xcb93c0a9, 0xd081ddbe, 0xd98fd6b3, 0x734382fe, 0x7a4d89f3, 0x615f94e4, 0x68519fe9, 0x577baeca, 0x5e75a5c7, 0x4567b8d0, 0x4c69b3dd, 0x3b33da96, 0x323dd19b, 0x292fcc8c, 0x2021c781, 0x1f0bf6a2, 0x1605fdaf, 0xd17e0b8, 0x419ebb5, 0xe3a3322e, 0xeaad3923, 0xf1bf2434, 0xf8b12f39, 0xc79b1e1a, 0xce951517, 0xd5870800, 0xdc89030d, 0xabd36a46, 0xa2dd614b, 0xb9cf7c5c, 0xb0c17751, 0x8feb4672, 0x86e54d7f, 0x9df75068, 0x94f95b65, 0x4898f945, 0x4196f248, 0x5a84ef5f, 0x538ae452, 0x6ca0d571, 0x65aede7c, 0x7ebcc36b, 0x77b2c866, 0xe8a12d, 0x9e6aa20, 0x12f4b737, 0x1bfabc3a, 0x24d08d19, 0x2dde8614, 0x36cc9b03, 0x3fc2900e, 0xd8784995, 0xd1764298, 0xca645f8f, 0xc36a5482, 0xfc4065a1, 0xf54e6eac, 0xee5c73bb, 0xe75278b6, 0x900811fd, 0x99061af0, 0x821407e7, 0x8b1a0cea, 0xb4303dc9, 0xbd3e36c4, 0xa62c2bd3, 0xaf2220de, 0xe9af8349, 0xe0a18844, 0xfbb39553, 0xf2bd9e5e, 0xcd97af7d, 0xc499a470, 0xdf8bb967, 0xd685b26a, 0xa1dfdb21, 0xa8d1d02c, 0xb3c3cd3b, 0xbacdc636, 0x85e7f715, 0x8ce9fc18, 0x97fbe10f, 0x9ef5ea02, 0x794f3399, 0x70413894, 0x6b532583, 0x625d2e8e, 0x5d771fad, 0x547914a0, 0x4f6b09b7, 0x466502ba, 0x313f6bf1, 0x383160fc, 0x23237deb, 0x2a2d76e6, 0x150747c5, 0x1c094cc8, 0x71b51df, 0xe155ad2, 0xd274f8f2, 0xdb7af3ff, 0xc068eee8, 0xc966e5e5, 0xf64cd4c6, 0xff42dfcb, 0xe450c2dc, 0xed5ec9d1, 0x9a04a09a, 0x930aab97, 0x8818b680, 0x8116bd8d, 0xbe3c8cae, 0xb73287a3, 0xac209ab4, 0xa52e91b9, 0x42944822, 0x4b9a432f, 0x50885e38, 0x59865535, 0x66ac6416, 0x6fa26f1b, 0x74b0720c, 0x7dbe7901, 0xae4104a, 0x3ea1b47, 0x18f80650, 0x11f60d5d, 0x2edc3c7e, 0x27d23773, 0x3cc02a64, 0x35ce2169, 0x9f027524, 0x960c7e29, 0x8d1e633e, 0x84106833, 0xbb3a5910, 0xb234521d, 0xa9264f0a, 0xa0284407, 0xd7722d4c, 0xde7c2641, 0xc56e3b56, 0xcc60305b, 0xf34a0178, 0xfa440a75, 0xe1561762, 0xe8581c6f, 0xfe2c5f4, 0x6eccef9, 0x1dfed3ee, 0x14f0d8e3, 0x2bdae9c0, 0x22d4e2cd, 0x39c6ffda, 0x30c8f4d7, 0x47929d9c, 0x4e9c9691, 0x558e8b86, 0x5c80808b, 0x63aab1a8, 0x6aa4baa5, 0x71b6a7b2, 0x78b8acbf, 0xa4d90e9f, 0xadd70592, 0xb6c51885, 0xbfcb1388, 0x80e122ab, 0x89ef29a6, 0x92fd34b1, 0x9bf33fbc, 0xeca956f7, 0xe5a75dfa, 0xfeb540ed, 0xf7bb4be0, 0xc8917ac3, 0xc19f71ce, 0xda8d6cd9, 0xd38367d4, 0x3439be4f, 0x3d37b542, 0x2625a855, 0x2f2ba358, 0x1001927b, 0x190f9976, 0x21d8461, 0xb138f6c, 0x7c49e627, 0x7547ed2a, 0x6e55f03d, 0x675bfb30, 0x5871ca13, 0x517fc11e, 0x4a6ddc09, 0x4363d704 -------------------------------------------------------------------------------- /AirPlay/Resources/table_s7.bin: -------------------------------------------------------------------------------- 1 | 0xb33a6e73, 0xbe336078, 0xa9287265, 0xa4217c6e, 0x871e565f, 0x8a175854, 0x9d0c4a49, 0x90054442, 0xdb721e2b, 0xd67b1020, 0xc160023d, 0xcc690c36, 0xef562607, 0xe25f280c, 0xf5443a11, 0xf84d341a, 0x63aa8ec3, 0x6ea380c8, 0x79b892d5, 0x74b19cde, 0x578eb6ef, 0x5a87b8e4, 0x4d9caaf9, 0x4095a4f2, 0xbe2fe9b, 0x6ebf090, 0x11f0e28d, 0x1cf9ec86, 0x3fc6c6b7, 0x32cfc8bc, 0x25d4daa1, 0x28ddd4aa, 0x801b508, 0x508bb03, 0x1213a91e, 0x1f1aa715, 0x3c258d24, 0x312c832f, 0x26379132, 0x2b3e9f39, 0x6049c550, 0x6d40cb5b, 0x7a5bd946, 0x7752d74d, 0x546dfd7c, 0x5964f377, 0x4e7fe16a, 0x4376ef61, 0xd89155b8, 0xd5985bb3, 0xc28349ae, 0xcf8a47a5, 0xecb56d94, 0xe1bc639f, 0xf6a77182, 0xfbae7f89, 0xb0d925e0, 0xbdd02beb, 0xaacb39f6, 0xa7c237fd, 0x84fd1dcc, 0x89f413c7, 0x9eef01da, 0x93e60fd1, 0xde4cc385, 0xd345cd8e, 0xc45edf93, 0xc957d198, 0xea68fba9, 0xe761f5a2, 0xf07ae7bf, 0xfd73e9b4, 0xb604b3dd, 0xbb0dbdd6, 0xac16afcb, 0xa11fa1c0, 0x82208bf1, 0x8f2985fa, 0x983297e7, 0x953b99ec, 0xedc2335, 0x3d52d3e, 0x14ce3f23, 0x19c73128, 0x3af81b19, 0x37f11512, 0x20ea070f, 0x2de30904, 0x6694536d, 0x6b9d5d66, 0x7c864f7b, 0x718f4170, 0x52b06b41, 0x5fb9654a, 0x48a27757, 0x45ab795c, 0x657718fe, 0x687e16f5, 0x7f6504e8, 0x726c0ae3, 0x515320d2, 0x5c5a2ed9, 0x4b413cc4, 0x464832cf, 0xd3f68a6, 0x3666ad, 0x172d74b0, 0x1a247abb, 0x391b508a, 0x34125e81, 0x23094c9c, 0x2e004297, 0xb5e7f84e, 0xb8eef645, 0xaff5e458, 0xa2fcea53, 0x81c3c062, 0x8ccace69, 0x9bd1dc74, 0x96d8d27f, 0xddaf8816, 0xd0a6861d, 0xc7bd9400, 0xcab49a0b, 0xe98bb03a, 0xe482be31, 0xf399ac2c, 0xfe90a227, 0x69d62f84, 0x64df218f, 0x73c43392, 0x7ecd3d99, 0x5df217a8, 0x50fb19a3, 0x47e00bbe, 0x4ae905b5, 0x19e5fdc, 0xc9751d7, 0x1b8c43ca, 0x16854dc1, 0x35ba67f0, 0x38b369fb, 0x2fa87be6, 0x22a175ed, 0xb946cf34, 0xb44fc13f, 0xa354d322, 0xae5ddd29, 0x8d62f718, 0x806bf913, 0x9770eb0e, 0x9a79e505, 0xd10ebf6c, 0xdc07b167, 0xcb1ca37a, 0xc615ad71, 0xe52a8740, 0xe823894b, 0xff389b56, 0xf231955d, 0xd2edf4ff, 0xdfe4faf4, 0xc8ffe8e9, 0xc5f6e6e2, 0xe6c9ccd3, 0xebc0c2d8, 0xfcdbd0c5, 0xf1d2dece, 0xbaa584a7, 0xb7ac8aac, 0xa0b798b1, 0xadbe96ba, 0x8e81bc8b, 0x8388b280, 0x9493a09d, 0x999aae96, 0x27d144f, 0xf741a44, 0x186f0859, 0x15660652, 0x36592c63, 0x3b502268, 0x2c4b3075, 0x21423e7e, 0x6a356417, 0x673c6a1c, 0x70277801, 0x7d2e760a, 0x5e115c3b, 0x53185230, 0x4403402d, 0x490a4e26, 0x4a08272, 0x9a98c79, 0x1eb29e64, 0x13bb906f, 0x3084ba5e, 0x3d8db455, 0x2a96a648, 0x279fa843, 0x6ce8f22a, 0x61e1fc21, 0x76faee3c, 0x7bf3e037, 0x58ccca06, 0x55c5c40d, 0x42ded610, 0x4fd7d81b, 0xd43062c2, 0xd9396cc9, 0xce227ed4, 0xc32b70df, 0xe0145aee, 0xed1d54e5, 0xfa0646f8, 0xf70f48f3, 0xbc78129a, 0xb1711c91, 0xa66a0e8c, 0xab630087, 0x885c2ab6, 0x855524bd, 0x924e36a0, 0x9f4738ab, 0xbf9b5909, 0xb2925702, 0xa589451f, 0xa8804b14, 0x8bbf6125, 0x86b66f2e, 0x91ad7d33, 0x9ca47338, 0xd7d32951, 0xdada275a, 0xcdc13547, 0xc0c83b4c, 0xe3f7117d, 0xeefe1f76, 0xf9e50d6b, 0xf4ec0360, 0x6f0bb9b9, 0x6202b7b2, 0x7519a5af, 0x7810aba4, 0x5b2f8195, 0x56268f9e, 0x413d9d83, 0x4c349388, 0x743c9e1, 0xa4ac7ea, 0x1d51d5f7, 0x1058dbfc, 0x3367f1cd, 0x3e6effc6, 0x2975eddb, 0x247ce3d0 -------------------------------------------------------------------------------- /AirPlay/Resources/table_s8.bin: -------------------------------------------------------------------------------- 1 | 0xb4469bf0, 0xbf4b92fe, 0xa25c89ec, 0xa95180e2, 0x9872bfc8, 0x937fb6c6, 0x8e68add4, 0x8565a4da, 0xec2ed380, 0xe723da8e, 0xfa34c19c, 0xf139c892, 0xc01af7b8, 0xcb17feb6, 0xd600e5a4, 0xdd0decaa, 0x4960b10, 0xf9b021e, 0x128c190c, 0x19811002, 0x28a22f28, 0x23af2626, 0x3eb83d34, 0x35b5343a, 0x5cfe4360, 0x57f34a6e, 0x4ae4517c, 0x41e95872, 0x70ca6758, 0x7bc76e56, 0x66d07544, 0x6ddd7c4a, 0xcffda02b, 0xc4f0a925, 0xd9e7b237, 0xd2eabb39, 0xe3c98413, 0xe8c48d1d, 0xf5d3960f, 0xfede9f01, 0x9795e85b, 0x9c98e155, 0x818ffa47, 0x8a82f349, 0xbba1cc63, 0xb0acc56d, 0xadbbde7f, 0xa6b6d771, 0x7f2d30cb, 0x742039c5, 0x693722d7, 0x623a2bd9, 0x531914f3, 0x58141dfd, 0x450306ef, 0x4e0e0fe1, 0x274578bb, 0x2c4871b5, 0x315f6aa7, 0x3a5263a9, 0xb715c83, 0x7c558d, 0x1d6b4e9f, 0x16664791, 0x422bed5d, 0x4926e453, 0x5431ff41, 0x5f3cf64f, 0x6e1fc965, 0x6512c06b, 0x7805db79, 0x7308d277, 0x1a43a52d, 0x114eac23, 0xc59b731, 0x754be3f, 0x36778115, 0x3d7a881b, 0x206d9309, 0x2b609a07, 0xf2fb7dbd, 0xf9f674b3, 0xe4e16fa1, 0xefec66af, 0xdecf5985, 0xd5c2508b, 0xc8d54b99, 0xc3d84297, 0xaa9335cd, 0xa19e3cc3, 0xbc8927d1, 0xb7842edf, 0x86a711f5, 0x8daa18fb, 0x90bd03e9, 0x9bb00ae7, 0x3990d686, 0x329ddf88, 0x2f8ac49a, 0x2487cd94, 0x15a4f2be, 0x1ea9fbb0, 0x3bee0a2, 0x8b3e9ac, 0x61f89ef6, 0x6af597f8, 0x77e28cea, 0x7cef85e4, 0x4dccbace, 0x46c1b3c0, 0x5bd6a8d2, 0x50dba1dc, 0x89404666, 0x824d4f68, 0x9f5a547a, 0x94575d74, 0xa574625e, 0xae796b50, 0xb36e7042, 0xb863794c, 0xd1280e16, 0xda250718, 0xc7321c0a, 0xcc3f1504, 0xfd1c2a2e, 0xf6112320, 0xeb063832, 0xe00b313c, 0x439c77b1, 0x48917ebf, 0x558665ad, 0x5e8b6ca3, 0x6fa85389, 0x64a55a87, 0x79b24195, 0x72bf489b, 0x1bf43fc1, 0x10f936cf, 0xdee2ddd, 0x6e324d3, 0x37c01bf9, 0x3ccd12f7, 0x21da09e5, 0x2ad700eb, 0xf34ce751, 0xf841ee5f, 0xe556f54d, 0xee5bfc43, 0xdf78c369, 0xd475ca67, 0xc962d175, 0xc26fd87b, 0xab24af21, 0xa029a62f, 0xbd3ebd3d, 0xb633b433, 0x87108b19, 0x8c1d8217, 0x910a9905, 0x9a07900b, 0x38274c6a, 0x332a4564, 0x2e3d5e76, 0x25305778, 0x14136852, 0x1f1e615c, 0x2097a4e, 0x9047340, 0x604f041a, 0x6b420d14, 0x76551606, 0x7d581f08, 0x4c7b2022, 0x4776292c, 0x5a61323e, 0x516c3b30, 0x88f7dc8a, 0x83fad584, 0x9eedce96, 0x95e0c798, 0xa4c3f8b2, 0xafcef1bc, 0xb2d9eaae, 0xb9d4e3a0, 0xd09f94fa, 0xdb929df4, 0xc68586e6, 0xcd888fe8, 0xfcabb0c2, 0xf7a6b9cc, 0xeab1a2de, 0xe1bcabd0, 0xb5f1011c, 0xbefc0812, 0xa3eb1300, 0xa8e61a0e, 0x99c52524, 0x92c82c2a, 0x8fdf3738, 0x84d23e36, 0xed99496c, 0xe6944062, 0xfb835b70, 0xf08e527e, 0xc1ad6d54, 0xcaa0645a, 0xd7b77f48, 0xdcba7646, 0x52191fc, 0xe2c98f2, 0x133b83e0, 0x18368aee, 0x2915b5c4, 0x2218bcca, 0x3f0fa7d8, 0x3402aed6, 0x5d49d98c, 0x5644d082, 0x4b53cb90, 0x405ec29e, 0x717dfdb4, 0x7a70f4ba, 0x6767efa8, 0x6c6ae6a6, 0xce4a3ac7, 0xc54733c9, 0xd85028db, 0xd35d21d5, 0xe27e1eff, 0xe97317f1, 0xf4640ce3, 0xff6905ed, 0x962272b7, 0x9d2f7bb9, 0x803860ab, 0x8b3569a5, 0xba16568f, 0xb11b5f81, 0xac0c4493, 0xa7014d9d, 0x7e9aaa27, 0x7597a329, 0x6880b83b, 0x638db135, 0x52ae8e1f, 0x59a38711, 0x44b49c03, 0x4fb9950d, 0x26f2e257, 0x2dffeb59, 0x30e8f04b, 0x3be5f945, 0xac6c66f, 0x1cbcf61, 0x1cdcd473, 0x17d1dd7d -------------------------------------------------------------------------------- /AirPlay/Resources/table_s9.bin: -------------------------------------------------------------------------------- 1 | 0x7cba6f01,0x2c06b734,0x220ec640,0x72b21e75,0x82c46d87,0xd278b5b2,0xdc70c4c6,0x8ccc1cf3,0xa4a8a905,0xf4147130,0xfa1c0044,0xaaa0d871,0x5ad6ab83,0xa6a73b6,0x46202c2,0x54dedaf7,0x2854fa3e,0x78e8220b,0x76e0537f,0x265c8b4a,0xd62af8b8,0x8696208d,0x889e51f9,0xd82289cc,0xf0463c3a,0xa0fae40f,0xaef2957b,0xfe4e4d4e,0xe383ebc,0x5e84e689,0x508c97fd,0x304fc8,0x613fde08,0x3183063d,0x3f8b7749,0x6f37af7c,0x9f41dc8e,0xcffd04bb,0xc1f575cf,0x9149adfa,0xb92d180c,0xe991c039,0xe799b14d,0xb7256978,0x47531a8a,0x17efc2bf,0x19e7b3cb,0x495b6bfe,0x35d14b37,0x656d9302,0x6b65e276,0x3bd93a43,0xcbaf49b1,0x9b139184,0x951be0f0,0xc5a738c5,0xedc38d33,0xbd7f5506,0xb3772472,0xe3cbfc47,0x13bd8fb5,0x43015780,0x4d0926f4,0x1db5fec1,0x8ab92d9,0x58174aec,0x561f3b98,0x6a3e3ad,0xf6d5905f,0xa669486a,0xa861391e,0xf8dde12b,0xd0b954dd,0x80058ce8,0x8e0dfd9c,0xdeb125a9,0x2ec7565b,0x7e7b8e6e,0x7073ff1a,0x20cf272f,0x5c4507e6,0xcf9dfd3,0x2f1aea7,0x524d7692,0xa23b0560,0xf287dd55,0xfc8fac21,0xac337414,0x8457c1e2,0xd4eb19d7,0xdae368a3,0x8a5fb096,0x7a29c364,0x2a951b51,0x249d6a25,0x7421b210,0x152e23d0,0x4592fbe5,0x4b9a8a91,0x1b2652a4,0xeb502156,0xbbecf963,0xb5e48817,0xe5585022,0xcd3ce5d4,0x9d803de1,0x93884c95,0xc33494a0,0x3342e752,0x63fe3f67,0x6df64e13,0x3d4a9626,0x41c0b6ef,0x117c6eda,0x1f741fae,0x4fc8c79b,0xbfbeb469,0xef026c5c,0xe10a1d28,0xb1b6c51d,0x99d270eb,0xc96ea8de,0xc766d9aa,0x97da019f,0x67ac726d,0x3710aa58,0x3918db2c,0x69a40319,0xec5d851c,0xbce15d29,0xb2e92c5d,0xe255f468,0x1223879a,0x429f5faf,0x4c972edb,0x1c2bf6ee,0x344f4318,0x64f39b2d,0x6afbea59,0x3a47326c,0xca31419e,0x9a8d99ab,0x9485e8df,0xc43930ea,0xb8b31023,0xe80fc816,0xe607b962,0xb6bb6157,0x46cd12a5,0x1671ca90,0x1879bbe4,0x48c563d1,0x60a1d627,0x301d0e12,0x3e157f66,0x6ea9a753,0x9edfd4a1,0xce630c94,0xc06b7de0,0x90d7a5d5,0xf1d83415,0xa164ec20,0xaf6c9d54,0xffd04561,0xfa63693,0x5f1aeea6,0x51129fd2,0x1ae47e7,0x29caf211,0x79762a24,0x777e5b50,0x27c28365,0xd7b4f097,0x870828a2,0x890059d6,0xd9bc81e3,0xa536a12a,0xf58a791f,0xfb82086b,0xab3ed05e,0x5b48a3ac,0xbf47b99,0x5fc0aed,0x5540d2d8,0x7d24672e,0x2d98bf1b,0x2390ce6f,0x732c165a,0x835a65a8,0xd3e6bd9d,0xddeecce9,0x8d5214dc,0x984c78c4,0xc8f0a0f1,0xc6f8d185,0x964409b0,0x66327a42,0x368ea277,0x3886d303,0x683a0b36,0x405ebec0,0x10e266f5,0x1eea1781,0x4e56cfb4,0xbe20bc46,0xee9c6473,0xe0941507,0xb028cd32,0xcca2edfb,0x9c1e35ce,0x921644ba,0xc2aa9c8f,0x32dcef7d,0x62603748,0x6c68463c,0x3cd49e09,0x14b02bff,0x440cf3ca,0x4a0482be,0x1ab85a8b,0xeace2979,0xba72f14c,0xb47a8038,0xe4c6580d,0x85c9c9cd,0xd57511f8,0xdb7d608c,0x8bc1b8b9,0x7bb7cb4b,0x2b0b137e,0x2503620a,0x75bfba3f,0x5ddb0fc9,0xd67d7fc,0x36fa688,0x53d37ebd,0xa3a50d4f,0xf319d57a,0xfd11a40e,0xadad7c3b,0xd1275cf2,0x819b84c7,0x8f93f5b3,0xdf2f2d86,0x2f595e74,0x7fe58641,0x71edf735,0x21512f00,0x9359af6,0x598942c3,0x578133b7,0x73deb82,0xf74b9870,0xa7f74045,0xa9ff3131,0xf943e904,0xcdbd827d,0x7165b72d,0x7914c323,0xc5ccf673,0xb3bf0483,0xf6731d3,0x71645dd,0xbbce708d,0xdf7b86a5,0x63a3b3f5,0x6bd2c7fb,0xd70af2ab,0xa179005b,0x1da1350b,0x15d04105,0xa9087455,0x2328bd29,0x9ff08879,0x9781fc77,0x2b59c927,0x5d2a3bd7,0xe1f20e87,0xe9837a89,0x555b4fd9,0x31eeb9f1,0x8d368ca1,0x8547f8af,0x399fcdff,0x4fec3f0f,0xf3340a5f,0xfb457e51,0x479d4b01,0x480c8b60,0xf4d4be30,0xfca5ca3e,0x407dff6e,0x360e0d9e,0x8ad638ce,0x82a74cc0,0x3e7f7990,0x5aca8fb8,0xe612bae8,0xee63cee6,0x52bbfbb6,0x24c80946,0x98103c16,0x90614818,0x2cb97d48,0xa699b434,0x1a418164,0x1230f56a,0xaee8c03a,0xd89b32ca,0x6443079a,0x6c327394,0xd0ea46c4,0xb45fb0ec,0x88785bc,0xf6f1b2,0xbc2ec4e2,0xca5d3612,0x76850342,0x7ef4774c,0xc22c421c,0xdc405a09,0x60986f59,0x68e91b57,0xd4312e07,0xa242dcf7,0x1e9ae9a7,0x16eb9da9,0xaa33a8f9,0xce865ed1,0x725e6b81,0x7a2f1f8f,0xc6f72adf,0xb084d82f,0xc5ced7f,0x42d9971,0xb8f5ac21,0x32d5655d,0x8e0d500d,0x867c2403,0x3aa41153,0x4cd7e3a3,0xf00fd6f3,0xf87ea2fd,0x44a697ad,0x20136185,0x9ccb54d5,0x94ba20db,0x2862158b,0x5e11e77b,0xe2c9d22b,0xeab8a625,0x56609375,0x59f15314,0xe5296644,0xed58124a,0x5180271a,0x27f3d5ea,0x9b2be0ba,0x935a94b4,0x2f82a1e4,0x4b3757cc,0xf7ef629c,0xff9e1692,0x434623c2,0x3535d132,0x89ede462,0x819c906c,0x3d44a53c,0xb7646c40,0xbbc5910,0x3cd2d1e,0xbf15184e,0xc966eabe,0x75bedfee,0x7dcfabe0,0xc1179eb0,0xa5a26898,0x197a5dc8,0x110b29c6,0xadd31c96,0xdba0ee66,0x6778db36,0x6f09af38,0xd3d19a68,0x2a579fed,0x968faabd,0x9efedeb3,0x2226ebe3,0x54551913,0xe88d2c43,0xe0fc584d,0x5c246d1d,0x38919b35,0x8449ae65,0x8c38da6b,0x30e0ef3b,0x46931dcb,0xfa4b289b,0xf23a5c95,0x4ee269c5,0xc4c2a0b9,0x781a95e9,0x706be1e7,0xccb3d4b7,0xbac02647,0x6181317,0xe696719,0xb2b15249,0xd604a461,0x6adc9131,0x62ade53f,0xde75d06f,0xa806229f,0x14de17cf,0x1caf63c1,0xa0775691,0xafe696f0,0x133ea3a0,0x1b4fd7ae,0xa797e2fe,0xd1e4100e,0x6d3c255e,0x654d5150,0xd9956400,0xbd209228,0x1f8a778,0x989d376,0xb551e626,0xc32214d6,0x7ffa2186,0x778b5588,0xcb5360d8,0x4173a9a4,0xfdab9cf4,0xf5dae8fa,0x4902ddaa,0x3f712f5a,0x83a91a0a,0x8bd86e04,0x37005b54,0x53b5ad7c,0xef6d982c,0xe71cec22,0x5bc4d972,0x2db72b82,0x916f1ed2,0x991e6adc,0x25c65f8c,0x3baa4799,0x877272c9,0x8f0306c7,0x33db3397,0x45a8c167,0xf970f437,0xf1018039,0x4dd9b569,0x296c4341,0x95b47611,0x9dc5021f,0x211d374f,0x576ec5bf,0xebb6f0ef,0xe3c784e1,0x5f1fb1b1,0xd53f78cd,0x69e74d9d,0x61963993,0xdd4e0cc3,0xab3dfe33,0x17e5cb63,0x1f94bf6d,0xa34c8a3d,0xc7f97c15,0x7b214945,0x73503d4b,0xcf88081b,0xb9fbfaeb,0x523cfbb,0xd52bbb5,0xb18a8ee5,0xbe1b4e84,0x2c37bd4,0xab20fda,0xb66a3a8a,0xc019c87a,0x7cc1fd2a,0x74b08924,0xc868bc74,0xacdd4a5c,0x10057f0c,0x18740b02,0xa4ac3e52,0xd2dfcca2,0x6e07f9f2,0x66768dfc,0xdaaeb8ac,0x508e71d0,0xec564480,0xe427308e,0x58ff05de,0x2e8cf72e,0x9254c27e,0x9a25b670,0x26fd8320,0x42487508,0xfe904058,0xf6e13456,0x4a390106,0x3c4af3f6,0x8092c6a6,0x88e3b2a8,0x343b87f8,0xd78d63dd,0xfb83361,0x7ecc3d69,0xa6f96dd5,0xd50b9da3,0xd3ecd1f,0x7c4ac317,0xa47f93ab,0x1189bbcf,0xc9bceb73,0xb8c8e57b,0x60fdb5c7,0x130f45b1,0xcb3a150d,0xba4e1b05,0x627b4bb9,0x42b23733,0x9a87678f,0xebf36987,0x33c6393b,0x4034c94d,0x980199f1,0xe97597f9,0x3140c745,0x84b6ef21,0x5c83bf9d,0x2df7b195,0xf5c2e129,0x8630115f,0x5e0541e3,0x2f714feb,0xf7441f57,0x66847e58,0xbeb12ee4,0xcfc520ec,0x17f07050,0x64028026,0xbc37d09a,0xcd43de92,0x15768e2e,0xa080a64a,0x78b5f6f6,0x9c1f8fe,0xd1f4a842,0xa2065834,0x7a330888,0xb470680,0xd372563c,0xf3bb2ab6,0x2b8e7a0a,0x5afa7402,0x82cf24be,0xf13dd4c8,0x29088474,0x587c8a7c,0x8049dac0,0x35bff2a4,0xed8aa218,0x9cfeac10,0x44cbfcac,0x37390cda,0xef0c5c66,0x9e78526e,0x464d02d2,0x2a5517cc,0xf2604770,0x83144978,0x5b2119c4,0x28d3e9b2,0xf0e6b90e,0x8192b706,0x59a7e7ba,0xec51cfde,0x34649f62,0x4510916a,0x9d25c1d6,0xeed731a0,0x36e2611c,0x47966f14,0x9fa33fa8,0xbf6a4322,0x675f139e,0x162b1d96,0xce1e4d2a,0xbdecbd5c,0x65d9ede0,0x14ade3e8,0xcc98b354,0x796e9b30,0xa15bcb8c,0xd02fc584,0x81a9538,0x7be8654e,0xa3dd35f2,0xd2a93bfa,0xa9c6b46,0x9b5c0a49,0x43695af5,0x321d54fd,0xea280441,0x99daf437,0x41efa48b,0x309baa83,0xe8aefa3f,0x5d58d25b,0x856d82e7,0xf4198cef,0x2c2cdc53,0x5fde2c25,0x87eb7c99,0xf69f7291,0x2eaa222d,0xe635ea7,0xd6560e1b,0xa7220013,0x7f1750af,0xce5a0d9,0xd4d0f065,0xa5a4fe6d,0x7d91aed1,0xc86786b5,0x1052d609,0x6126d801,0xb91388bd,0xcae178cb,0x12d42877,0x63a0267f,0xbb9576c3,0x3d90f33a,0xe5a5a386,0x94d1ad8e,0x4ce4fd32,0x3f160d44,0xe7235df8,0x965753f0,0x4e62034c,0xfb942b28,0x23a17b94,0x52d5759c,0x8ae02520,0xf912d556,0x212785ea,0x50538be2,0x8866db5e,0xa8afa7d4,0x709af768,0x1eef960,0xd9dba9dc,0xaa2959aa,0x721c0916,0x368071e,0xdb5d57a2,0x6eab7fc6,0xb69e2f7a,0xc7ea2172,0x1fdf71ce,0x6c2d81b8,0xb418d104,0xc56cdf0c,0x1d598fb0,0x8c99eebf,0x54acbe03,0x25d8b00b,0xfdede0b7,0x8e1f10c1,0x562a407d,0x275e4e75,0xff6b1ec9,0x4a9d36ad,0x92a86611,0xe3dc6819,0x3be938a5,0x481bc8d3,0x902e986f,0xe15a9667,0x396fc6db,0x19a6ba51,0xc193eaed,0xb0e7e4e5,0x68d2b459,0x1b20442f,0xc3151493,0xb2611a9b,0x6a544a27,0xdfa26243,0x79732ff,0x76e33cf7,0xaed66c4b,0xdd249c3d,0x511cc81,0x7465c289,0xac509235,0xc048872b,0x187dd797,0x6909d99f,0xb13c8923,0xc2ce7955,0x1afb29e9,0x6b8f27e1,0xb3ba775d,0x64c5f39,0xde790f85,0xaf0d018d,0x77385131,0x4caa147,0xdcfff1fb,0xad8bfff3,0x75beaf4f,0x5577d3c5,0x8d428379,0xfc368d71,0x2403ddcd,0x57f12dbb,0x8fc47d07,0xfeb0730f,0x268523b3,0x93730bd7,0x4b465b6b,0x3a325563,0xe20705df,0x91f5f5a9,0x49c0a515,0x38b4ab1d,0xe081fba1,0x71419aae,0xa974ca12,0xd800c41a,0x3594a6,0x73c764d0,0xabf2346c,0xda863a64,0x2b36ad8,0xb74542bc,0x6f701200,0x1e041c08,0xc6314cb4,0xb5c3bcc2,0x6df6ec7e,0x1c82e276,0xc4b7b2ca,0xe47ece40,0x3c4b9efc,0x4d3f90f4,0x950ac048,0xe6f8303e,0x3ecd6082,0x4fb96e8a,0x978c3e36,0x227a1652,0xfa4f46ee,0x8b3b48e6,0x530e185a,0x20fce82c,0xf8c9b890,0x89bdb698,0x5188e624,0x52ae490,0x307a5848,0x44745039,0x7124ece1,0x83d49a92,0xb684264a,0xc28a2e3b,0xf7da92e3,0x1f2f656,0x34a24a8e,0x40ac42ff,0x75fcfe27,0x870c8854,0xb25c348c,0xc6523cfd,0xf3028025,0x3a7e0a05,0xf2eb6dd,0x7b20beac,0x4e700274,0xbc807407,0x89d0c8df,0xfddec0ae,0xc88e7c76,0x3ea618c3,0xbf6a41b,0x7ff8ac6a,0x4aa810b2,0xb85866c1,0x8d08da19,0xf906d268,0xcc566eb0,0xc376121,0x3967ddf9,0x4d69d588,0x78396950,0x8ac91f23,0xbf99a3fb,0xcb97ab8a,0xfec71752,0x8ef73e7,0x3dbfcf3f,0x49b1c74e,0x7ce17b96,0x8e110de5,0xbb41b13d,0xcf4fb94c,0xfa1f0594,0x33638fb4,0x633336c,0x723d3b1d,0x476d87c5,0xb59df1b6,0x80cd4d6e,0xf4c3451f,0xc193f9c7,0x37bb9d72,0x2eb21aa,0x76e529db,0x43b59503,0xb145e370,0x84155fa8,0xf01b57d9,0xc54beb01,0xdd5ef56d,0xe80e49b5,0x9c0041c4,0xa950fd1c,0x5ba08b6f,0x6ef037b7,0x1afe3fc6,0x2fae831e,0xd986e7ab,0xecd65b73,0x98d85302,0xad88efda,0x5f7899a9,0x6a282571,0x1e262d00,0x2b7691d8,0xe20a1bf8,0xd75aa720,0xa354af51,0x96041389,0x64f465fa,0x51a4d922,0x25aad153,0x10fa6d8b,0xe6d2093e,0xd382b5e6,0xa78cbd97,0x92dc014f,0x602c773c,0x557ccbe4,0x2172c395,0x14227f4d,0xd44370dc,0xe113cc04,0x951dc475,0xa04d78ad,0x52bd0ede,0x67edb206,0x13e3ba77,0x26b306af,0xd09b621a,0xe5cbdec2,0x91c5d6b3,0xa4956a6b,0x56651c18,0x6335a0c0,0x173ba8b1,0x226b1469,0xeb179e49,0xde472291,0xaa492ae0,0x9f199638,0x6de9e04b,0x58b95c93,0x2cb754e2,0x19e7e83a,0xefcf8c8f,0xda9f3057,0xae913826,0x9bc184fe,0x6931f28d,0x5c614e55,0x286f4624,0x1d3ffafc,0x18ba037a,0x2deabfa2,0x59e4b7d3,0x6cb40b0b,0x9e447d78,0xab14c1a0,0xdf1ac9d1,0xea4a7509,0x1c6211bc,0x2932ad64,0x5d3ca515,0x686c19cd,0x9a9c6fbe,0xafccd366,0xdbc2db17,0xee9267cf,0x27eeedef,0x12be5137,0x66b05946,0x53e0e59e,0xa11093ed,0x94402f35,0xe04e2744,0xd51e9b9c,0x2336ff29,0x166643f1,0x62684b80,0x5738f758,0xa5c8812b,0x90983df3,0xe4963582,0xd1c6895a,0x11a786cb,0x24f73a13,0x50f93262,0x65a98eba,0x9759f8c9,0xa2094411,0xd6074c60,0xe357f0b8,0x157f940d,0x202f28d5,0x542120a4,0x61719c7c,0x9381ea0f,0xa6d156d7,0xd2df5ea6,0xe78fe27e,0x2ef3685e,0x1ba3d486,0x6faddcf7,0x5afd602f,0xa80d165c,0x9d5daa84,0xe953a2f5,0xdc031e2d,0x2a2b7a98,0x1f7bc640,0x6b75ce31,0x5e2572e9,0xacd5049a,0x9985b842,0xed8bb033,0xd8db0ceb,0xc0ce1287,0xf59eae5f,0x8190a62e,0xb4c01af6,0x46306c85,0x7360d05d,0x76ed82c,0x323e64f4,0xc4160041,0xf146bc99,0x8548b4e8,0xb0180830,0x42e87e43,0x77b8c29b,0x3b6caea,0x36e67632,0xff9afc12,0xcaca40ca,0xbec448bb,0x8b94f463,0x79648210,0x4c343ec8,0x383a36b9,0xd6a8a61,0xfb42eed4,0xce12520c,0xba1c5a7d,0x8f4ce6a5,0x7dbc90d6,0x48ec2c0e,0x3ce2247f,0x9b298a7,0xc9d39736,0xfc832bee,0x888d239f,0xbddd9f47,0x4f2de934,0x7a7d55ec,0xe735d9d,0x3b23e145,0xcd0b85f0,0xf85b3928,0x8c553159,0xb9058d81,0x4bf5fbf2,0x7ea5472a,0xaab4f5b,0x3ffbf383,0xf68779a3,0xc3d7c57b,0xb7d9cd0a,0x828971d2,0x707907a1,0x4529bb79,0x3127b308,0x4770fd0,0xf25f6b65,0xc70fd7bd,0xb301dfcc,0x86516314,0x74a11567,0x41f1a9bf,0x35ffa1ce,0xaf1d16 -------------------------------------------------------------------------------- /AirPlay/Services/SessionManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading.Tasks; 4 | using AirPlay.Models; 5 | using AirPlay.Models.Enums; 6 | 7 | namespace AirPlay.Services.Implementations 8 | { 9 | public class SessionManager 10 | { 11 | private static SessionManager _current = null; 12 | private ConcurrentDictionary _sessions; 13 | 14 | public static SessionManager Current => _current ?? (_current = new SessionManager()); 15 | 16 | private SessionManager() 17 | { 18 | _sessions = new ConcurrentDictionary(); 19 | } 20 | 21 | public Task GetSessionAsync(string key) 22 | { 23 | _sessions.TryGetValue(key, out Session _session); 24 | return Task.FromResult(_session ?? new Session(key)); 25 | } 26 | 27 | public Task CreateOrUpdateSessionAsync(string key, Session session) 28 | { 29 | _sessions.AddOrUpdate(key, session, (k, old) => 30 | { 31 | var s = new Session(k) 32 | { 33 | EcdhOurs = session.EcdhOurs ?? old.EcdhOurs, 34 | EcdhTheirs = session.EcdhTheirs ?? old.EcdhTheirs, 35 | EdTheirs = session.EdTheirs ?? old.EdTheirs, 36 | EcdhShared = session.EcdhShared ?? old.EcdhShared, 37 | PairVerified = session.PairVerified ?? old.PairVerified, 38 | AesKey = session.AesKey ?? old.AesKey, 39 | AesIv = session.AesIv ?? old.AesIv, 40 | StreamConnectionId = session.StreamConnectionId ?? old.StreamConnectionId, 41 | KeyMsg = session.KeyMsg ?? old.KeyMsg, 42 | DecryptedAesKey = session.DecryptedAesKey ?? old.DecryptedAesKey, 43 | MirroringListener = session.MirroringListener ?? old.MirroringListener, 44 | AudioControlListener = session.AudioControlListener ?? old.AudioControlListener, 45 | SpsPps = session.SpsPps ?? old.SpsPps, 46 | Pts = session.Pts ?? old.Pts, 47 | WidthSource = session.WidthSource ?? old.WidthSource, 48 | HeightSource = session.HeightSource ?? old.HeightSource, 49 | MirroringSession = session.MirroringSession ?? old.MirroringSession, 50 | AudioFormat = session.AudioFormat == AudioFormat.Unknown ? old.AudioFormat : session.AudioFormat 51 | }; 52 | return s; 53 | }); 54 | return Task.CompletedTask; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AirPlay/Utils/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace AirPlay.Utils 7 | { 8 | public static class Extensions 9 | { 10 | public static string BytesToHex(this byte[] data) 11 | { 12 | return string.Join(string.Empty, data.Select(s => s.ToString("X2"))); 13 | } 14 | 15 | public static byte[] HexToBytes(this string hex) 16 | { 17 | byte[] data = new byte[hex.Length / 2]; 18 | for (int index = 0; index < data.Length; index++) 19 | { 20 | string byteValue = hex.Substring(index * 2, 2); 21 | data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); 22 | } 23 | 24 | return data; 25 | } 26 | 27 | public static byte[] Reverse(this byte[] b) 28 | { 29 | Array.Reverse(b); 30 | return b; 31 | } 32 | 33 | public static UInt16 ReadUInt16BE(this BinaryReader binRdr) 34 | { 35 | return BitConverter.ToUInt16(binRdr.ReadBytesRequired(sizeof(UInt16)).Reverse(), 0); 36 | } 37 | 38 | public static Int16 ReadInt16BE(this BinaryReader binRdr) 39 | { 40 | return BitConverter.ToInt16(binRdr.ReadBytesRequired(sizeof(Int16)).Reverse(), 0); 41 | } 42 | 43 | public static UInt32 ReadUInt32BE(this BinaryReader binRdr) 44 | { 45 | return BitConverter.ToUInt32(binRdr.ReadBytesRequired(sizeof(UInt32)).Reverse(), 0); 46 | } 47 | 48 | public static Int32 ReadInt32BE(this BinaryReader binRdr) 49 | { 50 | return BitConverter.ToInt32(binRdr.ReadBytesRequired(sizeof(Int32)).Reverse(), 0); 51 | } 52 | 53 | public static UInt64 ReadUInt64BE(this BinaryReader binRdr) 54 | { 55 | return BitConverter.ToUInt64(binRdr.ReadBytesRequired(sizeof(UInt64)).Reverse(), 0); 56 | } 57 | 58 | public static Int64 ReadInt64BE(this BinaryReader binRdr) 59 | { 60 | return BitConverter.ToInt64(binRdr.ReadBytesRequired(sizeof(Int64)).Reverse(), 0); 61 | } 62 | 63 | public static byte[] ReadBytesRequired(this BinaryReader binRdr, int byteCount) 64 | { 65 | var result = binRdr.ReadBytes(byteCount); 66 | 67 | if (result.Length != byteCount) 68 | throw new EndOfStreamException(string.Format("{0} bytes required from stream, but only {1} returned.", byteCount, result.Length)); 69 | 70 | return result; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /AirPlay/Utils/LibraryLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace AirPlay.Utils 8 | { 9 | public static class LibraryLoader 10 | { 11 | static LibraryLoader() 12 | { 13 | if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX || (int)Environment.OSVersion.Platform == 128) 14 | { 15 | // Theoretically needed only w/ linux 16 | LoadPosixLibrary(); 17 | } 18 | } 19 | 20 | static void LoadPosixLibrary() 21 | { 22 | const int RTLD_NOW = 2; 23 | string rootDirectory = AppDomain.CurrentDomain.BaseDirectory; 24 | string assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 25 | 26 | var isOsx = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 27 | var libFile = isOsx ? "libdl.dylib" : "libdl.so"; 28 | 29 | var arch = (isOsx ? "osx" : "linux") + "-" + (Environment.Is64BitProcess ? "x64" : "x86"); 30 | 31 | // Search a few different locations for our native assembly 32 | var paths = new[] 33 | { 34 | Path.Combine(rootDirectory, "runtimes", arch, "native", libFile), 35 | Path.Combine(rootDirectory, libFile), 36 | Path.Combine("/usr/local/lib", libFile), 37 | Path.Combine("/usr/lib", libFile) 38 | }; 39 | 40 | foreach (var path in paths) 41 | { 42 | if (path == null) 43 | { 44 | continue; 45 | } 46 | 47 | if (File.Exists(path)) 48 | { 49 | var addr = dlopen(path, RTLD_NOW); 50 | if (addr == IntPtr.Zero) 51 | { 52 | var error = Marshal.PtrToStringAnsi(dlerror()); 53 | throw new Exception("dlopen failed: " + path + " : " + error); 54 | } 55 | 56 | return; 57 | } 58 | } 59 | 60 | if (!isOsx) 61 | { 62 | // Throw exception only with linux platform 63 | // OSX should works without libdl preload 64 | var error = "dlopen failed: unable to locate library " + libFile + ". Searched: " + paths.Aggregate((a, b) => a + "; " + b); 65 | Console.WriteLine(error); 66 | throw new Exception(error); 67 | } 68 | } 69 | 70 | public static IntPtr DlOpen(string fileName, int flags) 71 | { 72 | if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX || (int)Environment.OSVersion.Platform == 128) 73 | { 74 | return dlopen(fileName, flags); 75 | } 76 | else 77 | { 78 | var dllHandle = LoadLibrary(fileName); 79 | if (dllHandle == IntPtr.Zero) 80 | { 81 | var error = Marshal.GetLastWin32Error().ToString(); 82 | // Code 193 - Arch error / ex: Win32 dll on Win64 error 83 | // Code 162 - Dependecy libraries not found 84 | Console.WriteLine($"Error 'LoadLibrary': {error}"); 85 | throw new Exception(error); 86 | } 87 | 88 | return dllHandle; 89 | } 90 | } 91 | 92 | public static IntPtr DlSym(IntPtr handle, string symbol) 93 | { 94 | if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX || (int)Environment.OSVersion.Platform == 128) 95 | { 96 | return dlsym(handle, symbol); 97 | } 98 | else 99 | { 100 | return GetProcAddress(handle, symbol); 101 | } 102 | } 103 | 104 | public static IntPtr DlClose (IntPtr handle) 105 | { 106 | if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX || (int)Environment.OSVersion.Platform == 128) 107 | { 108 | return dlclose(handle); 109 | } 110 | else 111 | { 112 | return FreeLibrary(handle); 113 | } 114 | } 115 | 116 | // Used w/ Win32 & Win64 117 | 118 | [DllImport("kernel32.dll", EntryPoint = "LoadLibrary", SetLastError = true)] 119 | private static extern IntPtr LoadLibrary(string filename); 120 | 121 | [DllImport("kernel32.dll")] 122 | private static extern IntPtr GetProcAddress(IntPtr handle, string symbol); 123 | 124 | [DllImport("kernel32.dll")] 125 | private static extern IntPtr FreeLibrary(IntPtr handle); 126 | 127 | // Used w/ OSX & Linux 128 | 129 | [DllImport("libdl")] 130 | private static extern IntPtr dlopen(string fileName, int flags); 131 | 132 | [DllImport("libdl")] 133 | private static extern IntPtr dlerror(); 134 | 135 | [DllImport("libdl")] 136 | private static extern IntPtr dlsym(IntPtr handle, string symbol); 137 | 138 | [DllImport("libdl")] 139 | private static extern IntPtr dlclose(IntPtr handle); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /AirPlay/Utils/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using Org.BouncyCastle.Crypto; 7 | using Org.BouncyCastle.Crypto.Parameters; 8 | using Org.BouncyCastle.Security; 9 | 10 | namespace AirPlay.Utils 11 | { 12 | public static class Utilities 13 | { 14 | public const string PAIR_VERIFY_AES_KEY = "Pair-Verify-AES-Key"; 15 | public const string PAIR_VERIFY_AES_IV = "Pair-Verify-AES-IV"; 16 | 17 | public static byte[] CopyOfRange(byte[] src, int start, int end) 18 | { 19 | int len = end - start; 20 | byte[] dest = new byte[len]; 21 | Array.Copy(src, start, dest, 0, len); 22 | return dest; 23 | } 24 | 25 | public static byte[] Hash(byte[] first, byte[] last) 26 | { 27 | var sha512 = new SHA512CryptoServiceProvider(); 28 | byte[] combined = first.Concat(last).ToArray(); 29 | byte[] hashed = sha512.ComputeHash(combined); 30 | return hashed; 31 | } 32 | 33 | public static ushort SeqNumCmp(int s1, int s2) 34 | { 35 | return (ushort)(s1 - s2); 36 | } 37 | 38 | public static void Swap(byte[] arr, int idxA, int idxB) 39 | { 40 | using (var mem = new MemoryStream(arr)) 41 | using (var reader = new BinaryReader(mem)) 42 | using (var writer = new BinaryWriter(mem)) 43 | { 44 | mem.Position = idxA; 45 | var a = reader.ReadInt32(); 46 | 47 | mem.Position = idxB; 48 | var b = reader.ReadInt32(); 49 | 50 | mem.Position = idxB; 51 | writer.Write(a); 52 | 53 | mem.Position = idxA; 54 | writer.Write(b); 55 | } 56 | } 57 | 58 | public static byte[] WriteWavHeader(ushort numchannels, uint sampleRate, ushort bitsPerSample, uint tot) 59 | { 60 | var stream = new MemoryStream(); 61 | 62 | BinaryWriter bwl = new BinaryWriter(stream); 63 | bwl.Write(new char[4] { 'R', 'I', 'F', 'F' }); 64 | bwl.Write(tot + 38); 65 | bwl.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' }); 66 | bwl.Write((int)bitsPerSample); 67 | bwl.Write((short)1); 68 | bwl.Write(numchannels); 69 | bwl.Write(sampleRate); 70 | bwl.Write((int)(sampleRate * ((bitsPerSample * numchannels) / 8))); 71 | bwl.Write((short)((bitsPerSample * numchannels) / 8)); 72 | bwl.Write(bitsPerSample); 73 | bwl.Write(new char[4] { 'd', 'a', 't', 'a' }); 74 | bwl.Write(tot); 75 | 76 | return stream.ToArray(); 77 | } 78 | 79 | public static IBufferedCipher InitializeChiper(byte[] ecdhShared) 80 | { 81 | var pairverifyaeskey = Encoding.UTF8.GetBytes(PAIR_VERIFY_AES_KEY); 82 | var pairverifyaesiv = Encoding.UTF8.GetBytes(PAIR_VERIFY_AES_IV); 83 | 84 | byte[] digestAesKey = Utilities.Hash(pairverifyaeskey, ecdhShared); 85 | byte[] sharedSecretSha512AesKey = Utilities.CopyOfRange(digestAesKey, 0, 16); 86 | 87 | byte[] digestAesIv = Utilities.Hash(pairverifyaesiv, ecdhShared); 88 | 89 | byte[] sharedSecretSha512AesIv = Utilities.CopyOfRange(digestAesIv, 0, 16); 90 | 91 | var aesCipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); 92 | 93 | KeyParameter keyParameter = ParameterUtilities.CreateKeyParameter("AES", sharedSecretSha512AesKey); 94 | var cipherParameters = new ParametersWithIV(keyParameter, sharedSecretSha512AesIv, 0, sharedSecretSha512AesIv.Length); 95 | 96 | aesCipher.Init(true, cipherParameters); 97 | 98 | return aesCipher; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /AirPlay/appsettings_linux.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AirPlayReceiver": { 8 | "Instance": "steebono-test-1", 9 | "AirTunesPort": 5000, 10 | "AirPlayPort": 7000, 11 | "DeviceMacAddress": "11:22:33:44:55:66" 12 | }, 13 | "CodecLibraries": { 14 | "AACLibPath": "/usr/local/lib/libfdk-aac.dylib", 15 | "ALACLibPath": "/usr/local/lib/libalac.dylib" 16 | }, 17 | "Dump": { 18 | "Path": "/path_here/" 19 | } 20 | } -------------------------------------------------------------------------------- /AirPlay/appsettings_osx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AirPlayReceiver": { 8 | "Instance": "steebono-test", 9 | "AirTunesPort": 5000, 10 | "AirPlayPort": 7000, 11 | "DeviceMacAddress": "10:20:30:40:50:60" 12 | }, 13 | "CodecLibraries": { 14 | "AACLibPath": "/usr/local/lib/libfdk-aac.dylib", 15 | "ALACLibPath": "/usr/local/lib/libalac.dylib" 16 | }, 17 | "Dump": { 18 | "Path": "/Users/steebono/Desktop/dump/" 19 | } 20 | } -------------------------------------------------------------------------------- /AirPlay/appsettings_win.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AirPlayReceiver": { 8 | "Instance": "steebono-test-1", 9 | "AirTunesPort": 5000, 10 | "AirPlayPort": 7000, 11 | "DeviceMacAddress": "11:22:33:44:55:66" 12 | }, 13 | "CodecLibraries": { 14 | "AACLibPath": "C:\\msys64\\home\\sbono\\fdk-aac-master\\.libs\\libfdk-aac-2.dll", 15 | "ALACLibPath": "C:\\msys64\\mingw64\\bin\\libalac-0.dll" 16 | }, 17 | "Dump": { 18 | "Path": "C:\\Users\\Foo\\Desktop\\Dump\\" 19 | } 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stefano Bono 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AirPlay Receiver 2 | Open source implementation of AirPlay 2 Mirroring / Audio protocol in C# .Net Core. 3 | 4 | ## Generic 5 | 6 | Tested on macOS with iPhone 12 Pro iOS14. 7 | 8 | The project is fully functional, but the AAC and ALAC libraries written in C ++ must be built. 9 | 10 | ## How To 11 | 12 | ### Build AAC Codec 13 | To download, build and install fdk-aac do the following: 14 | 15 | Clone the repository and cd into the folder: 16 | ``` 17 | $ git clone https://github.com/mstorsjo/fdk-aac.git 18 | $ cd fdk-aac 19 | ``` 20 | 21 | Configure the build and make the library: 22 | ``` 23 | $ autoreconf -fi 24 | $ ./configure 25 | $ make 26 | ``` 27 | 28 | ### Build ALAC Codec 29 | To download, build and install alac do the following: 30 | 31 | Clone the repository and cd into the folder: 32 | ``` 33 | $ git clone https://github.com/mikebrady/alac.git 34 | $ cd alac 35 | ``` 36 | 37 | Download and paste 'GiteKat''s files in 'alac/codec' folder cloned before 38 | ``` 39 | $ https://github.com/GiteKat/LibALAC/tree/master/LibALAC 40 | ``` 41 | 42 | The 'mikebrady''s source code does not contains 'extern' keyword. 43 | We need external linkage so we use 'GiteKat''s source code files. 44 | 45 |
46 | 47 | Edit makefile.original as follow 48 | 49 | 50 | ``` 51 | # libalac make 52 | 53 | CFLAGS = -g -O3 -c 54 | LFLAGS = -Wall 55 | CC = g++ 56 | 57 | SRCDIR = . 58 | OBJDIR = ./obj 59 | INCLUDES = . 60 | 61 | HEADERS = \ 62 | $(SRCDIR)/EndianPortable.h \ 63 | $(SRCDIR)/aglib.h \ 64 | $(SRCDIR)/ALACAudioTypes.h \ 65 | $(SRCDIR)/ALACBitUtilities.h\ 66 | $(SRCDIR)/ALACDecoder.h \ 67 | $(SRCDIR)/ALACEncoder.h \ 68 | $(SRCDIR)/LibALAC.h \ 69 | $(SRCDIR)/dplib.h \ 70 | $(SRCDIR)/matrixlib.h 71 | 72 | SOURCES = \ 73 | $(SRCDIR)/EndianPortable.c \ 74 | $(SRCDIR)/ALACBitUtilities.c \ 75 | $(SRCDIR)/ALACDecoder.cpp \ 76 | $(SRCDIR)/ALACEncoder.cpp \ 77 | $(SRCDIR)/LibALAC.cpp \ 78 | $(SRCDIR)/ag_dec.c \ 79 | $(SRCDIR)/ag_enc.c \ 80 | $(SRCDIR)/dp_dec.c \ 81 | $(SRCDIR)/dp_enc.c \ 82 | $(SRCDIR)/matrix_dec.c \ 83 | $(SRCDIR)/matrix_enc.c 84 | 85 | OBJS = \ 86 | EndianPortable.o \ 87 | ALACBitUtilities.o \ 88 | ALACDecoder.o \ 89 | ALACEncoder.o \ 90 | LibALAC.o \ 91 | ag_dec.o \ 92 | ag_enc.o \ 93 | dp_dec.o \ 94 | dp_enc.o \ 95 | matrix_dec.o \ 96 | matrix_enc.o 97 | 98 | libalac.a: $(OBJS) 99 | ar rcs libalac.a $(OBJS) 100 | 101 | EndianPortable.o : EndianPortable.c 102 | $(CC) -I $(INCLUDES) $(CFLAGS) EndianPortable.c 103 | 104 | ALACBitUtilities.o : ALACBitUtilities.c 105 | $(CC) -I $(INCLUDES) $(CFLAGS) ALACBitUtilities.c 106 | 107 | ALACDecoder.o : ALACDecoder.cpp 108 | $(CC) -I $(INCLUDES) $(CFLAGS) ALACDecoder.cpp 109 | 110 | ALACEncoder.o : ALACEncoder.cpp 111 | $(CC) -I $(INCLUDES) $(CFLAGS) ALACEncoder.cpp 112 | 113 | LibALAC.o : LibALAC.cpp 114 | $(CC) -I $(INCLUDES) $(CFLAGS) LibALAC.cpp 115 | 116 | ag_dec.o : ag_dec.c 117 | $(CC) -I $(INCLUDES) $(CFLAGS) ag_dec.c 118 | 119 | ag_enc.o : ag_enc.c 120 | $(CC) -I $(INCLUDES) $(CFLAGS) ag_enc.c 121 | 122 | dp_dec.o : dp_dec.c 123 | $(CC) -I $(INCLUDES) $(CFLAGS) dp_dec.c 124 | 125 | dp_enc.o : dp_enc.c 126 | $(CC) -I $(INCLUDES) $(CFLAGS) dp_enc.c 127 | 128 | matrix_dec.o : matrix_dec.c 129 | $(CC) -I $(INCLUDES) $(CFLAGS) matrix_dec.c 130 | 131 | matrix_enc.o : matrix_enc.c 132 | $(CC) -I $(INCLUDES) $(CFLAGS) matrix_enc.c 133 | 134 | clean: 135 | -rm $(OBJS) libalac.a 136 | ``` 137 | 138 |
139 | 140 |
141 | 142 | Edit makefile.am as follow 143 | 144 | 145 | ``` 146 | ## Copyright (c) 2013 Tiancheng "Timothy" Gu 147 | ## Modifications copyright (c) 2016 Mike Brady 148 | ## Licensed under the Apache License, Version 2.0 (the "License"); 149 | ## you may not use this file except in compliance with the License. 150 | ## You may obtain a copy of the License at 151 | ## 152 | ## http://www.apache.org/licenses/LICENSE-2.0 153 | ## 154 | ## Unless required by applicable law or agreed to in writing, software 155 | ## distributed under the License is distributed on an "AS IS" BASIS, 156 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 157 | ## See the License for the specific language governing permissions and 158 | ## limitations under the License. 159 | 160 | lib_LTLIBRARIES = libalac.la 161 | 162 | libalac_la_CPPFLAGS = -Wno-multichar 163 | libalac_la_LDFLAGS = -version-info @ALAC_VERSION@ 164 | 165 | libalac_la_SOURCES = \ 166 | EndianPortable.c \ 167 | ALACBitUtilities.c \ 168 | ALACDecoder.cpp \ 169 | ALACEncoder.cpp \ 170 | LibALAC.cpp \ 171 | ag_dec.c \ 172 | ag_enc.c \ 173 | dp_dec.c \ 174 | dp_enc.c \ 175 | matrix_dec.c \ 176 | matrix_enc.c 177 | 178 | pkgconfigdir = $(libdir)/pkgconfig 179 | pkgconfig_DATA = alac.pc 180 | 181 | # Install to include/alac 182 | alacincludedir = $(includedir)/alac 183 | 184 | # Install everything 185 | alacinclude_HEADERS = *.h 186 | ``` 187 | 188 |
189 | 190 | Configure the build and make the library: 191 | ``` 192 | $ autoreconf -fi 193 | $ ./configure 194 | $ make 195 | ``` 196 | 197 | ### Linux 198 | On terminal type the follow to install build tools 199 | ``` 200 | apt-get install build-essential autoconf automake libtool 201 | ``` 202 | Add compiled DLL path into 'appsettings_linux.json' file. 203 | 204 | ### MacOS 205 | On terminal type the follow to install build tools 206 | 207 | ``` 208 | brew install autoconf automake libtool 209 | ``` 210 | Add compiled DLL path into 'appsettings_osx.json' file. 211 | 212 | ### Windows 213 | 214 | Use [this](http://www.gaia-gis.it/gaia-sins/mingw64_how_to.html#env) tutorial to understand how to install build tools and how to compile source code on Windows. 215 | You need MinGW32 or MinGW64 based on arch. 216 | 217 | Put repo folders inside msys64 home folder ('C:\msys64\home\'). 218 | ![homefolder](https://user-images.githubusercontent.com/11635557/116857182-b0b9b180-abfc-11eb-8e75-5d1b23d7541f.PNG) 219 | 220 | Start an mingw32.exe or mingw64.exe shell based on arch and execute commands. 221 | ![mingwshell](https://user-images.githubusercontent.com/11635557/116857648-756bb280-abfd-11eb-8d6b-43d474f4a27b.PNG) 222 | 223 | The compiled dll will be saved in 'C:\\msys64\\home\\username\\fdk-aac-master\\.libs\\'. 224 | Add compiled DLL path into 'appsettings_win.json' file. 225 | 226 | TIP 1: If the ALAC library gives you an error during the compilation try to insert the following arguments in the 'makefile.am' file: 227 | ``` 228 | libalac_la_LDFLAGS = -version-info @ALAC_VERSION@ -no-undefined -static-libgcc -static-libstdc++ 229 | ``` 230 | TIP 2: If the error code 126 appears when loading the dll, try to import all the dlls located in the C:\msys64\bin\ folder into the bin folder of the project. 231 | TIP 3: If the error code 193 appears when loading the dll, it means that you are trying to load a dll with the wrong architecture, so you have to compile the dll with the other mingwXX.exe. 232 | 233 | ## Wiki 234 | 235 | Here you will find an [Article](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) where I explain how the whole AirPlay 2 protocol works. 236 | 237 | ## Disclamier 238 | 239 | All the resources in this repository are written using only opensource projects. 240 | The code and related resources are meant for educational purposes only. 241 | I do not take any responsibility for the use that will be made of it. 242 | 243 | ## Credits 244 | 245 | Inspired by others AirPlay open source projects. 246 | Big ty to OmgHax.c's author 😱. 247 | 248 | ## If you want support me 🔥 249 | 250 | If you appreciate my work, consider buying me a cup of coffee to keep me recharged ☕ 251 | 252 | Buy Me A Coffee 253 | 254 | ... or ... crypto ... 255 | 256 | BTC: 1BXhfC5U75G2H8b99wk5AedGFxtqJ6xf8q 257 | BCH: 1BXhfC5U75G2H8b99wk5AedGFxtqJ6xf8q 258 | ETH: 0x4Fc12c7C71C581aBc77945Ab9cFBA8DF9692b713 (ERC20) 259 | --------------------------------------------------------------------------------