與iOS通信-libimobiledevice

1.什么是libimobiledevice?

A cross-platform software protocol library and tools to communicate with iOS? devices natively.

libimobiledevice is a cross-platform software library that talks the protocols to support iPhone?, iPod Touch?, iPad? and Apple TV? devices. Unlike other projects, it does not depend on using any existing proprietary libraries and does not require jailbreaking. It allows other software to easily access the device's filesystem, retrieve information about the device and it's internals, backup/restore the device, manage SpringBoard? icons, manage installed applications, retrieve addressbook/calendars/notes and bookmarks and (using libgpod) synchronize music and video to the device. The library is in development since August 2007 with the goal to bring support for these devices to the Linux Desktop.

官方網站:http://www.libimobiledevice.org
github地址:https://github.com/libimobiledevice

2.libimobiledevice目前的運用:

PP助手,itools, 愛思助手,xy助手等一大批蘋果助手類軟件,底層全部都是使用的此庫。

3.快速直接安裝libmobiledevice的方法

在MacOS下安裝可以使用brew,類似Ubuntu中的apt-get

brew update
brew install libimobiledevice
#libimobiledevice中并不包含ipa的安裝命令,所以還需要安裝
brew install ideviceinstaller

Ubuntu下安裝需要添加一個新的軟件庫,里面包含了libimobiledevice

sudo add-apt-repository ppa:pmcenery/ppa
sudo apt-get update
apt-get install libimobiledevice-utils
sudo apt-get install ideviceinstaller

常用功能

安裝ipa包,卸載應用

//命令安裝一個ipa文件到手機上,如果是企業簽名的,非越獄機器也可以直接安裝了。
ideviceinstaller -I xxx.ipa

//命令卸載應用,需要知道此應用的bundleID
ideviceinstaller -U [bundleID]

查看系統日志

idevicesyslog

查看當前已連接的設備的UUID

idevice_id --list

截圖

idevicescreenshot

查看設備信息

ideviceinfo

獲取設備時間

idevicedate

設置代理(可以用于轉發端口,比如將ssh的端口映射到電腦,這樣沒有網絡也可以ssh登錄)

iproxy

掛載DeveloperDiskImage,用于調試

ideviceimagemounter

獲取設備名稱

idevicename

調試程序(需要預先掛載DeveloperImage)

idevicedebug

查看和操作設備的描述文件

ideviceprovision list

掛載文件系統工具:ifuse
ifuse是一個依賴libimobiledevice庫的工具,所以必須首先安裝libimobiledevice

brew install osxfuse
brew install ifuse

掛載某應用的整個沙盒目錄

ifuse --container [要掛載的應用的bundleID] [掛載點]

如果是越獄的設備,并且配置好了,可以使用下面命令掛載整個iphone文件系統(暫時沒試過,還沒有開始研究越獄設備)

ifuse --root [掛載點]

4.手動編譯

github:https://github.com/libimobiledevice/libimobiledevice
安裝依賴軟件和庫

brew install openssl
brew install libplist
brew install usbmuxd//包含libusbmuxd
#brew install make //make不需要安裝,系統自帶
brew install autoconf//里面包含autoheader
brew install automake//包含automake
brew install libtool
brew install pkg-config
#brew install gcc//系統自帶

生成Makefile,必須指定openssl的路徑,由于openssl的漏洞,mac不在支持openssl,不支持將openssl的庫加入庫路徑,所以必須指定路徑

./autogen.sh openssl_CFLAGS=/usr/local/opt/openssl/include openssl_LIBS=/usr/local/opt/openssl/lib

編譯

make

安裝

sudo make install

5.什么是Makefile

