Eee note EA800 と Eee Note Sync の対話をキャプチャしてみた (2)
日本アジアカップ優勝おめでとう!
EA800 の解析事情
ここで外からちまちま TCP でつっついている間に、海外では EA800 を開腹して中の Micro SD を取り出すなどして解析が進められているらしい。
EA800のfirmwareが解析中 その1 | OSAKANA TAROのメモ帳
そのうち独自ファームウェアまで作られそうな勢いだ。熱い。
ファイルのダウンロード
あいにく手元に工具や SD カードリーダーが無いので、とりあえず外からつっつくのを続けてみたいと思う。色々試してみた結果、EA800 内のファイルをダウンロードする方法は概ね理解できた。大まかな流れとしては、xmlfile コマンドを使って EA800 内にあるファイルのリストを取得し、リストに載っている各ファイルを downloadfile コマンドでダウンロード、とすれば良さそうだ。
python スクリプト
2010-02-05 追記: フォルダIDに対応するパスが判明したのでソース先頭のコメント部に追記した
#!/usr/bin/env python #-*- coding: utf-8 -*- # # ea800_downloader.py # # Usage: python ea800_downloader.py FOLDER_ID OUT_DIR # # FOLDER_ID: ダウンロードしたいフォルダの ID (下記参照) # # | ID | folder path | # |----+------------------------------------| # | 0 | /eTablet/var/ebook | # | 2 | /eTablet/var/photos | # | 4 | /eTablet/var/enotes | # | 8 | ? | # | 12 | /eTablet/etc/db | # | 14 | /eTablet/etc/template | # | 31 | /eTablet | # | 32 | /usr/local/eTablet/bin | # | 33 | /eTablet/etc | # | 34 | /eTablet/var | # | 35 | /usr/local/eTablet/lib | # | 37 | /usr/local/eTablet/bin/ebookreader | # # OUT_DIR: ダウンロードしたフォルダの格納先ディレクトリ # OUT_DIR ディレクトリの直下に files と xmlfiles の 2 つのディレクトリを作り、 # files ディレクトリにはダウンロードしたファイルを格納し、 # xmlfiles ディレクトリにはファイルのリスト(XML形式)を格納する。 import sys import os import socket import xml.etree.ElementTree as etree import hashlib BUF_SIZE = 8192 class Connection(object): """ Simple wrapper class for Raw TCP connection. Refer to: http://docs.python.org/howto/sockets.html Attributes: - _sock """ def __init__(self, address): self._sock = socket.socket() self._sock.connect(address) def send(self, op): totalsent = 0 op = op.encode("utf-8") op_size = len(op) while totalsent < op_size: sent = self._sock.send(op) if sent == 0: raise RuntimeError, \ "socket connection broken" totalsent = totalsent + sent def recv(self, size=1): read = 0 buf = [] while read < size: data = self._sock.recv(BUF_SIZE) if data == "": raise RuntimeError, \ "socket connection broken" read = read + len(data) buf.append(data) return "".join(buf) def close(self): self._sock.close() class Downloader(object): """ File downloader for Eee Note EA800. Attributes: - _cs - _ds - _files_folder - _xmlfiles_folder """ def __init__(self, out_dir): # connect to command socket self._cs = Connection(("169.254.2.1", 20000)) # recieve welcome message self._cs.recv() # connect to data socket self._ds = Connection(("169.254.2.1", 20001)) # recieve welcome message self._cs.recv() self._files_folder = os.path.join(out_dir, "files") self._xmlfiles_folder = os.path.join(out_dir, "xmlfiles") def close(self): # close both connections self._ds.close() self._cs.close() def _md5sum(self, data): m = hashlib.md5() m.update(data) return m.hexdigest() def downloadfile(self, folder_id, path): """execute 'downloadfile' command""" # send "downloadfile" request op = "downloadfile\n%d\nPath=%s\n\\n::\\n" % (folder_id, path) self._cs.send(op) # receive "downloadfilereturnvalue" response res = self._cs.recv() res_list = res.split(b"\n") assert res_list[0] == "downloadfilereturnvalue", "not correct response" # extract status and file_size from "downloadfilereturnvalue" response status = res_list[1] file_size = int(res_list[2]) md5 = res_list[3][4:] # drop first 4 character "md5=" assert file_size > 0, "file_size is not positive integer" # prepare file and directory to output data outfile_path = os.path.join(self._files_folder, "%02d" % folder_id, path.lstrip("/")) outfile_dir = os.path.dirname(outfile_path) try: os.makedirs(outfile_dir) except OSError: pass # output data to local directory data = self._ds.recv(file_size) assert self._md5sum(data) == md5, "MD5 check sum invalid" outfile = open(outfile_path, "wb") outfile.write(data) outfile.close() print "downloaded: %s" % path def listfile(self, folder_id): """execute 'listfile' command""" # send "listfile" request op = "listfile\n%d\n\\n::\\n" % folder_id self._cs.send(op) # receive "listfilereturnvalue" response res = self._cs.recv() res_list = res.split(b"\n") assert res_list[0] == "listfilereturnvalue" # extract status and file_size from "listfilereturnvalue" response status = res_list[1] file_size = res_list[2] if file_size.endswith("\\n::\\n"): file_size = file_size[:-6] file_size = int(file_size) assert file_size > 0 # output data to console data = self._ds.recv(file_size) print data def xmlfile(self, folder_id): """execute 'xmlfile' command""" # send "xmlfile" request op = "xmlfile\n%d\n\\n::\\n" % folder_id self._cs.send(op) # receive "xmlfilereturnvalue" response res = self._cs.recv() res_list = res.split(b"\n") assert res_list[0] == "xmlfilereturnvalue" # extract status and file_size from "xmlfilereturnvalue" response status = res_list[1] file_size = res_list[2] file_size = int(file_size) assert file_size > 0 # prepare file and directory to output data outfile_path = os.path.join(self._xmlfiles_folder, "%02d.xml" % folder_id) try: os.makedirs(self._xmlfiles_folder) except OSError: pass # output data to local directory outfile = open(outfile_path, "wb") data = self._ds.recv(file_size) outfile.write(data) outfile.close() # parse xmlfile tree = etree.parse(outfile_path) return tree def downloadfolder(self, folder_id): """recursive download files in a folder""" tree = self.xmlfile(folder_id) self._traversal_and_download(tree.getroot(), u"", folder_id) def _traversal_and_download(self, elm, folder_name, folder_id): if elm.tag == "file": path = folder_name + u"/" + elm.get("name") try: self.downloadfile(folder_id, path) except AssertionError, e: print e else: for child in elm.getchildren(): self._traversal_and_download(child, elm.get("name", u""), folder_id) if __name__ == "__main__": folder_id = int(sys.argv[1]) out_dir = sys.argv[2] d = Downloader(out_dir) d.downloadfolder(folder_id) d.close()
利用例
例えばコマンドプロンプトで
python ea800_downloader.py 4 C:\tmp\eeenote
とすれば、フォルダID = 4 のフォルダ (/eTablet/etc/enotes) 内のファイルが C:\tmp\eeenote\files\12 内にダウンロードされます。
ただし自分用に適当に作ったものですので、何が起こっても責任は取れません。ファイルを削除してしまうようなことは無いと思いますが、ご利用は自己責任でお願いします。