1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Discovering the iPad interface

Discussion in 'TiVo Underground' started by wmcbrine, Jan 18, 2011.

  1. May 1, 2011 #61 of 366
    ckrames1234

    ckrames1234 New Member

    4
    0
    May 1, 2011
    Yeah, it's not dead anymore :p
     
  2. May 1, 2011 #62 of 366
    innocentfreak

    innocentfreak Active Member

    8,950
    3
    Aug 25, 2001
    Florida
    Lol just figured I would give you crap over it no worries.
     
  3. May 1, 2011 #63 of 366
    unitron

    unitron Active Member

    16,389
    2
    Apr 28, 2006
    semi-coastal NC
    ckrames1234 also seems to be breathing a little new life into the "other forum" as well.
     
  4. Jun 5, 2011 #64 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    Wow. I should have checked back here earlier. Here's the post I was about to write elsewhere, and the details I've found (more in my next reply):
    ----------------------------------------------​
    There's two parts to my story, one happy and one sad.

    Happy: I spent a bit of time, a while back, trying and failing to decode the protocol that the iPad app uses to communicate with the TiVo. I tried again this weekend and succeeded! Details will follow below.

    Sad: because I had spend so long not using my iPad (having switched to a Xoom, and keeping the iPad around only because I wanted to get back to figuring this out, so I could write an Android equivalent) the TiVo app on it was still version 1.1, with an upgrade to 1.2 available. I did this work before upgrading, then clicked the 'upgrade all' button. Bad idea. Version 1.2 of the iPad app speaks something different, which I can't decode yet. (Edit: removed call for old apps -- previous problems seem to be user error on my part!) Who knows how long this version will continue to work, given that it changed between 1.1 and 1.2? Hopefully forever. Anyway:

    Protocol details, for iPad app version 1.1:
    • The iPad discovers the TiVo with mDNS.
    • The iPad then connects to port 1393.
    • It does so over SSL so the data is impossible to sniff.
    (With 1.2, it connects on port 1413, which still appears to be SSL, but somehow changed. Perhaps it's validating certificates?)
    Previous attempts to reverse engineer with only tcpdump have seemed to stop here. Bummer!

    But I managed to forge some mDNS replies so that the iPad app connected to my desktop instead (this still works with 1.2 -- see https://gist.github.com/1009602#file_mdns_tivo_forgery.py) and used TcpCatcher as a man-in-the middle to decrypt the SSL (this part has failed for me so far with version 1.2).

    The result is this proof-of-concept script: https://gist.github.com/1009602#file_remote.py
    Open up TiVo search with something playing in the corner and run this script. It will pause the video, type "typed via remote" into the search box, and unpause the video. (Or, at least it does for me.)

    Here's both these scripts, plus all the RPCs I managed to dump (with my MAK and TSN stripped -- hope I didn't leave anything else that's too sensitive). As mentioned above, I can't dump any more because I no longer have version 1.1 of the iPad app, and would love it if anyone could help me get it back in case I missed anything.
    http://www.mediafire.com/?z40dci9756a7vh7
     
  5. Jun 5, 2011 #65 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    Is MRPC or MindRPC something you've ever heard of before, or just something you figured out based on context?

    Could you perhaps provide the dumps-the-requests binary (my iPad is jailbroken)? This would be likely painful, but enough to continue if it does turn out that I missed some important request types.
     
  6. Jun 6, 2011 #66 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    Nevermind! TcpCatcher was just being a pain. It is still possible to SSL man-in-the-middle version 1.2 of the app, as:

    Edit: see the .zip posted later.
     
  7. Jun 6, 2011 #67 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    Good stuff arantius, thanks for posting!
    Have you captured something more interesting, such as deleting a show from the Premiere via the iPad? Sending characters/numbers can already be handled with telnet based remote so to me is not as interesting to discover, though of course makes for a good repeatable prototype.
     
  8. Jun 6, 2011 #68 of 366
    orangeboy

    orangeboy yes, I AM orangeboy!

    4,083
    0
    Apr 19, 2004
    East Moline, IL
    There's some interesting captures in the tcp dump:
    {"type":"tunerConflictEventRegister"}
     
  9. Jun 6, 2011 #69 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    Yes, download the linked zip. It's chock full of all sorts of kinds of requests and responses. I'm hoping to sort through it all and try to get it documented, so I don't have to keep referring to the raw data. That might be a really big task.

    Also as mentioned, I realized I can monitor app 1.2's traffic as well, which appears to be similar but critically different in just enough ways to be a pain.
     
  10. Jun 7, 2011 #70 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
  11. Jun 11, 2011 #71 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    arantius, I finally had a little time this morning to play around with this a little and want to re-iterate thanks for your work on this. I was able to get your prototype working and tried some additional things. For others monitoring this thread, an example of a cool application of this remote:

    Jump to a specific location in currently playing program, example:
    RpcRequest('videoPlaybackPositionSet', offset=400000)

    The above request always jumps to specific offset from start of program given, so you can immediately jump to specific point in a program for advanced navigation controls.

    Haven't yet figured out how to delete a specific program directly, but something worth exploring further along with some other things.

    One little obstacle I ran into was I didn't have the 'simplejson' python module, but a quick Google search identified where I could download that from, and I just installed in same dir as the remote.py module.

    Thanks again!
     
  12. Jun 11, 2011 #72 of 366
    wmcbrine

    wmcbrine Ziphead

    10,368
    22
    Aug 2, 2003
    "simplejson" I think is the same as "json" in the standard library as of Python 2.6.
     
  13. Jun 11, 2011 #73 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    Note that the 'offset' argument of 'videoPlaybackPositionSet' is the time you want to jump to in milliseconds. i.e. To jump to 5 minute mark it would be 60*1000*5. i.e. It's easy to jump exactly to any point in a program that you want.

    One can use 'videoPlaybackInfoEventRegister' to get current position information. So based on the above 2 it's simple to implement a simple means of jumping forwards/backwards n minutes with a slight modification to the Key method in remote.py. Below is a sample implementation (with no error checking at all, but just for proof of concept):
    Code:
      def Key(self, key, offset=0):
        """Send a key.
    
        Supported:
          Letters 'a' through 'z'.
          Numbers '0' through '1'.
          Named keys:
            actionA, actionB, actionC, actionD, advance, channelDown,
            channelUp, clear, down, enter, forward, guide, info, left, liveTv,
            pause, play, record, replay, reverse, right, select, slow,
            thumbsDown, thumbsUp, tivo, up, zoom
        """
        if key == ' ':
          key = 'forward'
        elif key == 'jump':
          result = self.Key('position')
          pos = int(result['response']['position'])
          pos += 60000*offset
          req = RpcRequest('videoPlaybackPositionSet', offset=pos)
        elif key == 'position':
          req = RpcRequest('videoPlaybackInfoEventRegister', throttleDelay=1000)
        elif key.lower() in 'abcdefghijklmnopqrstuvwxyz':
          req = RpcRequest('keyEventSend', event='ascii', value=ord(key))
        elif key in '0123456789':
          req = RpcRequest('keyEventSend', event='num' + key)
        else:
          req = RpcRequest('keyEventSend', event=key)
    
        self.Write(req)
        result = self.Read()
        return result
    
    
    Then as examples the following work:

    # Jump 5 minutes ahead
    remote.Key('jump', offset=5)

    # Jump 7 minutes backwards
    remote.Key('jump', offset=-7)

    i.e. There seems to be enough uncovered already to mimic telnet based remotes as well as adding additional functionality such as above.
     
  14. Jun 11, 2011 #74 of 366
    orangeboy

    orangeboy yes, I AM orangeboy!

    4,083
    0
    Apr 19, 2004
    East Moline, IL
    Well, after actually reading the instructions above, yep, "typed via remote" shows up for me, too!

    Indeed it is (2.6.6). And in 2.7.0 and 2.7.1.

    I haven't looked into all the arguments, but did you notice if there's a 'videoPlaybackEnd' (or something similar) that represents total duration? I'm thinking of a horizontal scroll bar, representing 0 to <end> that mimics the iPad scrub. No clue if the two could be tied together somehow.
     
  15. Jun 11, 2011 #75 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    OK, another good one. Once you get hold of a recording id you can issue a "play" request as follows:

    Code:
    req = RpcRequest('uiNavigate', uri='x-tivo:classicui:playback', parameters={'fUseTrioId': 'true', 'recordingId': 'tivo:rc.247339', 'fHideBannerOnEnter': 'true'})
    Note that you can choose to display the banner or not as part of request. I really like NOT displaying the banner which is what I used above.

    Getting list of recording Id's seems trickier than it should be. By trial and error I couldn't get list of completed recordings, but I did manage to get in progress recordings using:
    Code:
    req = RpcRequest('recordingSearch', levelOfDetail='medium', bodyId='-', state=['inProgress'])
    For the above when I use 'complete' instead of 'inProgress' I get an error response.
    (search must have one of the following filters: 'offerId', 'collectionId', 'recordingId', 'state')

    To do list searching:
    Code:
    req = RpcRequest('recordingSearch', levelOfDetail='medium', bodyId='-', state=['scheduled'])
    
     
  16. Jun 12, 2011 #76 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    No it's different. It's older, and in my experience (quite surprisingly) much faster. Primarily, I'm used to it. So I stick with it. You can almost definitely substitute the standard library's "json" to no ill effect, assuming your Python is new enough to have that.
     
  17. Jun 12, 2011 #77 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
  18. Jun 12, 2011 #78 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    It's pretty clear that RPC is just part of the story though. I think the iPad is also communicating with tivo.com servers to get a lot of information and also perhaps using the traditional HMO to communicate with the TiVos as well.

    Typically RPC search queries directly to the TiVo only return 1 result and are highly targeted/filtered. For example, you can't get list of all recordings on the TiVo, but you can get additional details on particular shows. When you search for "scheduled" recordings without a filter you only get the first in To Do list as a response. i.e. When using this protocol you should already be armed with specific information about existing and/or scheduled recordings which are obtained with alternate means.

    Note also for example that a show 'delete' does not seem to happen through this protocol. The only relevant RPC related to delete I found was:
    111637-1229-1-recordingUpdate-recordingUpdate.txt
    i.e. Just information that a specific show was deleted. The actual delete itself was performed with a different method, probably using different port and protocol.
     
  19. Jun 12, 2011 #79 of 366
    arantius

    arantius New Member

    60
    0
    Jul 25, 2009
    I'm not certain of that yet. I think it's just complicated, nothing is completely done in one RPC.

    Plenty of RPCs response is an "idSequence". I think you just need to take those IDs and fire off another call to get their details. Anyway, this is one of the areas I'd expect to concentrate; getting the list of stuff on the TiVo and interacting with it (play, delete, etc.) is one of the primary nice features to add on top of the apps currently available for Android. (Which all seem to use IRCODE.)

    Clearly my docs are missing something, because I don't have any idSequence responses documented. But they're there in the traces. It's gonna take a lot of work to read through all the possible responses and see where I'm missing things. Requests seem to omit optional parameters all the time, making finding them extra tricky.

    That is the request to perform the delete. The response is 111637-1229-2-response-success.txt -- success, that recording ( tivo:rc.181929 ) was deleted.
     
  20. Jun 12, 2011 #80 of 366
    moyekj

    moyekj Well-Known Member

    11,148
    33
    Jan 23, 2006
    Mission...
    Ah, yes that should have been obvious - given that there is a response to go along with it. What threw me off was the state='deleted' which implied it already had happened. I was indeed able to delete a show using that method, and oddly enough it worked just using bodyId='-' (i.e. no need for tsn even though bodyId is a required argument). Now to focus next on how to obtain recording id's...
     

Share This Page