喵星软件园提供热门手机游戏下载,最新手机游戏攻略!

代号NSLG官网在哪下载 最新官方下载安装地址,

时间:2023-10-05 15:19:52 来源: 浏览:

网络通信5:HTTP下载器

游戏开发中需要动态下载资源。甚至支持热更新修复代码。都需要引擎支持文件下载功能。写个基于http协议的多线程文件下载器。用第三方库如何选择问题?如何实现接口方便调用问题?等等。其实前面也多次用到libcurl。libcurl作为是一个多协议的便于客户端使用的URL传输库。libcurl已经非常灵活。接口设计的非常细。完全可以满足需求。

这里分享一个基于libcurl的HTTP封装类,其功能包括:同步的(HTTP/HTTPS)GET、POST请求,以及文件下载和进度报告。通过网络通信1-5中通信相关的方式。游戏开发中用到的通信基本都囊括了。引擎底层已经具备了强大的通信能力和提供完善的接口。后续的重点就是如何使用好接口。和提高开发效率。尤其是团队中多人参与研发。如何高效协助的问题。必然需要完整的工具链实现网络消息生成。使真正开发者无需关注底层。只需依葫芦画瓢接收和发送消息即可。但作为游戏开发者还是需要多花些精力去实现下。知识大爆炸时代更需要组织代码能力强的人才。尽量减少代码编程的时间。

C++实现下载器类:Gk8HttpLoader.h

 #ifndef __GK8HTTPLOADER_H__ #define __GK8HTTPLOADER_H__ ​ #pragma once #include "Gk8BaseObj.h" #include "Gk8ByteMaker.h" #include "curl/curl.h" ​ ​ #define GK8LOADER_COUNT     0x0001 #define GK8LOADER_SIZE      0x0002 #define GK8LOADER_ONESIZE   0x0003 ​ class Gk8HttpLoader:public Gk8BaseObj {     DECLARE_TOSPP_MAP; private:     GK8_BOOL m_bLoading;                //是否正在文件下载     Gk8ByteMaker m_iFileBufData;        //<文件BUFF数据> ​     GK8_INT m_nLoadShowCase;            //<下载显示模式:1个数进度,2整体大小进度,3单个大小进度>     Gk8Var m_iLoadProgressCallVar;     Gk8Var m_iLoadFinishCallVar; ​     GK8_INT m_nLoadFileCount;           //<已下载文件个数>     GK8_INT m_nSumFileCount;            //<总文件个数> ​     GK8_INT m_nLoadFileSize;            //<已下载文件大小>     GK8_INT m_nSumFileSize;             //<总文件大小> ​ private:     GK8_BOOL InitLoaderThreadSemphore(); ​ public:     Gk8HttpLoader();     virtual ~Gk8HttpLoader();           static Gk8HttpLoader* GetInstance();     GK8_VOID Destroy();     GK8_VOID TOSPPFUNC AddLoaderFile(GK8_LPCSTR lpFileName,GK8_LPCSTR lpLoaderUrl,GK8_LPCSTR lpLocalFile,GK8_INT nFileSize);     GK8_VOID TOSPPFUNC SetHttpLoad(GK8_INT nLoadShowCase,Gk8Var& iLoadProgressCallVar,Gk8Var& iLoadFinishCallVar);     GK8_VOID TOSPPFUNC HttpLoaderFile();     ;     GK8_VOID OnProgress(GK8_LPVOID lpLoaderData,GK8_CDOUBLE dlTotal,GK8_CDOUBLE dlNow);     GK8_VOID ShowLoadBar(GK8_LPVOID lpLoaderData); ​     GK8_VOID HttpLoaderTick(); }; ​ #endif