Linux 環境下的程序員如果不會使用GNU make來構建和管理自己的工程,應該不能算是一個合格的專業程序員,至少不能稱得上是 Unix程序員。在 Linux(unix )環境下使用GNU 的make工具能夠比較容易的構建一個屬于你自己的工程,整個工程的編譯只需要一個命令就可以完成編譯、連接以至于最后的執行。不過這需要我們投入一些時間去完成一個或者多個稱之為Makefile 文件的編寫。
所要完成的Makefile 文件描述了整個工程的編譯、連接等規則。其中包括:工程中的哪些源文件需要編譯以及如何編譯、需要創建那些庫文件以及如何創建這些庫文件、如何最后產生我們想要的可執行文件。盡管看起來可能是很復雜的事情,但是為工程編寫Makefile 的好處是能夠使用一行命令來完成“自動化編譯”,一旦提供一個(通常對于一個工程來說會是多個)正確的 Makefile。編譯整個工程你所要做的唯一的一件事就是在shell 提示符下輸入make命令。整個工程完全自動編譯,極大提高了效率。
make是一個命令工具,它解釋Makefile 中的指令(應該說是規則)。在Makefile文件中描述了整個工程所有文件的編譯順序、編譯規則。Makefile 有自己的書寫格式、關鍵字、函數。像C 語言有自己的格式、關鍵字和函數一樣。而且在Makefile 中可以使用系統shell所提供的任何命令來完成想要的工作。Makefile(在其它的系統上可能是另外的文件名)在絕大多數的IDE 開發環境中都在使用,已經成為一種工程的編譯方法。

使用Makefile例子
1、建目錄

$ mkdir helloword
$ cd helloworld

2、 hello.c

#include <stdio.h>


int main(int argc, char const *argv[])
{
    printf("hello word!\n");
    return 0;
}

3.首先使用gcc編譯

gcc -o hello hello.c
./hello
output:hello word!

4.使用Makefile編譯
在工作目錄建立Makefile文件

hello:hello.c
    gcc -o hello hello.c

clean: 
    rm hello

使用make編譯

make
output:gcc -o hello hello.c
./hello
output:hello word!

5.Autoconf和Automake使用
1.生成configure

autoscan
ls
autoscan.log    configure.scan  hello.c

2.生成configure.ac
現在將configure.scan改名為configure.ac,并且編輯它,按下面的內容修改,去掉無關的語句:

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AM_INIT_AUTOMAKE(hello, 1.0)
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

3.執行aclocal和autoconf

aclocal
ls
aclocal.m4      autom4te.cache  autoscan.log    configure.ac    configure.scan  hello.c
autoconf
ls
aclocal.m4      autom4te.cache  autoscan.log    configure       configure.ac    configure.scan  hello.c

可以看到configure.ac內容是一些宏定義,這些宏經autoconf處理后會變成檢查系統特性、環境變量、軟件必須的參數的shell腳本。
autoconf 是用來生成自動配置軟件源代碼腳本(configure)的工具。configure腳本能獨立于autoconf運行,且在運行的過程中,不需要用戶的干預。
要生成configure文件,你必須告訴autoconf如何找到你所用的宏。方式是使用aclocal程序來生成你的aclocal.m4。
aclocal根據configure.ac文件的內容,自動生成aclocal.m4文件。aclocal是一個perl 腳本程序,它的定義是:“aclocal - create aclocal.m4 by scanning configure.ac”。
autoconf從configure.ac這個列舉編譯軟件時所需要各種參數的模板文件中創建configure。
autoconf需要GNU m4宏處理器來處理aclocal.m4,生成configure腳本。
m4是一個宏處理器。將輸入拷貝到輸出,同時將宏展開。宏可以是內嵌的,也可以是用戶定義的。除了可以展開宏,m4還有一些內建的函數,用來引用文件,執行命令,整數運算,文本操作,循環等。m4既可以作為編譯器的前端,也可以單獨作為一個宏處理器.
4.執行autoheader

autoheader
ls
aclocal.m4      autom4te.cache  autoscan.log    config.h.in     configure       configure.ac    configure.scan  hello.c

5.新建Makefile.am

AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=hello
hello_SOURCES=hello.c 

6.運行automake

