FDH2 / UxPlay

AirPlay Unix mirroring server
GNU General Public License v3.0
1.65k stars 83 forks source link

Work in Progress for airplay video streaming (was: Alternative Implementation. #134

Closed thiccaxe closed 5 months ago

thiccaxe commented 2 years ago

It seems that this implementation of airplay protocol (https://github.com/alexfansz/AirplayServer-1) has all of the airplay protocol implemented (and is compatible with windows). It just needs a front end.

EDIT (2024/06/24) : this code is gone now, but can be recognized as an early version of code now released https://github.com/air-display/apsdk-public

Just posting this here, if anyone from the community wants to add such a front end to this. It also has cleaned up code and what not. I am currently testing this software and may ultimately create a frontend implementation (as it is only a library)

fduncanh commented 2 years ago

Have you found where this AirplayServer-1 code comes from? It was dumped all at one time on Github on 1 Dec 2019. It must have some (missing) history elsewhere in some other github project, unless it was written from scratch. It's in cpp, and doesn't have any license or history, which is problematical. (I did not see any code I recognized in it). Does it have the AirPlay2 videostreaming implemented in it somewhere?

Another implementation (in C#) like this (with no front end) is this one: https://github.com/SteeBono/airplayreceiver which states an MIT license, but on inspection of the code clearly derives from (or is inspired by) the dsafa22 code that is the starting point of RPiPlay. I didn't see anything in that one that is beyond what is in UxPlay, but was not able to build it.

fduncanh commented 2 years ago

@thiccaxe There aren't any copyright headers with license info on any of the code files in AirplayServer-1 (except on "third-party" files) Maybe you could contact the publisher (who hopefully is the sole author too) at the published email address to find out, if you want to build something to use the AirplayServer-1 project.:

I guess you could build a front end that uses the AirplayServer-1 library, and tell users to download it and build it themselves.

Alternatively, the code might be useful to give insight into the AirPlay2 video streaming protocol, assuming it is included, without actually copying the code.

EDIT: everything turned out to be AirPlay 1

thiccaxe commented 2 years ago

Your last comment was my plan, as I too did not see any copyrights or sources.

I think that the history of the code is also suspect; I have seen comments that discuss other AirServer-1 repositories gone missing.

However as a whole the repository is a good source for improvements to uxplay and other airplay implementation. Especially when it comes to windows, as the code seems to support it from the ground up.

fduncanh commented 2 years ago

@thiccaxe Please report results of your tests!

Is there a description of the library's API in there somewhere?

As this is not an issue, I'll close it, but please continue to post your findings here

thiccaxe commented 2 years ago

The demo program is able to successfully negotiate the 'fp-setup2' handshake. Ultimately, it can get a URL for the video from an app like Nebula. it looks something like https://content.api.nebula.app/video/{author}-{title}/manifest/{id}.m3u8?token={token}. It does not hang when airplaying from youtube (from the icon built-in to youtube, not ios screen mirroring) but recieves a unusual URL "mlhls://localhost/master.m3u8". [1]. Screen Mirroring and audio sharing also work. But note that nothing is played, this data is just dumped to the console.

The implementation also supports ipv6 and ios / appletv devices seem to prefer this overall.

However, it seems as if this may not be the right choice after all. While good for the http-live streaming aspect, it seems apple has updated their alac protocol, or is atleast using new modes which the alac decoder used does not support.

At the end of the day hopefully this source code can be used as a reference to implement new features.

[1] It is unclear as to where this points. The iphone doesn't seem to open any ports, ipv6 or ipv4. mlhls protocol seems to be totally undocumented but is most likely some extension of Http live streaming. UPDATE: it seems another http request is made that doesn't seem to be totally handled by this library. It uses some "FCUP" protocol but gives a URL: https://{subdoman}.googlevideo.com/videoplayback/id/{id}/itag/{number}/source/youtube/cpn/{videoid?}/expire/{unixtimestamp}/ei/{base64}/ip/{ipv4}/requiressl/yes/ratebypass/yes/pfa/1/goi/133/sgoap/clen{something}/hls_chunk_host/{subdoman}.googlevideo.com/mh/N_/mm/31,26/mn/ {subdomains}/ms/au,onr/mv/m/mvi/1/pl/22/force_finished/1/initcwndbps/{number}/vprv/1/playlist_type/DVR/txp/{number}/mt/{unixtimstamp?}/fvip/4/keepalive/yes/fexp/{comma seperated integer list}/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,goi,sgoap,force_finished,vprv,playlist_type/sig/{BASE64}/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/{BASE64}/playlist/index.m3u8 - THIS WORKS WITH VLC, BUT AUDIO ONLY

Validity is 6 hours for that link.

fduncanh commented 2 years ago

I'm not sure "fp_setup2" does anything in AirplayServer-1....

void ap_airplay_connection::post_fp_setup2_handler(const request &req,
                                                   response &res) {
  DUMP_REQUEST_WITH_CONNECTION(req);

  res.with_status(ok);
}
thiccaxe commented 2 years ago

It in fact does not seem to do anything at all. But getting to that point is difficult. I seem to have the exact same responses to the iOS client as a real Apple TV, but it never makes the fp-setup2 request. Apparently you have to switch between RTSP/1.0 and HTTP/1.1 and HTTP/1.0, make sure certain values are set in the plists, etc. (but this did not seem to help the situation)

fduncanh commented 2 years ago

@thiccaxe Exactly. the fp_setup2 call is a dummy call. I followed the code in src/service/ap_airplay_service.cpp comparing it to UxPlay's raop.c with raop_handlers.h included. The RTSP stuff is identical (except POST/audioMode is not present in uxplay).

The only difference is that the 32bytes sent by the client in the pair-setup set are used to initialize the pairing crypto, while in uxplay they are unused (the android "Happy Cast" app (com.hpplay.happyplay 7.1.0 APK) with unobfuscated code that dsafa22 used to create the original AirServer uses it to create the "session-id " (context) linked to the client, as AppleTV allows 16 simultaneous connections. (uxplay only allows 1). dsafa22 just says "ignore the 32bytes", and just create the crypto without them.

See https://github.com/FDH2/UxPlay/wiki/AirPlay2 which is a translation of the chinese original https://www.itread01.com/hkeyxif.html (which was also used as the basis of https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol ).

I don't see any AirPlay2 use of encryption for server-client communication after pairing (This might be fp-setup2 ?) so I guess AirplayServer-1 is still using "Legacy pairing", like uxplay.

The extra features in AirplayServer-1 are that it supports the HTTP requests listed below, and "FCUP", which are not in AirPlay. I did find the Happy Cast v7.1.0 app available on the internet, and had a link for it in the wiki, but it's gone now (maybe because this drew attention to it with the link). dsafa22 says that only v7.1.0 was the "magic" one with unobfuscated code (except for crypto) Right now I don't know if this FCUP stuff is in there but dsafa22 just didn't implement it, or it's not there. (What is FCUP for? (internet search for this produces lots of results for something unrelated to airplay....) it uses the session_id.)

(It's possible that AirplayServer-1 is derived from based on Happy Cast). dsafa didnt take any code from happycast, just analyzed it to understand the protocol..

void ap_airplay_connection::send_fcup_request(int request_id,

                                              const std::string &url,
                                              const std::string &session_id) {

void ap_airplay_connection::initialize_request_handlers() {
  // The request route table
  request_route_t routes_table[] = {
      {"RTSP", "OPTIONS", "", RH(options_handler)},
      {"RTSP", "POST", "/pair-setup", RH(post_pair_setup_handler)},
      {"RTSP", "POST", "/pair-verify", RH(post_pair_verify_handler)},
      {"RTSP", "POST", "/fp-setup", RH(post_fp_setup_handler)},
      {"RTSP", "SETUP", "*", RH(setup_handler)},
      {"RTSP", "GET", "/info", RH(get_info_handler)},
      {"RTSP", "POST", "/feedback", RH(post_feedback_handler)},
      {"RTSP", "RECORD", "*", RH(record_handler)},
      {"RTSP", "GET_PARAMETER", "*", RH(get_parameter_handler)},
      {"RTSP", "SET_PARAMETER", "*", RH(set_parameter_handler)},
      {"RTSP", "TEARDOWN", "*", RH(teardown_handler)},
      {"RTSP", "FLUSH", "*", RH(flush_handler)},
      {"RTSP", "POST", "/audioMode", RH(post_audioMode)},
      {"HTTP", "GET", "/server-info", RH(get_server_info_handler)},
      {"HTTP", "POST", "/fp-setup", RH(post_fp_setup_handler)},
      {"HTTP", "POST", "/fp-setup2", RH(post_fp_setup2_handler)},
      {"HTTP", "POST", "/reverse", RH(post_reverse_handler)},
      {"HTTP", "POST", "/play", RH(post_play_handler)},
      {"HTTP", "POST", "/scrub", RH(post_scrub_handler)},
      {"HTTP", "POST", "/rate", RH(post_rate_handler)},
      {"HTTP", "POST", "/stop", RH(post_stop_handler)},
      {"HTTP", "POST", "/action", RH(post_action_handler)},
      {"HTTP", "GET", "/playback-info", RH(get_playback_info_handler)},
      {"HTTP", "PUT", "/setProperty", RH(put_setProperty_handler)},
      {"HTTP", "POST", "/getProperty", RH(post_getProperty_handler)},
  };

  // Register all the request handlers
  for (auto route : routes_table) {
    register_request_route(route);
  }
fduncanh commented 2 years ago

OK, by searching for text strings, it appears that everything in the request route table above (as well as FCUP stuff) was implemented in the android source discovered by dsafa22 (except {"RTSP", "POST", "/audioMode", RH(post_audioMode)}), which is an unimplemented dummy function that just returns "OK".

void ap_airplay_connection::post_audioMode(const request &req, response &res) {
  DUMP_REQUEST_WITH_CONNECTION(req);

  res.with_status(ok);
}

It's very possible that AirplayServer-1 is a translation of the android app with unobfuscated java code that dsafa22 found, but into c++.

It just has some features (FCUP) that dsafa22 did not implement, (dsafa22 just did AirPlay-Mirror protocol). dsfa22 did not actually take code from the android app (so the dsafa22 code is clean), just used it to discover the pieces of the mirror protocol that were needed, and documented it

thiccaxe commented 2 years ago

Some additional requests I have seen in wireshark

PUT /setProperty?textMarkupArray HTTP/1.1
Content-Type: application/x-apple-binary-plist
Content-Length: 165
X-Apple-Device-ID: 0xaaaaaaaaaaaa
X-Apple-Session-ID: UUID
User-Agent: AirPlay/635.78.1

bplist00ÑUvalue¡Ò_2CMWritingDirectionOrthogonalLinePositionPercentage^CMTextSelector#@Y_CMTextSelectorDefaultM\e}
{
  "value": [
    {
      "CMWritingDirectionOrthogonalLinePositionPercentage": 100,
      "CMTextSelector": "CMTextSelectorDefault"
    }
  ]
}

PUT /setProperty?selectedMediaArray HTTP/1.1
Content-Type: application/x-apple-binary-plist
Content-Length: 210
X-Apple-Device-ID: 0x84ab1a861aa6
X-Apple-Session-ID: 9e550557-2f36-4436-918a-9d89ffcd958d
User-Agent: AirPlay/635.78.1

bplist00ÑUvalue¢
Ó   _MediaSelectionGroupMediaType_AutomaticallySelected_!MediaSelectionOptionsPersistentIDTsoun Ñ_MediaSelectionGroupMediaTypeTsbtl:Rv{|~ 
¥
{
  "value": [
    {
      "MediaSelectionGroupMediaType": "soun",
      "AutomaticallySelected": true,
      "MediaSelectionOptionsPersistentID": 0
    },
    {
      "MediaSelectionGroupMediaType": "sbtl"
    }
  ]
}

NOTE I have seen something similar from dumped logs of AirServer (closed source):

[
  "processSetProperty",
  "selectedMediaArray",
  [
    {
      "MediaSelectionGroupMediaType": "sbtl"
    },
    {
      "MediaSelectionGroupMediaType": "soun",
      "MediaSelectionOptionsPersistentID": 1
    }
  ]
]
[
  "updateSelectedMediaArray:",
  [
    {
      "MediaSelectionGroupMediaType": "sbtl"
    },
    {
      "MediaSelectionGroupMediaType": "soun",
      "MediaSelectionOptionsPersistentID": 1
    }
  ]
]
thiccaxe commented 2 years ago

Also it seems the "FCUP" was somewhat recognized:

void send_fcup_request(int request_id, const std::string &url,
                         const std::string &session_id);

but anyway here is the dump:

POST /action HTTP/1.1
Content-Type: application/x-apple-binary-plist
Content-Length: 4870
X-Apple-Device-ID: 0xaaaaaaaaaaa
X-Apple-Session-ID: UUID
User-Agent: AirPlay/635.78.1

bplist00...

which gives us

{
  "type": "unhandledURLResponse",
  "params": {
    "FCUP_Response_URL": "mlhls://localhost/master.m3u8",
    "FCUP_Response_RequestID": 1,
    "FCUP_Response_StatusCode": 0,
    "FCUP_Response_Data": "..."
  }
}
#EXTM3U
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:URI="mlhls://localhost/itag/233/mediadata.m3u8",TYPE=AUDIO,GROUP-ID="233",DEFAULT=YES,AUTOSELECT=YES,NAME="Default"
#EXT-X-MEDIA:URI="mlhls://localhost/itag/234/mediadata.m3u8",TYPE=AUDIO,GROUP-ID="234",DEFAULT=YES,AUTOSELECT=YES,NAME="Default"
#EXT-X-MEDIA:URI="https://manifest.googlevideo.com/api/manifest/hls_timedtext_playlist/expire/1664767691/...",TYPE=SUBTITLES,GROUP-ID="vtt",DEFAULT=NO,AUTOSELECT=YES,NAME="English",LANGUAGE="en"
#EXT-X-STREAM-INF:BANDWIDTH=795148,CODECS="avc1.4D401E,mp4a.40.2",RESOLUTION=640x360,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/230/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=342387,CODECS="avc1.4D4015,mp4a.40.5",RESOLUTION=426x240,AUDIO="233",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/229/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=427037,CODECS="avc1.4D4015,mp4a.40.2",RESOLUTION=426x240,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/229/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1388497,CODECS="avc1.4D401F,mp4a.40.2",RESOLUTION=854x480,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/231/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3226612,CODECS="avc1.64001F,mp4a.40.2",RESOLUTION=1280x720,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/232/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=181304,CODECS="avc1.4D400C,mp4a.40.5",RESOLUTION=256x144,AUDIO="233",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/269/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5095563,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/270/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=97080,CODECS="vp09.00.10.08,mp4a.40.5",RESOLUTION=256x144,AUDIO="233",SUBTITLES="vtt",FRAME-RATE=15,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/602/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=159546,CODECS="vp09.00.11.08,mp4a.40.5",RESOLUTION=256x144,AUDIO="233",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/603/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=272688,CODECS="vp09.00.20.08,mp4a.40.5",RESOLUTION=426x240,AUDIO="233",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/604/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=357338,CODECS="vp09.00.20.08,mp4a.40.2",RESOLUTION=426x240,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/604/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=548378,CODECS="vp09.00.21.08,mp4a.40.2",RESOLUTION=640x360,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/605/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=895385,CODECS="vp09.00.30.08,mp4a.40.2",RESOLUTION=854x480,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/606/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1665683,CODECS="vp09.00.31.08,mp4a.40.2",RESOLUTION=1280x720,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/609/mediadata.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2670019,CODECS="vp09.00.40.08,mp4a.40.2",RESOLUTION=1920x1080,AUDIO="234",SUBTITLES="vtt",FRAME-RATE=30,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE
mlhls://localhost/itag/614/mediadata.m3u8

It seems that the mlhls://localhost is some sort of "proxy" (?) that runs on the , which will point back to the google url.

thiccaxe commented 2 years ago
{
  "type": "unhandledURLResponse",
  "params": {
    "FCUP_Response_URL": "mlhls://localhost/itag/614/mediadata.m3u8",
    "FCUP_Response_RequestID": 2,
    "FCUP_Response_StatusCode": 0,
    "FCUP_Response_Data": "..."
  }
}
#EXTM3U
#YT-EXT-CONDENSED-URL:BASE-URI="https://rr3---sn-o097znz7.googlevideo.com/videoplayback/id/.../playlist/index.m3u8",PARAMS="govp,gosq",PREFIX="s/"
#EXT-X-VERSION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:6
#EXT-X-MAP:URI="https://rr3---sn-o097znz7.googlevideo.com/videoplayback/id/.../playlist/index.m3u8/govp/slices%3D0-97413/gosq/0?is=1"
#EXTINF:5.067,
s/slices%3D0-97413/0
#EXTINF:5.066,
s/slices%3D0-219,97414-1049792/1
#EXTINF:5.067,
s/slices%3D0-219,1049793-2052531/2
#EXTINF:5.067,
s/slices%3D0-219,2052532-3341820/3
#EXTINF:5.066,
s/slices%3D0-219,3341821-4763143/4
#EXTINF:5.067,
s/slices%3D0-219,4763144-6175833/5
#EXTINF:5.067,
s/slices%3D0-219,6175834-7571442/6
#EXTINF:5.066,
s/slices%3D0-219,7571443-8602069/7
#EXTINF:5.067,
s/slices%3D0-219,8602070-9656565/8
#EXTINF:5.067,
s/slices%3D0-219,9656566-10673499/9
#EXTINF:5.066,
s/slices%3D0-219,10673500-11739681/10
#EXTINF:5.067,
s/slices%3D0-219,11739682-12904070/11
#EXTINF:5.067,
s/slices%3D0-219,12904071-14109440/12
#EXTINF:5.066,
s/slices%3D0-219,14109441-15111779/13
#EXTINF:5.067,
s/slices%3D0-219,15111780-16222516/14
#EXTINF:5.067,
s/slices%3D0-219,16222517-17229491/15
#EXTINF:5.066,
s/slices%3D0-219,17229492-18757052/16
#EXTINF:5.067,
s/slices%3D0-219,18757053-20096645/17
#EXTINF:5.067,
s/slices%3D0-219,20096646-20772737/18
#EXTINF:5.066,
s/slices%3D0-219,20772738-21901761/19
#EXTINF:5.067,
s/slices%3D0-219,21901762-23223312/20
#EXTINF:5.067,
s/slices%3D0-219,23223313-24446640/21
#EXTINF:5.066,
s/slices%3D0-219,24446641-25785407/22
#EXTINF:5.067,
s/slices%3D0-219,25785408-27197934/23
#EXTINF:5.067,
s/slices%3D0-219,27197935-28546183/24
#EXTINF:5.066,
s/slices%3D0-219,28546184-29977599/25
#EXTINF:5.067,
s/slices%3D0-219,29977600-31161214/26
#EXTINF:5.067,
s/slices%3D0-219,31161215-32515779/27
#EXTINF:5.066,
s/slices%3D0-219,32515780-33640841/28
#EXTINF:5.067,
s/slices%3D0-219,33640842-34787025/29
#EXTINF:5.067,
s/slices%3D0-219,34787026-35520617/30
#EXTINF:5.066,
s/slices%3D0-219,35520618-35869650/31
#EXTINF:5.067,
s/slices%3D0-219,35869651-36227435/32
#EXTINF:2.8,
s/slices%3D0-219,36227436-36568548/33
#EXT-X-ENDLIST

Maybe https://developer.apple.com/documentation/http_live_streaming/ has more info

fduncanh commented 2 years ago

M3U8 What Is an M3U8 File? A file with the M3U8 file extension is a UTF-8 Encoded Audio Playlist file. They are plain text files that can be used by both audio and video players to describe where media files are located. For example, one M3U8 file may give you references to online files for an internet radio station.

thiccaxe commented 2 years ago

M3U8 files are well-known and are implemented in the browser. But apple / YouTube is doing some hacking around to get these to work as they crash VLC and are not played in Firefox at all

fduncanh commented 2 years ago

do you think DMAP and/or HTTP servers are in the AirplayServer-1 code? (if so , where?; if not, external DMAP and webserver code is needed as well as renderers)

fduncanh commented 2 years ago

this may be a resource https://pyatv.dev/documentation/supported_features/#supported-features-1 https://pyatv.dev/internals/

thiccaxe commented 2 years ago

There is certainly an http server. But I doubt anyone has implemented DMAP. the protocol is not that bad, some research will need to be done on a real apple tv how it works.

thiccaxe commented 2 years ago

Seems that /playback-info should return a duration key.

shuax commented 10 months ago

https://github.com/air-display/apsdk-public

This seems to be a new version of AirplayServer-1 and may be useful.

thiccaxe commented 10 months ago

Seems like this is the "original"? Has a license thank god. Looks like it can be used in open source. Will need to check history a bit.

thiccaxe commented 10 months ago

Looks like they might have airplay 2 video streaming

fduncanh commented 10 months ago

will check it out

fduncanh commented 10 months ago

there is some documentation here: https://air-display.github.io/airplay-internal/overall_architecture.html

The current air-display code is missing the fairplay/playfair implementation which is "kept private"

fduncanh commented 10 months ago

To see if apsdk-public works,

it would presumably not be too difficult to clone it and add playfair to provide the missing fairplay stuff.

thiccaxe commented 10 months ago

I suspect that the author some how ripped the playfair code recently from an apple tv(and for this reason might behave differently that current os fairplay implementatoins), otherwise there would be no real reason to exclude the code. I will give it a shot though.

fduncanh commented 10 months ago

I'll close this again, for the moment.

thiccaxe commented 10 months ago

when using AirplayServer-1 and uxplay, screen mirroring, fairplay uses mode 1. the apsdk public uses mode 2. Is there any importance to mode 2? Because the code from Uxplay to handle fairplay is not working.

thiccaxe commented 10 months ago

Changing this line gets it back to "mode 1". s_instance->features_ = 0x6527FFFFF; // 0x0E5A7FFFF7 with pv // 0x0E527FFFF7 w/o pv;

fduncanh commented 10 months ago

The initial apsdk code appears on GitHub as an initial commit 2018-10-21.

In the plist GET/INFO response, it then called itself

        name_ = "WeCast Display";
        deviceID_ = "C02WF0AXJ1GQ";
        model_ = "AppleTV3,2";
        sourceVersion_ = "220.68";
        pi_ = "b08f5a79-db29-4384-b456-a4784d9e6055";
        pk_ = "99FD4299889422515FBD27949E4E1E21B2AF50A454499E3D4BE75A4E0F55FE63";
        macAddress_ = deviceID_;
        vv_ = 2;
        features_ = 0x5A7FDFD1;
        statusFlag_ = 68;

There are no copyright or License statements in that original code (except in parts with label "third party"), or anything else to indicate its provenance.

The initial commit predates dsafa22's analysis of another Android code, and doesn't seem to have any relation to that code. (based on code snippets quoted by dsafa22.

I found a link to a manual for a Wecast display device that supports both iOS and Android. https://usermanual.wiki/EC-Technology/C88040919-4304722.pdf Don't know if its related. The device is called "Wecast" not "WeCast".

EDIT: OK: "The WeCast android app is a free companion for the Wecast Media content server API."

https://play.google.com/store/apps/details?id=app.wecast.media

thiccaxe commented 10 months ago

regardless of that situation, the reality is that we can still use improvements such as https://github.com/air-display/apsdk-public/commit/f13fe9b9c2437919bc5a7d9b6d94271f6a706c36 since those are not from the original code and are now under GPL

thiccaxe commented 10 months ago

https://github.com/air-display/apsdk-public/commit/44b87a78394f1e8c396a6ad3cf86f1104f661ef3

fduncanh commented 10 months ago

@thiccaxe Have you worked out how to build the demo?

The top level CMakeLists.txt seems to have an option but I didnt find out how to do it.

I suceeded in build a static libaps.a

thiccaxe commented 10 months ago

Yes just change OFF to ON

thiccaxe commented 10 months ago

option(BUILD_APS_DEMO "Build the demo project" ON)

fduncanh commented 10 months ago

I did that already

cd apsdk-public
edit  CMakeLists.txt:  OFF ->ON
./generate_linux_proj.sh
cd .build/linux
make

This builds target aps (libaps.a, static) What next?

cd ../../demo
cmake .
make
[ 50%] Building CXX object CMakeFiles/apsdk-demo.dir/apsdk-demo.cpp.o
apsdk-public/demo/apsdk-demo.cpp:10:10: fatal error: ap_config.h: No such file or directory
 #include <ap_config.h>
          ^~~~~~~~~~~~~
compilation terminated.
make[2]: *** [CMakeFiles/apsdk-demo.dir/build.make:76: CMakeFiles/apsdk-demo.dir/apsdk-demo.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/apsdk-demo.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
thiccaxe commented 10 months ago

No run make for the root

fduncanh commented 10 months ago

@thiccaxe Got it! thanks

Stops at FPLY, hope you can get playfair working with it!

thiccaxe commented 10 months ago

Yeah, like I mentioned earlier, simply copypasting uxplay code didn't do the trick. For the exact same request, UxPlay and this one respond the exact same but only UxPlay works with an iOS device. Maybe it's some weird header.

fduncanh commented 10 months ago

The value of "mode" seems to change between calls to fp_setup values are 0, 1, 2, 3

thiccaxe commented 10 months ago

I saw that screen mirroring was always 2 and audio only was 3, consistently. Maybe I will need to double check

fduncanh commented 10 months ago

mode = data[15] (next to last byte of fairplay setup data)

data[0:3] is F, P, L, Y apsdk calls this header->signature data[4] must be 0x03 apsdk call this header-> major_version data[5] apsdk calls this header->minor_version data[6] apsdk calls this header->phase : it is 0x1 in fpsetup1, 0x3 in fpsetup2

in fpsetup1 Uxplay chooses one of 4 possible 142 byte return messages dependent on the value 0-3 of "mode"

char reply_message[4][142] = {{0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x00,0x0f,0x9f,0x3f,0x9e,0x0a,0x25,0x21,0xdb,0xdf,0x31,0x2a,0xb2,0xbf,0xb2,0x9e,0x8d,0x23,0x2b,0x63,0x76,0xa8,0xc8,0x18,0x70,0x1d,0x22,0xae,0x93,0xd8,0x27,0x37,0xfe,0xaf,0x9d,0xb4,0xfd,0xf4,0x1c,0x2d,0xba,0x9d,0x1f,0x49,0xca,0xaa,0xbf,0x65,0x91,0xac,0x1f,0x7b,0xc6,0xf7,0xe0,0x66,0x3d,0x21,0xaf,0xe0,0x15,0x65,0x95,0x3e,0xab,0x81,0xf4,0x18,0xce,0xed,0x09,0x5a,0xdb,0x7c,0x3d,0x0e,0x25,0x49,0x09,0xa7,0x98,0x31,0xd4,0x9c,0x39,0x82,0x97,0x34,0x34,0xfa,0xcb,0x42,0xc6,0x3a,0x1c,0xd9,0x11,0xa6,0xfe,0x94,0x1a,0x8a,0x6d,0x4a,0x74,0x3b,0x46,0xc3,0xa7,0x64,0x9e,0x44,0xc7,0x89,0x55,0xe4,0x9d,0x81,0x55,0x00,0x95,0x49,0xc4,0xe2,0xf7,0xa3,0xf6,0xd5,0xba},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x01,0xcf,0x32,0xa2,0x57,0x14,0xb2,0x52,0x4f,0x8a,0xa0,0xad,0x7a,0xf1,0x64,0xe3,0x7b,0xcf,0x44,0x24,0xe2,0x00,0x04,0x7e,0xfc,0x0a,0xd6,0x7a,0xfc,0xd9,0x5d,0xed,0x1c,0x27,0x30,0xbb,0x59,0x1b,0x96,0x2e,0xd6,0x3a,0x9c,0x4d,0xed,0x88,0xba,0x8f,0xc7,0x8d,0xe6,0x4d,0x91,0xcc,0xfd,0x5c,0x7b,0x56,0xda,0x88,0xe3,0x1f,0x5c,0xce,0xaf,0xc7,0x43,0x19,0x95,0xa0,0x16,0x65,0xa5,0x4e,0x19,0x39,0xd2,0x5b,0x94,0xdb,0x64,0xb9,0xe4,0x5d,0x8d,0x06,0x3e,0x1e,0x6a,0xf0,0x7e,0x96,0x56,0x16,0x2b,0x0e,0xfa,0x40,0x42,0x75,0xea,0x5a,0x44,0xd9,0x59,0x1c,0x72,0x56,0xb9,0xfb,0xe6,0x51,0x38,0x98,0xb8,0x02,0x27,0x72,0x19,0x88,0x57,0x16,0x50,0x94,0x2a,0xd9,0x46,0x68,0x8a},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x02,0xc1,0x69,0xa3,0x52,0xee,0xed,0x35,0xb1,0x8c,0xdd,0x9c,0x58,0xd6,0x4f,0x16,0xc1,0x51,0x9a,0x89,0xeb,0x53,0x17,0xbd,0x0d,0x43,0x36,0xcd,0x68,0xf6,0x38,0xff,0x9d,0x01,0x6a,0x5b,0x52,0xb7,0xfa,0x92,0x16,0xb2,0xb6,0x54,0x82,0xc7,0x84,0x44,0x11,0x81,0x21,0xa2,0xc7,0xfe,0xd8,0x3d,0xb7,0x11,0x9e,0x91,0x82,0xaa,0xd7,0xd1,0x8c,0x70,0x63,0xe2,0xa4,0x57,0x55,0x59,0x10,0xaf,0x9e,0x0e,0xfc,0x76,0x34,0x7d,0x16,0x40,0x43,0x80,0x7f,0x58,0x1e,0xe4,0xfb,0xe4,0x2c,0xa9,0xde,0xdc,0x1b,0x5e,0xb2,0xa3,0xaa,0x3d,0x2e,0xcd,0x59,0xe7,0xee,0xe7,0x0b,0x36,0x29,0xf2,0x2a,0xfd,0x16,0x1d,0x87,0x73,0x53,0xdd,0xb9,0x9a,0xdc,0x8e,0x07,0x00,0x6e,0x56,0xf8,0x50,0xce},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x03,0x90,0x01,0xe1,0x72,0x7e,0x0f,0x57,0xf9,0xf5,0x88,0x0d,0xb1,0x04,0xa6,0x25,0x7a,0x23,0xf5,0xcf,0xff,0x1a,0xbb,0xe1,0xe9,0x30,0x45,0x25,0x1a,0xfb,0x97,0xeb,0x9f,0xc0,0x01,0x1e,0xbe,0x0f,0x3a,0x81,0xdf,0x5b,0x69,0x1d,0x76,0xac,0xb2,0xf7,0xa5,0xc7,0x08,0xe3,0xd3,0x28,0xf5,0x6b,0xb3,0x9d,0xbd,0xe5,0xf2,0x9c,0x8a,0x17,0xf4,0x81,0x48,0x7e,0x3a,0xe8,0x63,0xc6,0x78,0x32,0x54,0x22,0xe6,0xf7,0x8e,0x16,0x6d,0x18,0xaa,0x7f,0xd6,0x36,0x25,0x8b,0xce,0x28,0x72,0x6f,0x66,0x1f,0x73,0x88,0x93,0xce,0x44,0x31,0x1e,0x4b,0xe6,0xc0,0x53,0x51,0x93,0xe5,0xef,0x72,0xe8,0x68,0x62,0x33,0x72,0x9c,0x22,0x7d,0x82,0x0c,0x99,0x94,0x45,0xd8,0x92,0x46,0xc8,0xc3,0x59}};
fduncanh commented 10 months ago

This should be a substitute for "fairplay_setup" in third_party/fairplay/fairplay.c in (not yet tested, though) seems to work for fpsetup-1


void fairplay_setup(const uint8_t mode, uint8_t *out) {

uint8_t reply_message[4][142] = {{0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x00,0x0f,0x9f,0x3f,0x9e,0x0a,0x25,0x21,0xdb,0xdf,0x31,0x2a,0xb2,0xbf,0xb2,0x9e,0x8d,0x23,0x2b,0x63,0x76,0xa8,0xc8,0x18,0x70,0x1d,0x22,0xae,0x93,0xd8,0x27,0x37,0xfe,0xaf,0x9d,0xb4,0xfd,0xf4,0x1c,0x2d,0xba,0x9d,0x1f,0x49,0xca,0xaa,0xbf,0x65,0x91,0xac,0x1f,0x7b,0xc6,0xf7,0xe0,0x66,0x3d,0x21,0xaf,0xe0,0x15,0x65,0x95,0x3e,0xab,0x81,0xf4,0x18,0xce,0xed,0x09,0x5a,0xdb,0x7c,0x3d,0x0e,0x25,0x49,0x09,0xa7,0x98,0x31,0xd4,0x9c,0x39,0x82,0x97,0x34,0x34,0xfa,0xcb,0x42,0xc6,0x3a,0x1c,0xd9,0x11,0xa6,0xfe,0x94,0x1a,0x8a,0x6d,0x4a,0x74,0x3b,0x46,0xc3,0xa7,0x64,0x9e,0x44,0xc7,0x89,0x55,0xe4,0x9d,0x81,0x55,0x00,0x95,0x49,0xc4,0xe2,0xf7,0xa3,0xf6,0xd5,0xba},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x01,0xcf,0x32,0xa2,0x57,0x14,0xb2,0x52,0x4f,0x8a,0xa0,0xad,0x7a,0xf1,0x64,0xe3,0x7b,0xcf,0x44,0x24,0xe2,0x00,0x04,0x7e,0xfc,0x0a,0xd6,0x7a,0xfc,0xd9,0x5d,0xed,0x1c,0x27,0x30,0xbb,0x59,0x1b,0x96,0x2e,0xd6,0x3a,0x9c,0x4d,0xed,0x88,0xba,0x8f,0xc7,0x8d,0xe6,0x4d,0x91,0xcc,0xfd,0x5c,0x7b,0x56,0xda,0x88,0xe3,0x1f,0x5c,0xce,0xaf,0xc7,0x43,0x19,0x95,0xa0,0x16,0x65,0xa5,0x4e,0x19,0x39,0xd2,0x5b,0x94,0xdb,0x64,0xb9,0xe4,0x5d,0x8d,0x06,0x3e,0x1e,0x6a,0xf0,0x7e,0x96,0x56,0x16,0x2b,0x0e,0xfa,0x40,0x42,0x75,0xea,0x5a,0x44,0xd9,0x59,0x1c,0x72,0x56,0xb9,0xfb,0xe6,0x51,0x38,0x98,0xb8,0x02,0x27,0x72,0x19,0x88,0x57,0x16,0x50,0x94,0x2a,0xd9,0x46,0x68,0x8a},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x02,0xc1,0x69,0xa3,0x52,0xee,0xed,0x35,0xb1,0x8c,0xdd,0x9c,0x58,0xd6,0x4f,0x16,0xc1,0x51,0x9a,0x89,0xeb,0x53,0x17,0xbd,0x0d,0x43,0x36,0xcd,0x68,0xf6,0x38,0xff,0x9d,0x01,0x6a,0x5b,0x52,0xb7,0xfa,0x92,0x16,0xb2,0xb6,0x54,0x82,0xc7,0x84,0x44,0x11,0x81,0x21,0xa2,0xc7,0xfe,0xd8,0x3d,0xb7,0x11,0x9e,0x91,0x82,0xaa,0xd7,0xd1,0x8c,0x70,0x63,0xe2,0xa4,0x57,0x55,0x59,0x10,0xaf,0x9e,0x0e,0xfc,0x76,0x34,0x7d,0x16,0x40,0x43,0x80,0x7f,0x58,0x1e,0xe4,0xfb,0xe4,0x2c,0xa9,0xde,0xdc,0x1b,0x5e,0xb2,0xa3,0xaa,0x3d,0x2e,0xcd,0x59,0xe7,0xee,0xe7,0x0b,0x36,0x29,0xf2,0x2a,0xfd,0x16,0x1d,0x87,0x73,0x53,0xdd,0xb9,0x9a,0xdc,0x8e,0x07,0x00,0x6e,0x56,0xf8,0x50,0xce},
                              {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x03,0x90,0x01,0xe1,0x72,0x7e,0x0f,0x57,0xf9,0xf5,0x88,0x0d,0xb1,0x04,0xa6,0x25,0x7a,0x23,0xf5,0xcf,0xff,0x1a,0xbb,0xe1,0xe9,0x30,0x45,0x25,0x1a,0xfb,0x97,0xeb,0x9f,0xc0,0x01,0x1e,0xbe,0x0f,0x3a,0x81,0xdf,0x5b,0x69,0x1d,0x76,0xac,0xb2,0xf7,0xa5,0xc7,0x08,0xe3,0xd3,0x28,0xf5,0x6b,0xb3,0x9d,0xbd,0xe5,0xf2,0x9c,0x8a,0x17,0xf4,0x81,0x48,0x7e,0x3a,0xe8,0x63,0xc6,0x78,0x32,0x54,0x22,0xe6,0xf7,0x8e,0x16,0x6d,0x18,0xaa,0x7f,0xd6,0x36,0x25,0x8b,0xce,0x28,0x72,0x6f,0x66,0x1f,0x73,0x88,0x93,0xce,0x44,0x31,0x1e,0x4b,0xe6,0xc0,0x53,0x51,0x93,0xe5,0xef,0x72,0xe8,0x68,0x62,0x33,0x72,0x9c,0x22,0x7d,0x82,0x0c,0x99,0x94,0x45,0xd8,0x92,0x46,0xc8,0xc3,0x59}};

memcpy(out, reply_message[mode], 142);
}
fduncanh commented 10 months ago

I forked this to http://github.com/FDH2/apsdk-public

working in branch "test"

thiccaxe commented 10 months ago

The first fp setup call is fine, the second fails.

fduncanh commented 10 months ago

The second one is now working, Just need to add fp_decrypt and playfair code.

fduncanh commented 10 months ago

The branch "test" will be removed. A new branch "playfair" now has the playfair implementation of fairplay integrated into apsdk-public Seems to be working. I left the third-party/playfair/fairplay.c.in unmodified and in place and added a fairplay_playfair.c.in, to replace it, and modified third-party/playfair/CMakeLists.txt to use it instead when make is run.

https://github.com/FDH2/apsdk-public/tree/playfair

Next, to explore apsdk-public.

@thiccaxe: It's all yours! From the structure I saw, I'm guessing the "private" fairplay implementation is just playfair.

Youtube app videos stream with "FCUP" stuff showing, but of course the front end is missing. Maybe the gstreamer part of UxPlay can be strapped on to it.

thiccaxe commented 10 months ago

@fduncanh your code is not working for me! what is your ios version?

thiccaxe commented 10 months ago

Does not work ipados 16.6 or ios 17.3

thiccaxe commented 10 months ago

Hm, just a weird bug. Fixed in PR.

fduncanh commented 10 months ago

Don't see the PR (The branch is now playfair not test)