C++实现下载器类:Gk8HttpLoader.cpp

 #include "Gk8HttpLoader.h" #include "Gk8OperSys.h" #include "Gk8SetMb.cpp" #include "Gk8BufEntry.cpp" #include "Gk8FileUtil.h" ​ #include <queue> #include <pthread.h> #include <semaphore.h> #include <errno.h> #include <fcntl.h> ​ typedef struct tagLOADERDATA {     GK8_UINT nNameId;                       //<文件名ID>     GK8_LPCSTR pFileName;                   //<下载文件名>     GK8_LPCSTR pLoaderUrl;                  //<下载HTTP URL>     GK8_LPCSTR pLocalFile;                  //<本地文件存放地>     GK8_INT nFileSize;                      //<文件大小>     Gk8ByteMaker* pFileBufData;             //<文件BUFF数据>     GK8_BOOL bSucceed;                      //     GK8_INT nResponseCode;                  //     Gk8Str sErrorBuffer;                    // }LOADERDATA,*LPLOADERDATA; ​ ​ static Gk8Str sg_iLoadProgressEvent("OnLoadProgress");  //<下载进度信息> static Gk8Str sg_iLoadFinishEvent("OnLoadFinish");      //<下载完成信息> ​ ​ static Gk8HttpLoader* sg_pHttpLoader=NULL;      //<静态HTTP下载器> static Gk8BufEntry<LOADERDATA,256>* s_pLoaderCache=NULL; ​ static GK8_BOOL sg_bHttpLoaderQuit=false;       //<退出HTTP下载器> static sem_t* sg_pLoaderSem=NULL;               //<信号量的数据类型> ​ //<线程及互斥定义> static pthread_t sg_LoaderNetWorkThread;                // static pthread_mutex_t sg_RequestLoaderMutex;           // static pthread_mutex_t sg_ResponseLoaderMutex;          // ​ static Gk8SetMb<LPLOADERDATA> sg_iRequestLoaderQueue;   // static Gk8SetMb<LPLOADERDATA> sg_iResponseLoaderQueue;  // ​ static GK8_CHAR sg_szLoaderErrorBuf;           //<错误信息> static Gk8Str sg_iLoaderErrorStr; ​ typedef size_t (*HTTPLOADERWRITE_CALLBACK)(GK8_LPVOID lpData,size_t nSize,size_t nMemBlock,GK8_LPVOID lpResponseData); ​ #if (GK8_TARGET_PLATFORM==GK8_PLATFORM_IOS) #define GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE 1 #else #define GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE 0 #endif ​ #if GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE #define GK8_ASYNC_HTTPREQUEST_SEMAPHORE "Gk8HttpAsync" #else static sem_t sg_iLoaderSem; #endif ​ ​ GK8_INT ProcessLoaderTask(LPLOADERDATA pHttpLoaderResponse,HTTPLOADERWRITE_CALLBACK lpCallBack,GK8_LPINT lpResponseCode); ​ ​ //<接收HTTP下载数据> size_t WriteHttpLoaderData(GK8_LPVOID lpData,size_t nSize,size_t nMemBlock,GK8_LPVOID lpResponseData) {     Gk8ByteMaker* pResponseData=(Gk8ByteMaker*)lpResponseData;     size_t nLength=nSize*nMemBlock;     pResponseData->WriteBuf(lpData,(GK8_INT)nLength);     return nLength; } ​ //<进度回调> size_t HandleLoaderProgress(GK8_LPVOID lpBuffer,GK8_DOUBLE dlTotal,GK8_DOUBLE dlNow,GK8_DOUBLE ulTotal,GK8_DOUBLE ulNow) {     LPLOADERDATA pHttpLoaderResponse=(LPLOADERDATA)lpBuffer;     Gk8HttpLoader* pHttpLoader=Gk8HttpLoader::GetInstance();     pHttpLoader->OnProgress(pHttpLoaderResponse,dlTotal,dlNow);     return 0; } ​ //<处理POST请求:流的行事发出> GK8_INT ProcessLoaderTask(LPLOADERDATA pHttpLoaderResponse,HTTPLOADERWRITE_CALLBACK lpWriteCallBack,GK8_LPINT lpResponseCode) {     CURLcode nCurlCode=CURL_LAST;     CURL* pCurl=curl_easy_init();     do     {         //远程URL,支持HTTP,HTTPS,FTP         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_URL,pHttpLoaderResponse->pLoaderUrl);         if(nCurlCode!=CURLE_OK) break; ​         //设置User-Agent         Gk8Str iUserAgentStr="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1";         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_USERAGENT,iUserAgentStr);         if(nCurlCode!=CURLE_OK) break; ​         //设置重定向的最大次数         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_MAXREDIRS,5);         if(nCurlCode!=CURLE_OK) break; ​         //设置301,302跳转跟随location         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_FOLLOWLOCATION,1);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_NOSIGNAL,1L);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_POST,false);         if(nCurlCode!=CURLE_OK) break; ​         //下载内容回调函数         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEFUNCTION,lpWriteCallBack);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,pHttpLoaderResponse->pFileBufData);         if(nCurlCode!=CURLE_OK) break; ​         //进度回调函数         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_NOPROGRESS, 0);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_PROGRESSDATA,pHttpLoaderResponse);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_PROGRESSFUNCTION,HandleLoaderProgress);         if(nCurlCode!=CURLE_OK) break; ​         //跳过服务器SSL验证,不使用CA证书         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_SSL_VERIFYPEER,0L);         if(nCurlCode!=CURLE_OK) break; ​         //验证服务器端发送的证书,默认是2(高),1(中),0(禁用)         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_SSL_VERIFYHOST,0L);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_setopt(pCurl,CURLOPT_ERRORBUFFER,sg_szLoaderErrorBuf);         if(nCurlCode!=CURLE_OK) return false; ​         nCurlCode=curl_easy_perform(pCurl);         if(nCurlCode!=CURLE_OK) break; ​         nCurlCode=curl_easy_getinfo(pCurl,CURLINFO_RESPONSE_CODE,lpResponseCode);         if(nCurlCode!=CURLE_OK||*lpResponseCode!=200)         {             nCurlCode=CURLE_HTTP_RETURNED_ERROR;         }     }while(0);     if(pCurl) curl_easy_cleanup(pCurl); ​     return (nCurlCode==CURLE_OK?0:1); } //<创建网络下载线程> static GK8_LPVOID LoaderNetWorkThread(GK8_LPVOID lpData) {     LPLOADERDATA pHttpLoaderRequest=NULL;     LPLOADERDATA pHttpLoaderResponse=NULL; ​     while(true)     {         //Wait for http request tasks from main thread         GK8_INT nSemWaitRet=sem_wait(sg_pLoaderSem);         if(nSemWaitRet<0)         {             _GK8ERR<<"HttpRequest async thread semaphore error:"<<strerror(errno)<<CR;             break;         }         if(sg_bHttpLoaderQuit) break; ​         //<第一步:发送HTTP请求>         pHttpLoaderRequest=NULL;         pthread_mutex_lock(&sg_RequestLoaderMutex);         if(0!=sg_iRequestLoaderQueue.GetSize())         {             pHttpLoaderRequest=sg_iRequestLoaderQueue.GetItemAt(0);             sg_iRequestLoaderQueue.RemoveItemAt(0);         }         pthread_mutex_unlock(&sg_RequestLoaderMutex); ​         if(NULL==pHttpLoaderRequest) continue;         //<清除二进制数据>         pHttpLoaderRequest->pFileBufData->ClearStream(); ​         //<第二步:libCurl同步请求>         //pHttpLoaderResponse=new LOADERDATA();         //memcpy(pHttpLoaderResponse,pHttpLoaderRequest,sizeof(LOADERDATA));         pHttpLoaderResponse=pHttpLoaderRequest;         GK8_INT nResponseCode=-1;         GK8_INT nRetValue=0; ​ ​         nRetValue=ProcessLoaderTask(pHttpLoaderResponse,WriteHttpLoaderData,&nResponseCode); ​         pHttpLoaderResponse->nResponseCode=nResponseCode;         if(nRetValue!=0)         {             pHttpLoaderResponse->bSucceed=false;             sg_iLoaderErrorStr=sg_szLoaderErrorBuf;             pHttpLoaderResponse->sErrorBuffer=sg_iLoaderErrorStr;         }else         {             pHttpLoaderResponse->bSucceed=true;         } ​         pthread_mutex_lock(&sg_ResponseLoaderMutex);         sg_iResponseLoaderQueue.AddItem(pHttpLoaderResponse);         pthread_mutex_unlock(&sg_ResponseLoaderMutex);     }     pthread_mutex_lock(&sg_RequestLoaderMutex);     sg_iRequestLoaderQueue.Clear();     pthread_mutex_unlock(&sg_RequestLoaderMutex); ​     if(sg_pLoaderSem!=NULL)     { #if GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE         sem_unlink(GK8_ASYNC_HTTPREQUEST_SEMAPHORE);         sem_close(sg_pLoaderSem); #else         sem_destroy(sg_pLoaderSem); #endif         sg_pLoaderSem=NULL; ​         pthread_mutex_destroy(&sg_RequestLoaderMutex);         pthread_mutex_destroy(&sg_ResponseLoaderMutex); ​         //<依次删除数据>         GK8_INT nIndex;         for(nIndex=0;nIndex<sg_iRequestLoaderQueue.GetSize();nIndex++)         {             pHttpLoaderRequest=sg_iRequestLoaderQueue.GetItemAt(nIndex);             delete pHttpLoaderRequest;         }         for(nIndex=0;nIndex<sg_iResponseLoaderQueue.GetSize();nIndex++)         {             pHttpLoaderResponse=sg_iResponseLoaderQueue.GetItemAt(nIndex);             delete pHttpLoaderResponse;         }         sg_iRequestLoaderQueue.Destroy();         sg_iResponseLoaderQueue.Destroy();     }     pthread_exit(NULL);     return 0; } ​ /////////////////////////////////////////////CLASS-TOSPP//////////////////////////////////////////////////// BEGIN_TOSPP_MAP(Gk8HttpLoader,Gk8BaseObj)     TOSPP_FUNC(Gk8HttpLoader,AddLoaderFile,' ',"sssd","AddLoaderFile(lpFileName,lpLoaderUrl,lpLocalFile,nFileSize)")     TOSPP_FUNC(Gk8HttpLoader,HttpLoaderFile,' ',"","HttpLoaderFile()")     TOSPP_FUNC(Gk8HttpLoader,SetHttpLoad,' ',"d&v&v","SetHttpLoad(nLoadShowCase,iLoadProgressCallVar,iLoadFinishCallVar)") END_TOSPP_MAP() ​ BEGIN_TOSPP_STATIC_FUN_MAP(Gk8HttpLoader)     TOSPP_STATIC_FUNC(Gk8HttpLoader,GetInstance,'p',"","") END_TOSPP_STATIC_FUN_MAP ///////////////////////////////////////////////////////////////////////////////////////////////// Gk8HttpLoader::Gk8HttpLoader() {     m_bLoading=false;     m_nLoadShowCase=1; ​     m_nLoadFileCount=0;     m_nSumFileCount=0; ​     m_nLoadFileSize=0;     m_nSumFileSize=0; ​     s_pLoaderCache=new Gk8BufEntry<LOADERDATA,256>; } ​ Gk8HttpLoader::~Gk8HttpLoader() {     sg_bHttpLoaderQuit=true;     if(sg_pLoaderSem!=NULL)     {         sem_post(sg_pLoaderSem);     }     Destroy(); } //<初始化线程> GK8_BOOL Gk8HttpLoader::InitLoaderThreadSemphore() {     if(sg_pLoaderSem!=NULL)     {         return true;     }else     { #if GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE         sg_pLoaderSem=sem_open(GK8_ASYNC_HTTPREQUEST_SEMAPHORE,O_CREAT,0644,0);         if(sg_pLoaderSem==SEM_FAILED)         {             _GK8ERR<<"Open HttpLoader Semaphore Failed"<<CR;             sg_pLoaderSem=NULL;             return false;         } #else         GK8_INT nSemRet=sem_init(&sg_iLoaderSem,0,0);         if(nSemRet<0)         {             _GK8ERR<<"Init HttpLoader Semaphore Failed"<<CR;             return false;         }         sg_pLoaderSem=&sg_iLoaderSem; #endif         pthread_mutex_init(&sg_RequestLoaderMutex,NULL);         pthread_mutex_init(&sg_ResponseLoaderMutex,NULL); ​         pthread_create(&sg_LoaderNetWorkThread,NULL,LoaderNetWorkThread,NULL);         pthread_detach(sg_LoaderNetWorkThread); ​         sg_bHttpLoaderQuit=false;     }     return true; } ​ //<增加HTTP下载文件> GK8_VOID Gk8HttpLoader::AddLoaderFile(GK8_LPCSTR lpFileName,GK8_LPCSTR lpLoaderUrl,GK8_LPCSTR lpLocalFile,GK8_INT nFileSize) {     GK8_UINT nNameId=Gk8OperSys::Str2UINT(lpFileName);     LPLOADERDATA lpLoaderData=s_pLoaderCache->AddEntryWithNameId(nNameId);     lpLoaderData->nNameId=nNameId;     lpLoaderData->pFileName=lpFileName;     lpLoaderData->pLoaderUrl=lpLoaderUrl;     lpLoaderData->pLocalFile=lpLocalFile;     lpLoaderData->nFileSize=nFileSize;     lpLoaderData->pFileBufData=&m_iFileBufData; ​     //<准备显示数据>     m_nSumFileCount++;     m_nSumFileSize+=nFileSize;     m_iFileBufData.WriteAlloc(nFileSize); } ​ GK8_VOID Gk8HttpLoader::SetHttpLoad(GK8_INT nLoadShowCase,Gk8Var& iLoadProgressCallVar,Gk8Var& iLoadFinishCallVar) {     m_nLoadShowCase=nLoadShowCase;     m_iLoadProgressCallVar=iLoadProgressCallVar;     m_iLoadFinishCallVar=iLoadFinishCallVar; ​     Gk8Var iEmptyVar;     if(iLoadProgressCallVar.GetSize()==1 && iLoadProgressCallVar<0>.IfInt()) SetEvent(sg_iLoadProgressEvent,iLoadProgressCallVar<0>,iEmptyVar);     if(iLoadFinishCallVar.GetSize()==1 && iLoadFinishCallVar<0>.IfInt()) SetEvent(sg_iLoadFinishEvent,iLoadFinishCallVar<0>,iEmptyVar); } ​ //<开始HTTP下载文件> GK8_VOID Gk8HttpLoader::HttpLoaderFile() {     //正在下载中     if(m_bLoading) return;     if(s_pLoaderCache->GetBufNum()<1)     {         //<通知脚本下载完成>         static Gk8Var sl_iParamVar;         sl_iParamVar.RemoveAll();         if(m_iLoadFinishCallVar.GetSize()==1)         {             RunEventWithArgs(sg_iLoadFinishEvent,sl_iParamVar);         }else         {             if(!m_iLoadFinishCallVar<0>.IfPtr()||!IfObjPtr(m_iLoadFinishCallVar<0>.GetPtr(),m_iLoadFinishCallVar<0>.GetPtrId())) return; ​             Gk8Obj* pBindObj=m_iLoadFinishCallVar<0>.GetSafePtr(); ​             pBindObj->OnCall(m_iLoadFinishCallVar<1>,sl_iParamVar);         }         m_nLoadFileCount=0;         m_nSumFileCount=0; ​         m_nLoadFileSize=0;         m_nSumFileSize=0;         return;     }     LPLOADERDATA lpLoaderData=s_pLoaderCache->GetBufEntryAt(0);     if(false==InitLoaderThreadSemphore()) return;     if(!lpLoaderData) return;     m_bLoading=true;     pthread_mutex_lock(&sg_RequestLoaderMutex);     sg_iRequestLoaderQueue.AddItem(lpLoaderData);     pthread_mutex_unlock(&sg_RequestLoaderMutex); ​     sem_post(sg_pLoaderSem); } //<处理进度条> GK8_VOID Gk8HttpLoader::OnProgress(GK8_LPVOID lpLoaderData,GK8_CDOUBLE dlTotal,GK8_CDOUBLE dlNow) {     LPLOADERDATA pLoaderData=(LPLOADERDATA)lpLoaderData; ​     static Gk8Var sl_iParamVar;     sl_iParamVar.RemoveAll();     sl_iParamVar<<m_nLoadShowCase<<pLoaderData->pFileName<<m_nLoadFileCount<<m_nSumFileCount         <<(m_nLoadFileSize+(GK8_INT)dlNow)<<m_nSumFileSize<<(GK8_INT)dlTotal<<(GK8_INT)dlNow;     if(m_iLoadProgressCallVar.GetSize()==1)     {         RunEventWithArgs(sg_iLoadProgressEvent,sl_iParamVar);     }else     {         if(!m_iLoadProgressCallVar<0>.IfPtr()||!IfObjPtr(m_iLoadProgressCallVar<0>.GetPtr(),m_iLoadProgressCallVar<0>.GetPtrId())) return; ​         Gk8Obj* pBindObj=m_iLoadProgressCallVar<0>.GetSafePtr();                  pBindObj->OnCall(m_iLoadProgressCallVar<1>,sl_iParamVar);     } } //<显示进度条> GK8_VOID Gk8HttpLoader::ShowLoadBar(GK8_LPVOID lpLoaderData) {     LPLOADERDATA pLoaderData=(LPLOADERDATA)lpLoaderData;     GK8_DOUBLE dlTotal=pLoaderData->nFileSize;     GK8_DOUBLE dlNow=pLoaderData->nFileSize;     OnProgress(lpLoaderData,dlTotal,dlNow); } //<获取HTTP下载器单例> Gk8HttpLoader* Gk8HttpLoader::GetInstance() {     if(sg_pHttpLoader==NULL) sg_pHttpLoader=new Gk8HttpLoader();     return sg_pHttpLoader; } ​ GK8_VOID Gk8HttpLoader::Destroy() {     if(s_pLoaderCache!=NULL)     {         delete s_pLoaderCache;         s_pLoaderCache=NULL;     } } // GK8_VOID Gk8HttpLoader::HttpLoaderTick() {     if(sg_pLoaderSem==NULL) return; ​     LPLOADERDATA pLoaderData=NULL; ​     pthread_mutex_lock(&sg_ResponseLoaderMutex);     if(sg_iResponseLoaderQueue.GetSize()>0)     {         pLoaderData=sg_iResponseLoaderQueue.GetItemAt(0);         sg_iResponseLoaderQueue.RemoveItemAt(0);     }     pthread_mutex_unlock(&sg_ResponseLoaderMutex); ​     if(!pLoaderData) return; ​     //<把服务器数据派遣到脚本中>     Gk8Str iResponstStr;     if(!pLoaderData->bSucceed)     {         _GK8ERR<<"Gk8HttpLoader Response Failed Error Is "<<pLoaderData->sErrorBuffer<<" nResponseCode:"<<pLoaderData->nResponseCode<<CR;         iResponstStr="UnKown Error";     }else     {         //保存文件         Gk8File iFile;         if(iFile.Open(pLoaderData->pLocalFile,Gk8File::modeWriteAbs))         {             iFile.Write(pLoaderData->pFileBufData->GetBuf(),pLoaderData->pFileBufData->GetStreamSize());             iFile.Close();         }     }     m_nLoadFileCount++;     ShowLoadBar(pLoaderData);     m_nLoadFileSize+=pLoaderData->nFileSize; ​     //移除已下载数据:继续下载     s_pLoaderCache->RemoveEntryWithNameId(pLoaderData->nNameId);     m_bLoading=false;     HttpLoaderFile(); }