automake --add-missing
configure.ac:6: warning: AM_INIT_AUTOMAKE: two- and three-arguments forms are deprecated.  For more info, see:
configure.ac:6: https://www.gnu.org/software/automake/manual/automake.html#Modernize-AM_005fINIT_005fAUTOMAKE-invocation
configure.ac:11: installing './compile'
configure.ac:6: installing './install-sh'
configure.ac:6: installing './missing'
Makefile.am: installing './depcomp'
ls
Makefile.am     aclocal.m4      autoscan.log    config.h.in     configure.ac    depcomp         install-sh
Makefile.in     autom4te.cache  compile         configure       configure.scan  hello.c         missing

automake會根據Makefile.am文件產生一些文件,包含最重要的Makefile.in。
7.執行configure生成Makefile

./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of gcc... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
ls
Makefile        aclocal.m4      compile         config.log      configure.ac    hello.c         stamp-h1
Makefile.am     autom4te.cache  config.h        config.status   configure.scan  install-sh
Makefile.in     autoscan.log    config.h.in     configure       depcomp         missing

8.make及運行

make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-am
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT hello.o -MD -MP -MF .deps/hello.Tpo -c -o hello.o hello.c
mv -f .deps/hello.Tpo .deps/hello.Po
gcc  -g -O2   -o hello hello.o
ls
Makefile        aclocal.m4      compile         config.log      configure.ac    hello           install-sh
Makefile.am     autom4te.cache  config.h        config.status   configure.scan  hello.c         missing
Makefile.in     autoscan.log    config.h.in     configure       depcomp         hello.o         stamp-h1
./hello
hello word!

autotools命令詳解:
1、 autoscan
  autoscan是用來掃描源代碼目錄生成configure.scan文件的。autoscan可以用目錄名做為參數,但如果你不使用參數的話,那么autoscan將認為使用的是當前目錄。autoscan將掃描你所指定目錄中的源文件,并創建configure.scan文件。

2、 configure.scan
  configure.scan包含了系統配置的基本選項,里面都是一些宏定義。我們需要將它改名為configure.ac

3、 aclocal
  aclocal是一個perl 腳本程序。aclocal根據configure.ac文件的內容,自動生成aclocal.m4文件。aclocal的定義是:“aclocal - create aclocal.m4 by scanning configure.ac”。

4、 autoconf
  使用autoconf,根據configure.in和aclocal.m4來產生configure文件。configure是一個腳本,它能設置源程序來適應各種不同的操作系統平臺,并且根據不同的系統來產生合適的Makefile,從而可以使你的源代碼能在不同的操作系統平臺上被編譯出來。
  configure.ac文件的內容是一些宏,這些宏經過autoconf 處理后會變成檢查系統特性、環境變量、軟件必須的參數的shell腳本。configure.ac文件中的宏的順序并沒有規定,但是你必須在所有宏的最前面和最后面分別加上AC_INIT宏和AC_OUTPUT宏。
  在configure.ini中:

  #號表示注釋,這個宏后面的內容將被忽略。

  AC_INIT(FILE) //這個宏用來檢查源代碼所在的路徑。
  AM_INIT_AUTOMAKE(PACKAGE, VERSION) //這個宏是必須的,它描述了我們將要生成的軟件包的名字及其版本號:PACKAGE是軟件包的名字,VERSION是版本號。當你使用make dist命令時,它會給你生成一個類似helloworld-1.0.tar.gz的軟件發行包,其中就有對應的軟件包的名字和版本號。
  AC_PROG_CC  //這個宏將檢查系統所用的C編譯器。
  AC_OUTPUT(FILE)  //這個宏是我們要輸出的Makefile的名字。

我們在使用automake時,實際上還需要用到其他的一些宏,但我們可以用aclocal 來幫我們自動產生。執行aclocal后我們會得到aclocal.m4文件。   產生了configure.ac和aclocal.m4 兩個宏文件后,我們就可以使用autoconf來產生configure文件了。

