SteamworksUGC.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. //
  2. // Copyright 2020 Electronic Arts Inc.
  3. //
  4. // The Command & Conquer Map Editor and corresponding source code is free
  5. // software: you can redistribute it and/or modify it under the terms of
  6. // the GNU General Public License as published by the Free Software Foundation,
  7. // either version 3 of the License, or (at your option) any later version.
  8. // The Command & Conquer Map Editor and corresponding source code is distributed
  9. // in the hope that it will be useful, but with permitted additional restrictions
  10. // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
  11. // distributed with this program. You should have received a copy of the
  12. // GNU General Public License along with permitted additional restrictions
  13. // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
  14. using MobiusEditor.Model;
  15. using Steamworks;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Text;
  21. using System.Windows.Forms;
  22. namespace MobiusEditor.Utility
  23. {
  24. public interface ISteamworksOperation : IDisposable
  25. {
  26. bool Done { get; }
  27. bool Failed { get; }
  28. string Status { get; }
  29. void OnSuccess();
  30. void OnFailed();
  31. }
  32. public class SteamworksUGCPublishOperation : ISteamworksOperation
  33. {
  34. private readonly string ugcPath;
  35. private readonly IList<string> tags;
  36. private readonly Action onSuccess;
  37. private readonly Action<string> onFailed;
  38. private CallResult<CreateItemResult_t> createItemResult;
  39. private CallResult<SubmitItemUpdateResult_t> submitItemUpdateResult;
  40. private SteamSection steamSection = new SteamSection();
  41. public bool Done => !(createItemResult?.IsActive() ?? false) && !(submitItemUpdateResult?.IsActive() ?? false);
  42. public bool Failed { get; private set; }
  43. public string Status { get; private set; }
  44. public SteamworksUGCPublishOperation(string ugcPath, SteamSection steamSection, IList<string> tags, Action onSuccess, Action<string> onFailed)
  45. {
  46. this.ugcPath = ugcPath;
  47. this.steamSection = steamSection;
  48. this.tags = tags;
  49. this.onSuccess = onSuccess;
  50. this.onFailed = onFailed;
  51. if (steamSection.PublishedFileId == PublishedFileId_t.Invalid.m_PublishedFileId)
  52. {
  53. CreateUGCItem();
  54. }
  55. else
  56. {
  57. UpdateUGCItem();
  58. }
  59. Status = "Publishing UGC...";
  60. }
  61. public void OnSuccess() => onSuccess();
  62. public void OnFailed() => onFailed(Status);
  63. private void CreateUGCItem()
  64. {
  65. var steamAPICall = SteamUGC.CreateItem(SteamUtils.GetAppID(), EWorkshopFileType.k_EWorkshopFileTypeCommunity);
  66. if (steamAPICall == SteamAPICall_t.Invalid)
  67. {
  68. Failed = true;
  69. Status = "Publishing failed.";
  70. return;
  71. }
  72. createItemResult = CallResult<CreateItemResult_t>.Create(OnCreateItemResult);
  73. createItemResult.Set(steamAPICall);
  74. }
  75. private void UpdateUGCItem()
  76. {
  77. var updateHandle = SteamUGC.StartItemUpdate(SteamUtils.GetAppID(), new PublishedFileId_t(steamSection.PublishedFileId));
  78. if (updateHandle == UGCUpdateHandle_t.Invalid)
  79. {
  80. Failed = true;
  81. Status = "Publishing failed.";
  82. return;
  83. }
  84. bool success = true;
  85. success = success && SteamUGC.SetItemContent(updateHandle, ugcPath);
  86. success = success && SteamUGC.SetItemPreview(updateHandle, steamSection.PreviewFile);
  87. success = success && SteamUGC.SetItemUpdateLanguage(updateHandle, "English");
  88. success = success && SteamUGC.SetItemTitle(updateHandle, steamSection.Title);
  89. success = success && SteamUGC.SetItemDescription(updateHandle, steamSection.Description);
  90. success = success && SteamUGC.SetItemVisibility(updateHandle, steamSection.Visibility);
  91. success = success && SteamUGC.SetItemTags(updateHandle, tags);
  92. if (!success)
  93. {
  94. Failed = true;
  95. Status = "Publishing failed.";
  96. return;
  97. }
  98. var steamAPICall = SteamUGC.SubmitItemUpdate(updateHandle, "");
  99. if (steamAPICall == SteamAPICall_t.Invalid)
  100. {
  101. Failed = true;
  102. Status = "Publishing failed.";
  103. return;
  104. }
  105. submitItemUpdateResult = CallResult<SubmitItemUpdateResult_t>.Create(OnSubmitItemUpdateResult);
  106. submitItemUpdateResult.Set(steamAPICall);
  107. }
  108. private void OnCreateItemResult(CreateItemResult_t callback, bool ioFailure)
  109. {
  110. if (ioFailure)
  111. {
  112. Failed = true;
  113. Status = "Publishing failed.";
  114. return;
  115. }
  116. switch (callback.m_eResult)
  117. {
  118. case EResult.k_EResultOK:
  119. steamSection.PublishedFileId = callback.m_nPublishedFileId.m_PublishedFileId;
  120. UpdateUGCItem();
  121. break;
  122. case EResult.k_EResultFileNotFound:
  123. Failed = true;
  124. Status = "UGC not found.";
  125. break;
  126. case EResult.k_EResultNotLoggedOn:
  127. Failed = true;
  128. Status = "Not logged on.";
  129. break;
  130. default:
  131. Failed = true;
  132. Status = "Publishing failed.";
  133. break;
  134. }
  135. }
  136. private void OnSubmitItemUpdateResult(SubmitItemUpdateResult_t callback, bool ioFailure)
  137. {
  138. if (ioFailure)
  139. {
  140. Failed = true;
  141. Status = "Publishing failed.";
  142. return;
  143. }
  144. switch (callback.m_eResult)
  145. {
  146. case EResult.k_EResultOK:
  147. Status = "Done.";
  148. steamSection.PublishedFileId = callback.m_nPublishedFileId.m_PublishedFileId;
  149. break;
  150. case EResult.k_EResultFileNotFound:
  151. Failed = true;
  152. Status = "UGC not found.";
  153. break;
  154. case EResult.k_EResultLimitExceeded:
  155. Failed = true;
  156. Status = "Size limit exceeded.";
  157. break;
  158. default:
  159. Failed = true;
  160. Status = string.Format("Publishing failed. ({0})", callback.m_eResult);
  161. break;
  162. }
  163. }
  164. #region IDisposable Support
  165. private bool disposedValue = false;
  166. protected virtual void Dispose(bool disposing)
  167. {
  168. if (!disposedValue)
  169. {
  170. if (disposing)
  171. {
  172. createItemResult?.Dispose();
  173. submitItemUpdateResult?.Dispose();
  174. }
  175. disposedValue = true;
  176. }
  177. }
  178. public void Dispose()
  179. {
  180. Dispose(true);
  181. }
  182. #endregion
  183. }
  184. public static class SteamworksUGC
  185. {
  186. public static bool IsInit { get; private set; }
  187. public static ISteamworksOperation CurrentOperation { get; private set; }
  188. public static string WorkshopURL
  189. {
  190. get
  191. {
  192. var app_id = IsInit ? SteamUtils.GetAppID() : AppId_t.Invalid;
  193. if (app_id == AppId_t.Invalid)
  194. {
  195. return string.Empty;
  196. }
  197. return string.Format("http://steamcommunity.com/app/{0}/workshop/", app_id.ToString());
  198. }
  199. }
  200. public static bool IsSteamBuild => File.Exists("steam_appid.txt");
  201. private static Callback<GameLobbyJoinRequested_t> GameLobbyJoinRequested;
  202. public static bool Init()
  203. {
  204. if (IsInit)
  205. {
  206. return true;
  207. }
  208. if (!IsSteamBuild)
  209. {
  210. return false;
  211. }
  212. if (!Packsize.Test())
  213. {
  214. return false;
  215. }
  216. if (!DllCheck.Test())
  217. {
  218. return false;
  219. }
  220. if (!SteamAPI.Init())
  221. {
  222. return false;
  223. }
  224. SteamClient.SetWarningMessageHook(new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook));
  225. GameLobbyJoinRequested = Callback<GameLobbyJoinRequested_t>.Create(OnGameLobbyJoinRequested);
  226. IsInit = true;
  227. return IsInit;
  228. }
  229. public static void Shutdown()
  230. {
  231. if (IsInit)
  232. {
  233. GameLobbyJoinRequested?.Dispose();
  234. GameLobbyJoinRequested = null;
  235. CurrentOperation?.Dispose();
  236. CurrentOperation = null;
  237. SteamAPI.Shutdown();
  238. IsInit = false;
  239. }
  240. }
  241. public static void Service()
  242. {
  243. SteamAPI.RunCallbacks();
  244. if (CurrentOperation?.Done ?? false)
  245. {
  246. if (CurrentOperation.Failed)
  247. {
  248. CurrentOperation.OnFailed();
  249. }
  250. else
  251. {
  252. CurrentOperation.OnSuccess();
  253. }
  254. CurrentOperation.Dispose();
  255. CurrentOperation = null;
  256. }
  257. }
  258. public static bool PublishUGC(string ugcPath, SteamSection steamSection, IList<string> tags, Action onSuccess, Action<string> onFailed)
  259. {
  260. if (CurrentOperation != null)
  261. {
  262. return false;
  263. }
  264. CurrentOperation = new SteamworksUGCPublishOperation(ugcPath, steamSection, tags, onSuccess, onFailed);
  265. return true;
  266. }
  267. private static void SteamAPIDebugTextHook(int nSeverity, StringBuilder pchDebugText)
  268. {
  269. Debug.WriteLine(pchDebugText);
  270. }
  271. private static void OnGameLobbyJoinRequested(GameLobbyJoinRequested_t data)
  272. {
  273. MessageBox.Show("You cannot accept an invitation to a multiplayer game while using the map editor.", "Steam", MessageBoxButtons.OK, MessageBoxIcon.Information);
  274. }
  275. }
  276. }