标题:代号NSLG官网在哪下载 最新官方下载安装地址,
链接:https://www.miaoshengapp.cn/yxgl/131637.html
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
《江南百景图》灵犀旗使用方法说明[多图] 灵犀旗有什么用,
《江南百景图》灵犀旗使用方法说明[多图] 灵犀旗有什么用,

【协会动态】山东省人工智能领域产学研合作

2023-10-04
汉字找茬王打工人的一周怎么过 打工人的一周通关攻略
汉字找茬王打工人的一周怎么过 打工人的一周通关攻略

汉字找茬王是一款在抖音上非常热门的找茬小

2023-07-19
《宝可梦大探险》热带龙&木棉球&种子铁球,
《宝可梦大探险》热带龙&木棉球&种子铁球,

五只似龙非龙的神奇宝贝,隔壁椰蛋树都是龙,你

2023-10-04
《天谕手游》花园迷宫高级怎么过?花园迷宫高级过关攻略,
《天谕手游》花园迷宫高级怎么过?花园迷宫高级过关攻略,

本周精品手机游戏测试推荐,天谕手游战魂铭人

2023-10-04
《原神》万端珊瑚事件簿稍欠灵光简要心得,原神万端珊瑚事件簿有隐藏成就吗
《原神》万端珊瑚事件簿稍欠灵光简要心得,原神万端珊瑚事件簿有隐藏成就吗

原神·成就丨稻妻每日委托及其对应成就获取

2023-10-05
《博德之门3》正式版可选种族与职业介绍
《博德之门3》正式版可选种族与职业介绍

这里为大家带来《博德之门3》正式版可选种

2023-08-06