5、 Makefile.am
  Makefile.am是用來生成Makefile.in的,需要你手工書寫。Makefile.am中定義了一些內容:

  AUTOMAKE_OPTIONS  //在執行automake時,它會檢查目錄下是否存在標準GNU軟件包中應具備的各種文件,例如AUTHORS、ChangeLog、NEWS等文件。我們將其設置成foreign時,automake會改用一般軟件包的標準來檢查。
  bin_PROGRAMS  //這個是指定我們所要產生的可執行文件的文件名。如果你要產生多個可執行文件,那么在各個名字間用空格隔開。
  helloworld_SOURCES  //這個是指定產生“helloworld”時所需要的源代碼。如果它用到了多個源文件,那么請使用空格符號將它們隔開。比如需要helloworld.h,helloworld.c那么請寫成helloworld_SOURCES= helloworld.h helloworld.c。
  如果你在bin_PROGRAMS定義了多個可執行文件,則對應每個可執行文件都要定義相對的filename_SOURCES。

6、 automake
  使用automake,根據configure.in和Makefile.am來產生Makefile.in。
  --add-missing //定義是“add missing standard files to package”,它會讓automake加入一個標準的軟件包所必須的一些文件。
  用automake產生出來的Makefile.in文件是符合GNU Makefile慣例的,接下來我們只要執行configure這個shell 腳本就可以產生合適的 Makefile 文件了。

7、 Makefile
  在符合GNU Makefiel慣例的Makefile中,包含了一些基本的預先定義的操作:

  make      //根據Makefile編譯源代碼,連接,生成目標文件,可執行文件。
  make clean  //清除上次的make命令所產生的object文件(后綴為“.o”的文件)及可執行文件。
  make install  //將編譯成功的可執行文件安裝到系統目錄中,一般為/usr/local/bin目錄。
  make list  //產生發布軟件包文件(即distribution package)。這個命令會將可執行文件及相關文件打包成一個tar.gz壓縮的文件用來作為發布軟件的軟件包。它會在當前目錄下生成一個名字類似“PACKAGE-VERSION.tar.gz”的文件。PACKAGE和VERSION,是我們在configure.ac中定義的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。
  make distcheck  //生成發布軟件包并對其進行測試檢查,以確定發布包的正確性。這個操作將自動把壓縮包文件解開,然后執行configure命令,并且執行make,來確認編譯不出現錯誤,最后提示你軟件包已經準備好,可以發布了。
  make distclean  //類似make clean,但同時也將configure生成的文件全部刪除掉,包括Makefile。
667911-20160531165212977-1698924539.gif

5.代碼使用libimobiledevice

//
//  main.m
//  testlibimobiledevice
//
//  Created by dujianjie on 2018/6/26.
//  Copyright ? 2018年 dujianjie. All rights reserved.
//
#include <pthread.h>
#include <libimobiledevice/house_arrest.h>
#include <libimobiledevice/afc.h>
#include <libimobiledevice/lockdown.h>
#import <libimobiledevice/libimobiledevice.h>
#import <Foundation/Foundation.h>

