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