Steam.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. #define SUPPORT_STEAM (DESKTOP && !ARM)
  4. #define CLOUD_WORKAROUND 1 // perhaps this is needed only when running apps manually and not through Steam
  5. #if SUPPORT_STEAM
  6. #pragma warning(push)
  7. #pragma warning(disable:4996)
  8. #include "../../../ThirdPartyLibs/begin.h"
  9. #include "../../../ThirdPartyLibs/SteamWorks/steam_api.h"
  10. #include "../../../ThirdPartyLibs/end.h"
  11. #pragma warning(pop)
  12. #endif
  13. #if DEBUG && 0
  14. #define ISteamMicroTxn "ISteamMicroTxnSandbox"
  15. #else
  16. #define ISteamMicroTxn "ISteamMicroTxn"
  17. #endif
  18. #if SUPPORT_MBED_TLS
  19. #define STEAM_API "https://api.steampowered.com/" ISteamMicroTxn // use HTTPS if we can support it
  20. #else
  21. #define STEAM_API "http://api.steampowered.com/" ISteamMicroTxn // fall back to HTTP
  22. #endif
  23. #define DAYS_PER_MONTH 30.436875f // average number of days in a month (365.2425 days in a year / 12 months) https://en.wikipedia.org/wiki/Year#Summary
  24. /******************************************************************************/
  25. namespace EE{
  26. /******************************************************************************/
  27. Str SteamWorks::StoreLink(C Str &app_id) {return S+"https://store.steampowered.com/app/"+app_id;}
  28. /******************************************************************************/
  29. enum OPERATION_TYPE
  30. {
  31. PURCHASE ,
  32. FINALIZE ,
  33. QUERY_PURCHASE ,
  34. QUERY_SUBSCRIPTION,
  35. };
  36. /******************************************************************************/
  37. #if SUPPORT_STEAM
  38. static struct SteamCallbacks // !! do not remove this !!
  39. {
  40. STEAM_CALLBACK(SteamCallbacks, MicroTxnAuthorizationResponse, MicroTxnAuthorizationResponse_t, m_MicroTxnAuthorizationResponse);
  41. STEAM_CALLBACK(SteamCallbacks, PersonaStateChange , PersonaStateChange_t, m_PersonaStateChange );
  42. STEAM_CALLBACK(SteamCallbacks, AvatarImageLoaded , AvatarImageLoaded_t, m_AvatarImageLoaded );
  43. SteamCallbacks() : // this will register the callbacks using Steam API, using callbacks requires 'SteamUpdate' to be called
  44. m_MicroTxnAuthorizationResponse(this, &SteamCallbacks::MicroTxnAuthorizationResponse),
  45. m_PersonaStateChange (this, &SteamCallbacks::PersonaStateChange ),
  46. m_AvatarImageLoaded (this, &SteamCallbacks::AvatarImageLoaded )
  47. {}
  48. }SC;
  49. void SteamCallbacks::MicroTxnAuthorizationResponse(MicroTxnAuthorizationResponse_t *response)
  50. {
  51. if(response && response->m_unAppID==Steam.appID())
  52. {
  53. if(response->m_bAuthorized)
  54. {
  55. Memt<HTTPParam> p;
  56. p.New().set("key" , Steam._web_api_key , HTTP_POST);
  57. p.New().set("appid" , response->m_unAppID , HTTP_POST);
  58. p.New().set("orderid", (ULong)response->m_ulOrderID, HTTP_POST);
  59. SteamWorks::Operation &op=Steam._operations.New();
  60. op.type =FINALIZE;
  61. op.order_id=response->m_ulOrderID;
  62. op.create(STEAM_API "/FinalizeTxn/V0001/", p);
  63. }else
  64. if(auto callback=Steam.order_callback)callback(SteamWorks::ORDER_USER_CANCELED, response->m_ulOrderID, S, null, null);
  65. }
  66. }
  67. void SteamCallbacks::PersonaStateChange(PersonaStateChange_t *change)
  68. {
  69. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_Name==k_EPersonaChangeName);
  70. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_Status==k_EPersonaChangeStatus);
  71. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_ComeOnline==k_EPersonaChangeComeOnline);
  72. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_GoneOffline==k_EPersonaChangeGoneOffline);
  73. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_GamePlayed==k_EPersonaChangeGamePlayed);
  74. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_GameServer==k_EPersonaChangeGameServer);
  75. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_Avatar==k_EPersonaChangeAvatar);
  76. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_JoinedSource==k_EPersonaChangeJoinedSource);
  77. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_LeftSource==k_EPersonaChangeLeftSource);
  78. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_RelationshipChanged==k_EPersonaChangeRelationshipChanged);
  79. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_NameFirstSet==k_EPersonaChangeNameFirstSet);
  80. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_FacebookInfo==k_EPersonaChangeFacebookInfo);
  81. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_Nickname==k_EPersonaChangeNickname);
  82. ASSERT(SteamWorks::FRIEND_STATE_CHANGE_SteamLevel==k_EPersonaChangeSteamLevel);
  83. if(change)if(auto callback=Steam.friend_state_changed)callback(change->m_ulSteamID, change->m_nChangeFlags);
  84. }
  85. void SteamCallbacks::AvatarImageLoaded(AvatarImageLoaded_t *avatar) // called when 'GetLargeFriendAvatar' was requested but not yet available, simply notify user with callback that new avatar is available for a user
  86. {
  87. if(avatar)if(auto callback=Steam.friend_state_changed)callback(avatar->m_steamID.ConvertToUint64(), SteamWorks::FRIEND_STATE_CHANGE_Avatar);
  88. }
  89. #endif
  90. SteamWorks Steam;
  91. /******************************************************************************/
  92. static void SteamUpdate () {Steam.update();}
  93. static ULong SteamUnixTime()
  94. {
  95. #if SUPPORT_STEAM
  96. if(ISteamUtils *i=SteamUtils())return i->GetServerRealTime();
  97. #endif
  98. return 0;
  99. }
  100. static Bool SetDateFromYYYYMMDD(DateTime &dt, C Str &text)
  101. {
  102. dt.zero();
  103. if(text.length()==8)
  104. {
  105. REPA(text)if(!FlagTest(CharFlag(text[i]), CHARF_DIG))goto invalid;
  106. dt.year =CharInt(text[0])*1000
  107. +CharInt(text[1])* 100
  108. +CharInt(text[2])* 10
  109. +CharInt(text[3])* 1;
  110. dt.month=CharInt(text[4])* 10
  111. +CharInt(text[5])* 1;
  112. dt.day =CharInt(text[6])* 10
  113. +CharInt(text[7])* 1;
  114. return true;
  115. }
  116. invalid:
  117. return false;
  118. }
  119. /******************************************************************************/
  120. SteamWorks::Purchase::Purchase()
  121. {
  122. finalized=false;
  123. user_id=0;
  124. item_id=cost_in_cents=0;
  125. date.zero();
  126. }
  127. SteamWorks::Subscription::Subscription()
  128. {
  129. active=false;
  130. item_id=cost_in_cents=frequency=0;
  131. period=DAY;
  132. created.zero();
  133. last_payment.zero();
  134. next_payment.zero();
  135. }
  136. #if 0 // following function was used to check that MONTH formula will work on every possible date
  137. void Test()
  138. {
  139. DateTime last_payment; last_payment.zero();
  140. last_payment.day=1;
  141. last_payment.month=1;
  142. REP(365*400) // include leap-years
  143. {
  144. for(Int frequency=1; frequency<=12; frequency++)
  145. {
  146. DateTime cur_time=last_payment; REP(frequency)cur_time.incMonth();
  147. ULong unix_time=cur_time.seconds();
  148. ULong expiry=last_payment.seconds();
  149. switch(2)
  150. {
  151. case 0: expiry+= ( frequency* 1+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  152. case 1: expiry+= ( frequency* 14+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  153. case 2: expiry+=Trunc((frequency*DAYS_PER_MONTH+2.21f)*24)* 3600; break; // 3600=number of seconds in hour (60*60 ), +1 for 1 day tolerance
  154. case 3: expiry+= ( frequency* 365+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  155. }
  156. if(!(unix_time<=expiry))
  157. {
  158. Flt a=unix_time/86400.0f,
  159. b=expiry /86400.0f;
  160. int z=0; // FAIL
  161. }
  162. }
  163. last_payment.incDay();
  164. }
  165. int z=0; // OK
  166. }
  167. #endif
  168. Bool SteamWorks::Subscription::valid()C
  169. {
  170. if(last_payment.valid())
  171. {
  172. ULong unix_time=SteamUnixTime(); if(!unix_time)return false; // we need to know Steam time
  173. ULong expiry=last_payment.seconds1970();
  174. switch(period)
  175. {
  176. case DAY : expiry+= ( frequency* 1+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  177. case WEEK : expiry+= ( frequency* 14+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  178. case MONTH: expiry+=Trunc((frequency*DAYS_PER_MONTH+2.21f)*24)* 3600; break; // 3600=number of seconds in an hour (60*60 ), day tolerance was calculated using 'Test' function above
  179. case YEAR : expiry+= ( frequency* 365+1 )*86400; break; // 86400=number of seconds in a day (60*60*24), +1 for 1 day tolerance
  180. }
  181. return unix_time<=expiry;
  182. }
  183. return false;
  184. }
  185. /******************************************************************************/
  186. SteamWorks:: SteamWorks() {_initialized=false; init();}
  187. SteamWorks::~SteamWorks() { shut();}
  188. /******************************************************************************/
  189. SteamWorks& SteamWorks::webApiKey(C Str8 &web_api_key) {T._web_api_key=web_api_key; return T;}
  190. #if SUPPORT_STEAM
  191. extern void (*SteamSetTime)();
  192. static void _SteamSetTime() // this is called at least once
  193. {
  194. if(ISteamUtils *i=SteamUtils())
  195. {
  196. Steam._start_time_s=i->GetSecondsSinceAppActive(); // 'GetSecondsSinceAppActive' is since Steam Client was started and not this application
  197. }
  198. if(App._callbacks.initialized())App._callbacks.include(SteamUpdate); // include only if initialized, as this may be called before 'App' constructor and it would crash
  199. }
  200. #endif
  201. Bool SteamWorks::init()
  202. {
  203. #if SUPPORT_STEAM
  204. if(!_initialized)
  205. {
  206. _initialized=SteamAPI_Init();
  207. // we can't add 'SteamUpdate' to the 'App._callbacks' here because this may be called in the constructor and 'App._callbacks' may not be initialized yet, instead this will be done in '_SteamSetTime' which is called here and during Time creation where 'App._callbacks' will be initialized
  208. SteamSetTime=_SteamSetTime; // set function pointer, so when 'Time' gets initialized, it will call this function too
  209. _SteamSetTime(); // call anyway, in case 'Time' was already initialized
  210. }
  211. #endif
  212. return _initialized;
  213. }
  214. void SteamWorks::shut()
  215. {
  216. #if SUPPORT_STEAM
  217. if(_initialized)
  218. {
  219. SteamAPI_Shutdown();
  220. _initialized=false;
  221. }
  222. #endif
  223. }
  224. /******************************************************************************/
  225. // GET
  226. /******************************************************************************/
  227. UInt SteamWorks::appID()C
  228. {
  229. #if SUPPORT_STEAM
  230. if(ISteamUtils *i=SteamUtils())return i->GetAppID();
  231. #endif
  232. return 0;
  233. }
  234. ULong SteamWorks::userID()C
  235. {
  236. #if SUPPORT_STEAM
  237. if(ISteamUser *i=SteamUser())return i->GetSteamID().ConvertToUint64();
  238. #endif
  239. return 0;
  240. }
  241. Str SteamWorks::userName()C
  242. {
  243. #if SUPPORT_STEAM
  244. if(ISteamFriends *i=SteamFriends())return FromUTF8(i->GetPersonaName());
  245. #endif
  246. return S;
  247. }
  248. #if SUPPORT_STEAM
  249. static SteamWorks::USER_STATUS SteamStatus(EPersonaState state)
  250. {
  251. ASSERT(k_EPersonaStateMax==7);
  252. switch(state)
  253. {
  254. case k_EPersonaStateOffline: return SteamWorks::STATUS_OFFLINE;
  255. case k_EPersonaStateLookingToTrade:
  256. case k_EPersonaStateLookingToPlay :
  257. case k_EPersonaStateOnline : return SteamWorks::STATUS_ONLINE;
  258. case k_EPersonaStateBusy :
  259. case k_EPersonaStateAway :
  260. case k_EPersonaStateSnooze: return SteamWorks::STATUS_AWAY;
  261. }
  262. return SteamWorks::STATUS_UNKNOWN;
  263. }
  264. #endif
  265. SteamWorks::USER_STATUS SteamWorks::userStatus()C
  266. {
  267. #if SUPPORT_STEAM
  268. if(ISteamFriends *i=SteamFriends())return SteamStatus(i->GetPersonaState());
  269. #endif
  270. return STATUS_UNKNOWN;
  271. }
  272. Bool SteamWorks::userAvatar(Image &image)C {return userAvatar(userID(), image);}
  273. CChar8* SteamWorks::appLanguage()C
  274. {
  275. #if SUPPORT_STEAM
  276. if(ISteamApps *i=SteamApps())return i->GetCurrentGameLanguage();
  277. #endif
  278. return null;
  279. }
  280. CChar8* SteamWorks::country()C
  281. {
  282. #if SUPPORT_STEAM
  283. if(ISteamUtils *i=SteamUtils())return i->GetIPCountry();
  284. #endif
  285. return null;
  286. }
  287. DateTime SteamWorks::date()C
  288. {
  289. DateTime dt;
  290. #if SUPPORT_STEAM
  291. if(ULong unix_time=SteamUnixTime())dt.from1970s(unix_time);else
  292. #endif
  293. dt.zero();
  294. return dt;
  295. }
  296. UInt SteamWorks::curTimeS()C
  297. {
  298. #if SUPPORT_STEAM
  299. if(ISteamUtils *i=SteamUtils())return i->GetSecondsSinceAppActive()-_start_time_s;
  300. #endif
  301. return 0;
  302. }
  303. Bool SteamWorks::overlayAvailable()C
  304. {
  305. #if SUPPORT_STEAM
  306. if(ISteamUtils *i=SteamUtils())return i->IsOverlayEnabled();
  307. #endif
  308. return false;
  309. }
  310. Bool SteamWorks::overlayAvailableMsgBox()C
  311. {
  312. #if SUPPORT_STEAM
  313. if(overlayAvailable())return true;
  314. Gui.msgBox("Enable Steam Game Overlay", "Making purchases requires \"Steam Game Overlay\" to be enabled.\nPlease go to \"Steam Client Settings \\ In-Game\", click on the \"Enable the Steam Overlay while in-game\" and restart this app.");
  315. #endif
  316. return false;
  317. }
  318. Bool SteamWorks::drmExit()C
  319. {
  320. #if SUPPORT_STEAM
  321. UInt app_id=appID();
  322. if( !app_id || SteamAPI_RestartAppIfNecessary(app_id)){App.flag|=APP_EXIT_IMMEDIATELY; return true;}
  323. #endif
  324. return false;
  325. }
  326. #if 0
  327. SteamWorks::RESULT SteamWorks::getUserInfo()
  328. {
  329. if(!init() )return STEAM_NOT_INITIALIZED;
  330. if(!_web_api_key.is())return STEAM_NOT_SETUP;
  331. if(!userID() )return USER_ID_UNAVAILABLE;
  332. Memt<HTTPParam> p;
  333. p.New().set("steamid", Steam.userID());
  334. d.create(STEAM_API "/GetUserInfo/V0001/", p);
  335. }
  336. #endif
  337. /******************************************************************************/
  338. // FRIENDS
  339. /******************************************************************************/
  340. Bool SteamWorks::getFriends(MemPtr<ULong> friend_ids)C
  341. {
  342. #if SUPPORT_STEAM
  343. if(ISteamFriends *f=SteamFriends())
  344. {
  345. UInt flags=k_EFriendFlagImmediate;
  346. friend_ids.setNum(f->GetFriendCount(flags)); REPAO(friend_ids)=f->GetFriendByIndex(i, flags).ConvertToUint64();
  347. return true;
  348. }
  349. #endif
  350. friend_ids.clear(); return false;
  351. }
  352. Str SteamWorks::userName(ULong user_id)C
  353. {
  354. #if SUPPORT_STEAM
  355. if(ISteamFriends *i=SteamFriends())return FromUTF8(i->GetFriendPersonaName((uint64)user_id));
  356. #endif
  357. return S;
  358. }
  359. SteamWorks::USER_STATUS SteamWorks::userStatus(ULong user_id)C
  360. {
  361. #if SUPPORT_STEAM
  362. if(ISteamFriends *i=SteamFriends())return SteamStatus(i->GetFriendPersonaState((uint64)user_id));
  363. #endif
  364. return STATUS_UNKNOWN;
  365. }
  366. Bool SteamWorks::userAvatar(ULong user_id, Image &image)C
  367. {
  368. #if SUPPORT_STEAM
  369. if(ISteamFriends *i=SteamFriends())
  370. {
  371. Int image_id=i->GetLargeFriendAvatar((uint64)user_id); // request large avatar first, if not available then it will return -1 and 'AvatarImageLoaded' will be called where we notify of new avatar available
  372. if( image_id<0)image_id=i->GetMediumFriendAvatar((uint64)user_id); // if not yet loaded, then get medium size, this should always be available
  373. if(!image_id){image.del(); return true;} // according to steam headers, 0 means no image set
  374. if(ISteamUtils *i=SteamUtils())
  375. {
  376. UInt width=0, height=0;
  377. if(i->GetImageSize(image_id, &width, &height))
  378. if(image.createSoftTry(width, height, 1, IMAGE_R8G8B8A8))
  379. if(i->GetImageRGBA(image_id, image.data(), image.memUsage()))
  380. return true;
  381. }
  382. }
  383. #endif
  384. image.del(); return false;
  385. }
  386. /******************************************************************************/
  387. // ORDER
  388. /******************************************************************************/
  389. SteamWorks::RESULT SteamWorks::purchase(ULong order_id, Int item_id, C Str &item_name, Int cost_in_cents, C Str8 &currency)
  390. {
  391. if(!init() )return STEAM_NOT_INITIALIZED;
  392. if(!_web_api_key.is())return STEAM_NOT_SETUP;
  393. if(! appID() )return APP_ID_UNAVAILABLE;
  394. if(!userID() )return USER_ID_UNAVAILABLE;
  395. Memt<HTTPParam> p;
  396. p.New().set("key", _web_api_key, HTTP_POST);
  397. p.New().set("steamid", userID(), HTTP_POST);
  398. p.New().set("appid" , appID(), HTTP_POST);
  399. p.New().set("orderid", order_id, HTTP_POST);
  400. p.New().set("itemcount", 1, HTTP_POST);
  401. p.New().set("language", "EN", HTTP_POST);
  402. p.New().set("currency", currency, HTTP_POST);
  403. p.New().set("itemid[0]", item_id, HTTP_POST);
  404. p.New().set("qty[0]", 1, HTTP_POST);
  405. p.New().set("amount[0]", cost_in_cents, HTTP_POST);
  406. p.New().set("description[0]", item_name, HTTP_POST);
  407. SteamWorks::Operation &op=Steam._operations.New();
  408. op.type =PURCHASE;
  409. op.order_id=order_id;
  410. op.create(STEAM_API "/InitTxn/V0002/", p);
  411. return WAITING;
  412. }
  413. SteamWorks::RESULT SteamWorks::subscribe(ULong order_id, Int item_id, C Str &item_name, Int cost_in_cents, PERIOD period, Int frequency, C Str8 &currency)
  414. {
  415. if(!init() )return STEAM_NOT_INITIALIZED;
  416. if(!_web_api_key.is())return STEAM_NOT_SETUP;
  417. if(! appID() )return APP_ID_UNAVAILABLE;
  418. if(!userID() )return USER_ID_UNAVAILABLE;
  419. ULong unix_time=SteamUnixTime(); if(!unix_time)return STEAM_NOT_INITIALIZED; // use date from Steam in case the OS has an incorrect date set
  420. Memt<HTTPParam> p;
  421. p.New().set("key", _web_api_key, HTTP_POST);
  422. p.New().set("steamid", userID(), HTTP_POST);
  423. p.New().set("appid" , appID(), HTTP_POST);
  424. p.New().set("orderid", order_id, HTTP_POST);
  425. p.New().set("itemcount", 1, HTTP_POST);
  426. p.New().set("language", "EN", HTTP_POST);
  427. p.New().set("currency", currency, HTTP_POST);
  428. p.New().set("itemid[0]", item_id, HTTP_POST);
  429. p.New().set("qty[0]", 1, HTTP_POST);
  430. p.New().set("amount[0]", cost_in_cents, HTTP_POST);
  431. p.New().set("description[0]", item_name, HTTP_POST);
  432. p.New().set("billingtype[0]", "steam", HTTP_POST);
  433. p.New().set("frequency[0]", frequency, HTTP_POST);
  434. p.New().set("recurringamt[0]", cost_in_cents, HTTP_POST);
  435. switch(period)
  436. {
  437. case DAY : p.New().set("period[0]", "day" , HTTP_POST); unix_time+=frequency*(86400 ); break; // 86400=number of seconds in a day (60*60*24)
  438. case WEEK : p.New().set("period[0]", "week" , HTTP_POST); unix_time+=frequency*(86400* 14); break; // 86400=number of seconds in a day (60*60*24)
  439. case MONTH: p.New().set("period[0]", "month", HTTP_POST); unix_time+=frequency*(86400* 30); break; // 86400=number of seconds in a day (60*60*24), use 30 as avg number of days in a month
  440. case YEAR : p.New().set("period[0]", "year" , HTTP_POST); unix_time+=frequency*(86400*365); break; // 86400=number of seconds in a day (60*60*24)
  441. default : return ORDER_REQUEST_FAILED;
  442. }
  443. DateTime date; date.from1970s(unix_time);
  444. p.New().set("startdate[0]", TextInt(date.year, 4)+TextInt(date.month, 2)+TextInt(date.day, 2), HTTP_POST); // format is YYYYMMDD
  445. SteamWorks::Operation &op=Steam._operations.New();
  446. op.type =PURCHASE;
  447. op.order_id=order_id;
  448. op.create(STEAM_API "/InitTxn/V0002/", p);
  449. return WAITING;
  450. }
  451. SteamWorks::RESULT SteamWorks::purchaseState(ULong order_id)
  452. {
  453. if(!init() )return STEAM_NOT_INITIALIZED;
  454. if(!_web_api_key.is())return STEAM_NOT_SETUP;
  455. if(! appID() )return APP_ID_UNAVAILABLE;
  456. Memt<HTTPParam> p;
  457. p.New().set("key" , _web_api_key);
  458. p.New().set("appid" , appID() );
  459. p.New().set("orderid", order_id );
  460. SteamWorks::Operation &op=Steam._operations.New();
  461. op.type =QUERY_PURCHASE;
  462. op.order_id=order_id;
  463. op.create(STEAM_API "/QueryTxn/V0001/", p);
  464. return WAITING;
  465. }
  466. SteamWorks::RESULT SteamWorks::subscriptionState()
  467. {
  468. if(!init() )return STEAM_NOT_INITIALIZED;
  469. if(!_web_api_key.is())return STEAM_NOT_SETUP;
  470. if(! appID() )return APP_ID_UNAVAILABLE;
  471. if(!userID() )return USER_ID_UNAVAILABLE;
  472. Memt<HTTPParam> p;
  473. p.New().set("key" , _web_api_key);
  474. p.New().set("steamid", userID() );
  475. p.New().set("appid" , appID() );
  476. SteamWorks::Operation &op=Steam._operations.New();
  477. op.type =QUERY_SUBSCRIPTION;
  478. op.order_id=0;
  479. op.create(STEAM_API "/GetUserAgreementInfo/V0001/", p);
  480. return WAITING;
  481. }
  482. void SteamWorks::update()
  483. {
  484. REPA(_operations)
  485. {
  486. Operation &op=_operations[i]; if(op.state()==DWNL_ERROR || op.state()==DWNL_DONE) // have to process errors too, to report them into callback, try to read downloaded data from errors too, as they may contain some information
  487. {
  488. RESULT res;
  489. switch(op.type)
  490. {
  491. case PURCHASE : res=ORDER_REQUEST_FAILED ; break;
  492. case FINALIZE : res=ORDER_FINALIZE_FAILED ; break;
  493. case QUERY_PURCHASE : res=PURCHASE_STATUS_FAILED ; break;
  494. case QUERY_SUBSCRIPTION: res=SUBSCRIPTION_STATUS_FAILED; break;
  495. default : res=STEAM_NOT_INITIALIZED ; break;
  496. }
  497. Str error_message;
  498. Purchase purchase , * purchase_ptr=null;
  499. Subscription subscription, *subscription_ptr=null;
  500. TextData data; FileText f; data.loadJSON(f.readMem(op.data(), op.done()));
  501. #if DEBUG && 0
  502. data.save(FFirst("d:/", "txt"));
  503. #endif
  504. if(data.nodes.elms()==1)if(TextNode *response=data.nodes[0].findNode("response"))
  505. {
  506. TextNode *result=response->findNode("result");
  507. if(result && result->asText()=="OK")
  508. {
  509. TextNode *params=response->findNode("params");
  510. if(op.type==QUERY_SUBSCRIPTION) // subscription status
  511. {
  512. res=SUBSCRIPTION_STATUS_OK;
  513. if(params)if(TextNode *agreements=params->findNode("agreements"))if(agreements->nodes.elms()==1) // if this is null or it has no elements, then there are no subscriptions
  514. {
  515. subscription_ptr=&subscription;
  516. TextNode &agreement=agreements->nodes[0];
  517. if(TextNode *p=agreement.findNode("status" ))subscription.active =(p->asText()=="Active");
  518. if(TextNode *p=agreement.findNode("itemid" ))subscription.item_id = p->asInt ();
  519. if(TextNode *p=agreement.findNode("frequency" ))subscription.frequency = p->asInt ();
  520. if(TextNode *p=agreement.findNode("recurringamt"))subscription.cost_in_cents= p->asInt ();
  521. if(TextNode *p=agreement.findNode("currency" ))subscription.currency = p->asText();
  522. if(TextNode *p=agreement.findNode("timecreated" ))SetDateFromYYYYMMDD(subscription.created , p->asText());
  523. if(TextNode *p=agreement.findNode("nextpayment" ))SetDateFromYYYYMMDD(subscription.next_payment, p->asText());
  524. if(TextNode *p=agreement.findNode("lastpayment" ))SetDateFromYYYYMMDD(subscription.last_payment, p->asText()); if(!subscription.last_payment.valid())subscription.last_payment=subscription.created; // "lastpayment" value can be "NIL", so in case that happens or it wasn't specified at all, then use the 'subscription.created'
  525. if(TextNode *p=agreement.findNode("period" ))
  526. {
  527. if(p->asText()=="day" )subscription.period=DAY ;else
  528. if(p->asText()=="week" )subscription.period=WEEK ;else
  529. if(p->asText()=="month")subscription.period=MONTH;else
  530. if(p->asText()=="year" )subscription.period=YEAR ;
  531. }
  532. }
  533. }else
  534. if(params)if(TextNode *order_id=params->findNode("orderid"))if(order_id->asULong()==op.order_id)
  535. {
  536. switch(op.type)
  537. {
  538. case PURCHASE : res=ORDER_REQUEST_OK ; break;
  539. case FINALIZE : res=ORDER_FINALIZE_OK; break;
  540. case QUERY_PURCHASE:
  541. {
  542. res=PURCHASE_STATUS_OK;
  543. purchase_ptr=&purchase;
  544. if(TextNode *p=params->findNode("status" ))purchase.finalized=(p->asText ()=="Succeeded");
  545. if(TextNode *p=params->findNode("steamid" ))purchase.user_id = p->asULong();
  546. if(TextNode *p=params->findNode("currency"))purchase.currency = p->asText ();
  547. if(TextNode *p=params->findNode("country" ))purchase.country = p->asText ();
  548. if(TextNode *p=params->findNode("time" ))purchase.date.fromText(p->value.replace('T', ' ').replace('Z', '\0'));
  549. if(TextNode *items=params->findNode("items"))if(items->nodes.elms()==1)
  550. {
  551. TextNode &item=items->nodes[0];
  552. if(TextNode *itemid=item.findNode("itemid"))purchase.item_id =itemid->asInt();
  553. if(TextNode *amount=item.findNode("amount"))purchase.cost_in_cents=amount->asInt();
  554. }
  555. }break;
  556. }
  557. }
  558. }else
  559. if(TextNode *error=response->findNode("error"))
  560. {
  561. if(TextNode *errordesc=error->findNode("errordesc"))Swap(error_message, errordesc->value);
  562. /*if(TextNode *errorcode=error->findNode("errorcode"))switch(errorcode->asInt())
  563. {
  564. case
  565. }*/
  566. }
  567. goto finish;
  568. }
  569. f.rewind(); f.getAll(error_message);
  570. finish:
  571. if(auto callback=order_callback)callback(res, op.order_id, error_message, purchase_ptr, subscription_ptr);
  572. _operations.removeValid(i);
  573. }
  574. }
  575. #if SUPPORT_STEAM
  576. SteamAPI_RunCallbacks();
  577. #endif
  578. App._callbacks.add(SteamUpdate);
  579. }
  580. /******************************************************************************/
  581. // CLOUD SAVES
  582. /******************************************************************************/
  583. Long SteamWorks::cloudAvailableSize()C
  584. {
  585. #if SUPPORT_STEAM
  586. if(ISteamRemoteStorage *i=SteamRemoteStorage()){uint64 total, available; if(i->GetQuota(&total, &available))return available;}
  587. #endif
  588. return 0;
  589. }
  590. Long SteamWorks::cloudTotalSize()C
  591. {
  592. #if SUPPORT_STEAM
  593. if(ISteamRemoteStorage *i=SteamRemoteStorage()){uint64 total, available; if(i->GetQuota(&total, &available))return total;}
  594. #endif
  595. return 0;
  596. }
  597. Bool SteamWorks::cloudDel(C Str &file_name)
  598. {
  599. #if SUPPORT_STEAM
  600. if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->FileDelete(UTF8(file_name));
  601. #endif
  602. return false;
  603. }
  604. Bool SteamWorks::cloudExists(C Str &file_name)
  605. {
  606. #if SUPPORT_STEAM
  607. if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->FileExists(UTF8(file_name));
  608. #endif
  609. return false;
  610. }
  611. Long SteamWorks::cloudSize(C Str &file_name)
  612. {
  613. #if SUPPORT_STEAM
  614. if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->GetFileSize(UTF8(file_name));
  615. #endif
  616. return 0;
  617. }
  618. DateTime SteamWorks::cloudTimeUTC(C Str &file_name)
  619. {
  620. DateTime dt;
  621. #if SUPPORT_STEAM
  622. if(ISteamRemoteStorage *i=SteamRemoteStorage())
  623. if(int64 timestamp=i->GetFileTimestamp(UTF8(file_name)))
  624. return dt.from1970s(timestamp);
  625. #endif
  626. return dt.zero();
  627. }
  628. Bool SteamWorks::cloudSave(C Str &file_name, File &f, Cipher *cipher)
  629. {
  630. #if SUPPORT_STEAM
  631. if(file_name.is())if(ISteamRemoteStorage *i=SteamRemoteStorage())
  632. {
  633. Memt<Byte> data; data.setNum(f.left()); if(f.get(data.data(), data.elms()))
  634. {
  635. if(cipher)cipher->encrypt(data.data(), data.data(), data.elms(), 0);
  636. return i->FileWrite(UTF8(file_name), data.data(), data.elms());
  637. }
  638. }
  639. #endif
  640. return false;
  641. }
  642. Bool SteamWorks::cloudLoad(C Str &file_name, File &f, Bool memory, Cipher *cipher)
  643. {
  644. #if SUPPORT_STEAM
  645. if(file_name.is())if(ISteamRemoteStorage *i=SteamRemoteStorage())
  646. {
  647. Str8 name=UTF8(file_name);
  648. Long size=i->GetFileSize(name);
  649. if(size>0 || size==0 && i->FileExists(name))
  650. {
  651. if(size>0)
  652. {
  653. Memt<Byte> data; data.setNum(size);
  654. #if CLOUD_WORKAROUND
  655. REPD(attempt, 1000)
  656. #endif
  657. {
  658. Int read=i->FileRead(name, data.data(), data.elms());
  659. if( read==data.elms())
  660. {
  661. if(cipher)cipher->decrypt(data.data(), data.data(), data.elms(), 0);
  662. if(memory)f.writeMemFixed(size);
  663. if(f.put(data.data(), data.elms()))return true;
  664. #if CLOUD_WORKAROUND
  665. break;
  666. #endif
  667. }
  668. #if CLOUD_WORKAROUND
  669. //LogN(S+"failed:"+attempt+" "+read+'/'+size);
  670. Time.wait(1);
  671. #endif
  672. }
  673. }else // "size==0"
  674. {
  675. f.writeMemFixed(0);
  676. return true;
  677. }
  678. }
  679. }
  680. #endif
  681. if(memory)f.close(); return false;
  682. }
  683. Bool SteamWorks::cloudSave(C Str &file_name, CPtr data, Int size)
  684. {
  685. #if SUPPORT_STEAM
  686. if(file_name.is())if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->FileWrite(UTF8(file_name), data, size);
  687. #endif
  688. return false;
  689. }
  690. Bool SteamWorks::cloudLoad(C Str &file_name, Ptr data, Int size)
  691. {
  692. #if SUPPORT_STEAM
  693. if(file_name.is())if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->FileRead(UTF8(file_name), data, size)==size;
  694. #endif
  695. return false;
  696. }
  697. Int SteamWorks::cloudFiles()C
  698. {
  699. #if SUPPORT_STEAM
  700. if(ISteamRemoteStorage *i=SteamRemoteStorage())return i->GetFileCount();
  701. #endif
  702. return 0;
  703. }
  704. Bool SteamWorks::cloudFile(Int file_index, Str &name, Long &size)C
  705. {
  706. #if SUPPORT_STEAM
  707. if(file_index>=0)if(ISteamRemoteStorage *i=SteamRemoteStorage())
  708. {
  709. int32 file_size; CChar8 *file_name=i->GetFileNameAndSize(file_index, &file_size);
  710. if(Is(file_name))
  711. {
  712. name=FromUTF8(file_name);
  713. size=file_size;
  714. return true;
  715. }
  716. }
  717. #endif
  718. name.clear(); size=0; return false;
  719. }
  720. /******************************************************************************/
  721. }
  722. /******************************************************************************/