void* modify(void *info) {
    int *error = malloc(sizeof(int));
    
    size_t len = strlen((char *)info);
    char *udid = malloc(len+1);
    if (!udid) {
        *error = -10;
        goto l_udid;
    }
    memset(udid, 0, len+1);
    memcpy(udid, info, len);
    
    idevice_t device = NULL;
    idevice_new(&device, udid);
    if (!device) {
        *error = -10;
        goto l_device;
    }
    
    NSLog(@"idevice_new success");
    
    lockdownd_client_t lockdownd_client = NULL;
    lockdownd_error_t lockdownd_err = LOCKDOWN_E_UNKNOWN_ERROR;
    
    lockdownd_err = lockdownd_client_new_with_handshake(device, &lockdownd_client, "handshake");
    if (lockdownd_err != LOCKDOWN_E_SUCCESS) {
        *error = -10;
        goto l_device;
    }
    NSLog(@"lockdownd_client_new_with_handshake success");
//    lockdownd_client_free(lockdownd_client);
    
    house_arrest_error_t err = 0;
    house_arrest_client_t client = NULL;
   
    err = house_arrest_client_start_service(device, &client, NULL);
    if(err  != HOUSE_ARREST_E_SUCCESS) {
        *error = err;
        goto l_device;
    }
    
    NSLog(@"house_arrest_client_start_service success");
    err = house_arrest_send_command(client, "VendContainer", "com.cvte.EasiAir");
    if(err  != HOUSE_ARREST_E_SUCCESS) {
        *error = err;
        goto l_house;
    }
    
    NSLog(@"house_arrest_send_command success");
    plist_t dict = NULL;
    
    err = house_arrest_get_result(client, &dict);
    
    if(err  != HOUSE_ARREST_E_SUCCESS) {
        NSLog(@"house_arrest_get_result  :%d", err);
        *error = err;
        goto l_house;
    }
    char *xml = NULL;
    uint32_t length = 0;
    plist_to_xml(dict, &xml, &length);
    
    NSLog(@"house_arrest_get_result success:%s",xml);
    if (xml) free(xml);
    
    
    plist_t node = plist_dict_get_item(dict, "Status");
    if (!node) {
        *error = -4;
        goto l_list;
    }
    char *status = NULL;
    plist_get_string_val(node, &status);
    if (!status) {
        *error = -4;
        goto l_list;
    }
    NSLog(@"status:%s", status);
    
    if (strcmp(status, "Complete")!=0) {
        *error = -4;
        goto l_status;
    }
    NSLog(@"house_arrest_send_command success");
    
    afc_client_t afc_client = NULL;
    afc_error_t afc_err = afc_client_new_from_house_arrest_client(client, &afc_client);
    
    if (afc_err != AFC_E_SUCCESS) {
        *error = afc_err;
        goto l_status;
    }
    NSLog(@"afc_client_new_from_house_arrest_client success");
    
    afc_err = afc_make_directory(afc_client, "Documents/Custom_dujj");
    
    if (afc_err != AFC_E_SUCCESS) {
        *error = afc_err;
        goto l_status;
    }
    NSLog(@"afc_make_directory success");
    
l_status:
    free(status);
l_list:
    plist_free(dict);
l_house:
     house_arrest_client_free(client);
l_device:
    idevice_free(device);
l_udid:
    free(udid);
    return error;
}

void idevice_event_callback(const idevice_event_t *event, void *user_data) {
    switch(event->event) {
        case IDEVICE_DEVICE_ADD:{
            NSLog(@"add device %s", event->udid);
            static pthread_t devmon = 0;
            if (devmon == 0)
            pthread_create(&devmon, NULL, modify, (void *)event->udid);
        }break;
        case IDEVICE_DEVICE_REMOVE:{
            NSLog(@"remove device %s", event->udid);
        }break;
        case IDEVICE_DEVICE_PAIRED:{
            NSLog(@"paired device %s", event->udid);
        }break;
        default: {
            
        }break;
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        idevice_set_debug_level(1);
        
        idevice_error_t err = idevice_event_subscribe(idevice_event_callback, NULL);
        struct timespec ts;
        ts.tv_sec = 180;
        ts.tv_nsec = 0;
        
        while(1) {
            nanosleep(&ts, NULL);
        }
        
        err = idevice_event_unsubscribe();
    }
    return 0;
}

6.libimobiledevice內部魔法

1.usbmuxd
A socket daemon to multiplex connections from and to iOS devices


1.png

2.守護進程
守護進程時一個在后臺運行并且不受任何終端控制的進程。
百度百科

3.udev
Linux kernel2.6系列的設備管理器,在libimobiledevice中用于監控usb熱插拔。
百度百科

4.systemd
Systemd 是 Linux 系統中最新的初始化系統(init),它主要的設計目標是克服 sysvinit 固有的缺點,提高系統的啟動速度。
百度百科

7.libimobiledevice結構圖

2.png

8.libimobiledevice提供的iOS服務

  • com.apple.afc,文件系統服務
  • com.apple.afc2,越獄文件系統服務
  • com.apple.mobile.installation_proxy,應用服務
  • com.apple.mobile.house_arrest,應用內文件服務
  • com.apple.mobile.notification_proxy,通知服務
    更多服務地址
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容