├── .gitignore ├── DuckGame.Mod.mm.sln ├── DuckGame.Mod.mm ├── DuckGame.Mod.mm.csproj ├── Properties │ └── AssemblyInfo.cs └── src │ ├── patch_DuckFile.cs │ ├── patch_ModLoader.cs │ └── patch_Program.cs ├── LICENSE ├── README.md ├── Steam ├── Properties │ └── AssemblyInfo.cs ├── README.md ├── Steam.csproj └── src │ ├── FriendRelationship.cs │ ├── ItemUpdateStatus.cs │ ├── Lobby.cs │ ├── Lock.cs │ ├── OfflineSelfUser.cs │ ├── P2PDataSendType.cs │ ├── RemoteStoragePublishedFileVisibility.cs │ ├── SharpSteam.cs │ ├── Steam.cs │ ├── SteamLobbyComparison.cs │ ├── SteamLobbyJoinResult.cs │ ├── SteamLobbyType.cs │ ├── SteamLobbyUserStatusFlags.cs │ ├── SteamPacket.cs │ ├── SteamResult.cs │ ├── SteamUserState.cs │ ├── TransferProgress.cs │ ├── User.cs │ ├── UserInfo.cs │ ├── WorkshopItem.cs │ ├── WorkshopItemData.cs │ ├── WorkshopItemState.cs │ ├── WorkshopList.cs │ ├── WorkshopQueryAll.cs │ ├── WorkshopQueryBase.cs │ ├── WorkshopQueryData.cs │ ├── WorkshopQueryFileDetails.cs │ ├── WorkshopQueryFilterOrder.cs │ ├── WorkshopQueryResult.cs │ ├── WorkshopQueryResultAdditionalPreview.cs │ ├── WorkshopQueryResultDetails.cs │ ├── WorkshopQueryUGC.cs │ ├── WorkshopQueryUser.cs │ ├── WorkshopResultStatistic.cs │ ├── WorkshopSortOrder.cs │ ├── WorkshopType.cs │ └── _.cs ├── Steamworks.NET ├── CSteamworks.bundle │ └── Contents │ │ ├── Info.plist │ │ └── MacOS │ │ ├── CSteamworks │ │ └── libsteam_api.dylib ├── CSteamworks.dll ├── Steamworks.NET-linux.dll ├── Steamworks.NET-stubbed.dll ├── Steamworks.NET.dll ├── libCSteamworks.so ├── libsteam_api.so └── steam_api64.dll └── libs ├── DuckGame.exe ├── FNA.dll ├── README.md └── XnaToFna.exe /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | # [Xx]64/ 19 | # [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | #*.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuckGame.Mod.mm", "DuckGame.Mod.mm\DuckGame.Mod.mm.csproj", "{73E9C6D2-D61F-4956-81BC-A8CA9A292AB8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steam", "Steam\Steam.csproj", "{D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {73E9C6D2-D61F-4956-81BC-A8CA9A292AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {73E9C6D2-D61F-4956-81BC-A8CA9A292AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {73E9C6D2-D61F-4956-81BC-A8CA9A292AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {73E9C6D2-D61F-4956-81BC-A8CA9A292AB8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm/DuckGame.Mod.mm.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {73E9C6D2-D61F-4956-81BC-A8CA9A292AB8} 8 | Library 9 | Properties 10 | DuckGame 11 | DuckGame.Mod.mm 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | true 35 | false 36 | 37 | 38 | 39 | ..\libs\DuckGame.exe 40 | 41 | 42 | ..\libs\FNA.dll 43 | 44 | 45 | 46 | ..\libs\XnaToFna.exe 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DuckGame.Mod.mm")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DuckGame.Mod.mm")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("73e9c6d2-d61f-4956-81bc-a8ca9a292ab8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm/src/patch_DuckFile.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it 2 | using MonoMod; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DuckGame { 11 | class patch_DuckFile { 12 | 13 | [MonoModIgnore] 14 | private static string _saveRoot; 15 | [MonoModIgnore] 16 | private static string _saveDirectory; 17 | [MonoModIgnore] 18 | private static List _allPaths; 19 | 20 | public static extern void orig_Initialize(); 21 | public static void Initialize() { 22 | orig_Initialize(); 23 | string prefix = _saveRoot + _saveDirectory; 24 | foreach (string path in _allPaths) { 25 | if (path.StartsWith(prefix)) 26 | Directory.CreateDirectory(path); 27 | else 28 | Directory.CreateDirectory(prefix + path); 29 | } 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm/src/patch_ModLoader.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it 2 | using MonoMod; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DuckGame { 11 | class patch_ModLoader { 12 | 13 | [MonoModIgnore] 14 | internal static string modHash { get; private set; } 15 | 16 | internal static void DefaultModHash() { 17 | if (modHash == null) 18 | modHash = "nomods"; 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DuckGame.Mod.mm/src/patch_Program.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it 2 | 3 | using MonoMod; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DuckGame { 14 | internal static class patch_Program { 15 | 16 | public static void LogLine(string line) { 17 | Console.WriteLine(line); 18 | } 19 | 20 | public static void WriteToLog(string s) { 21 | Console.Error.WriteLine(s); 22 | } 23 | 24 | private static extern void orig_UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e); 25 | private static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e) { 26 | LogDetailed((Exception) e.ExceptionObject); 27 | orig_UnhandledExceptionTrapper(sender, e); 28 | } 29 | 30 | public static void LogDetailed(Exception e, string tag = null) { 31 | for (Exception e_ = e; e_ != null; e_ = e_.InnerException) { 32 | Console.WriteLine(e_.GetType().FullName + ": " + e_.Message + "\n" + e_.StackTrace); 33 | if (e_ is ReflectionTypeLoadException) { 34 | ReflectionTypeLoadException rtle = (ReflectionTypeLoadException) e_; 35 | for (int i = 0; i < rtle.Types.Length; i++) { 36 | Console.WriteLine("ReflectionTypeLoadException.Types[" + i + "]: " + rtle.Types[i]); 37 | } 38 | for (int i = 0; i < rtle.LoaderExceptions.Length; i++) { 39 | LogDetailed(rtle.LoaderExceptions[i], tag + (tag == null ? "" : ", ") + "rtle:" + i); 40 | } 41 | } 42 | if (e_ is TypeLoadException) { 43 | Console.WriteLine("TypeLoadException.TypeName: " + ((TypeLoadException) e_).TypeName); 44 | } 45 | if (e_ is BadImageFormatException) { 46 | Console.WriteLine("BadImageFormatException.FileName: " + ((BadImageFormatException) e_).FileName); 47 | } 48 | } 49 | } 50 | 51 | public static void MakeNetLog() { 52 | } 53 | 54 | [MonoModLinkTo("DuckGame.ModLoader", "System.Void set_modHash(System.String)")] 55 | private static void _ModLoader_set_modHash(string value) { 56 | } 57 | 58 | public static extern void orig_Main(string[] args); 59 | [STAThread] 60 | public static void Main(string[] args) { 61 | bool nothreading = true; 62 | bool nomods = true; 63 | 64 | Queue argq = new Queue(args); 65 | while (argq.Count > 0) { 66 | string arg = argq.Dequeue(); 67 | if (arg == "-debug") 68 | Debugger.Launch(); 69 | else if (arg == "-yesthreading") 70 | nothreading = false; 71 | else if (arg == "-yesmods") 72 | nomods = false; 73 | } 74 | 75 | List argl = new List(args); 76 | argl.Add("<"); 77 | 78 | if (nothreading) 79 | argl.Add("-nothreading"); 80 | 81 | if (nomods) 82 | argl.Add("-nomods"); 83 | 84 | argl.Add(">"); 85 | 86 | args = argl.ToArray(); 87 | 88 | // Parse arguments again... 89 | argq = new Queue(args); 90 | while (argq.Count > 0) { 91 | string arg = argq.Dequeue(); 92 | if (arg == "-nomods") 93 | // MonoMain.moddingEnabled = false skips ManagedContent.InitializeMods. 94 | // InitializeMods calls ModLoader.LoadMods. 95 | // LoadMods sets ModLoader.modHash. 96 | // -nomods thus leaves ModLoader.modHash == null, which causes issues. 97 | patch_ModLoader.DefaultModHash(); 98 | } 99 | 100 | orig_Main(args); 101 | } 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Maik Macho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Duck Game Linux FNA patches 2 | ### Because Duck Game forgets to mkdir and wine is slow 3 | ##### MIT-licensed 4 | ---- 5 | 6 | [**Feel free to discuss this on Reddit!**](https://www.reddit.com/r/linux_gaming/comments/6zqrrx/duckgamelinux_fna_custom_steamworks_bindings_some/) 7 | [Screenshots](https://twitter.com/0x0ade/status/907745108010946560), [GamingOnLinux.com article](https://www.gamingonlinux.com/articles/want-to-play-duck-game-on-linux-well-its-possible-thanks-to-xnatofna.10339) 8 | 0x0ade on [**Twitter**](https://www.twitter.com/0x0ade), [**Patreon**](https://www.patreon.com/0x0ade) 9 | 10 | 11 | [**You can support me on Patreon!**](https://www.patreon.com/0x0ade) 12 | This project wouldn't be possible without the support from: 13 | * Ethan Lee: Thank you for creating FNA! 14 | * Artus Elias Meyer-Toms, Renaud Bedard and Ryan Kistner: I wouldn't be able to get my hands on the game without your support! 15 | 16 | ### Usage instructions: 17 | **Preparations:** 18 | * Get yourself a fresh copy of Duck Game, f.e. via Steam... through Wine... or from a friend with Windows. 19 | * Install `mono-complete` and `libcurl3:i386` and `ffmpeg` (or matching) via your package manager. 20 | * `ffmpeg`, not "`libav`" / `avconv`. 21 | 22 | **Installing / updating:** 23 | * Download [**the latest released DuckGame-Linux-Complete.zip**](https://github.com/0x0ade/DuckGame-Linux/releases) 24 | * Extract the .zip into the Duck Game directory. Overwrite the original files when extracting. `XnaToFna.exe`, and `DuckGame.exe` should be next to each other; The old `Steam.dll` should be replaced by the one in the .zip. 25 | * Open terminal in Duck Game directory, run `chmod a+x ./mod.sh; ./mod.sh` 26 | * `mod.sh` creates a backup of important files in an `orig` subdirectory, which XnaToFna needs. It also gives all files in the directory read-write permissions for all users, otherwise both XnaToFna and MonoMod will fail. 27 | * Run `mono DuckGame.exe` OR Launch the game via Steam (add `DuckGame.sh` to your library as "non-Steam game"). 28 | * Be a duck with a gun! 29 | 30 | The game stores its save data in `~/DuckGame`, which is technically the same as on Windows. 31 | 32 | ### Current collection of patches: 33 | * [XnaToFna](https://github.com/0x0ade/XnaToFna) gets the game running using [FNA](https://fna-xna.github.io/) instead of XNA. Thanks to [flibitijibibo](https://www.patreon.com/flibitijibibo) for FNA. Without him this wouldn't be possible! 34 | * [Non-mixed-mode Steam.dll "proxy" to Steamworks.NET](https://github.com/0x0ade/DuckGame-Linux/tree/master/Steam) - this allows you to use Steam functionality natively... although it still contains a few holes. Working on it! 35 | * More verbose fatal error logging that help you when patching the game. 36 | * Create missing directories automatically. Does Windows just implicitly create the directories?! 37 | * Automatically pass -nothreading because it's faster. 38 | * Automatically pass -nomods because the mods would need to be relinked to FNA. This doesn't happen automagically yet. 39 | * Fix `ModLoader.modHash == null`, not `"nomods"` when `-nomods` is passed. This also affects vanilla Duck Game and can kill Steam. 40 | 41 | If for whatever reason something doesn't work, please create an issue on GitHub. I want this to work for everyone! 42 | -------------------------------------------------------------------------------- /Steam/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Versioning; 5 | using System.Security; 6 | using System.Security.Permissions; 7 | 8 | [assembly: AssemblyTitle("")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | [assembly: Guid("d63ad3fc-dcf0-4db6-b944-5d828a10ccb8")] 18 | 19 | [assembly: AssemblyVersion("0.0.0.0")] 20 | 21 | [assembly: SecurityRules(SecurityRuleSet.Level1)] 22 | #pragma warning disable CS0618 // RequestMinimum is obsolete 23 | [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] 24 | #pragma warning restore CS0618 25 | -------------------------------------------------------------------------------- /Steam/README.md: -------------------------------------------------------------------------------- 1 | # Duck Game `Steam.dll` for Linux 2 | ### Proxy `Steam.dll` into [Steamworks.NET](https://github.com/rlabrecque/Steamworks.NET) 3 | ##### MIT-licensed 4 | ---- 5 | 6 | ### But... why? 7 | When trying to run Duck Game on Linux natively with [XnaToFna](https://github.com/0x0ade/XnaToFna), Duck Game will try to access the Steam API (Steamworks) through its `Steam.dll`. 8 | The only issue with that is... that it's a mixed-mode assembly, thus restricted to Windows. 9 | Furthermore, it's restricted to x86, which isn't that horrible for XNA games, but FNA supports making use of all 64 bits in your CPU(tm). 10 | 11 | This `Steam.dll` replacement just tries to "proxy" any `Steam.dll` calls to Steamworks.NET, which supports all Steam platforms: x86 and x64 Windows, Linux and macOS. 12 | 13 | ### Notes: 14 | * When using XnaToFna, you'll need to pass `-nothreading -nomods`. 15 | * On Linux, Duck Game will complain about missing paths as it forgets to create the save profile directories. Currently working on a separate fix. 16 | * _It's not a complete replacement yet_, it'll be outdated with future Duck Game updates and I'm stuck on a 1G connection right now, which makes testing this a pain in the a~ 17 | * You need to copy the proper files from the Steamworks.NET directory to the Duck Game directory. For Linux, you need to rename `Steamworks.NET-linux.dll` to just `Steamworks.NET.dll`. The x64 variants of steam_api and CSteamworks are supplied. 18 | * You should be able to use a stubbed Steamworks.NET to get Duck Game running "DRM-free" on non-Windows platforms. I've uploaded one for FNADroid, it should just work(tm): https://github.com/0x0ade/FNADroid/blob/master/FNADroid/libs/managed/Steamworks.NET.dll 19 | -------------------------------------------------------------------------------- /Steam/Steam.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | {D63AD3FC-DCF0-4DB6-B944-5D828A10CCB8} 8 | Library 9 | Properties 10 | Steam 11 | Steam 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | bin\Debug\ 18 | true 19 | 20 | 21 | AnyCPU 22 | bin\Release\ 23 | true 24 | 25 | 26 | 27 | ..\Steamworks.NET\Steamworks.NET.dll 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Steam/src/FriendRelationship.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum FriendRelationship { 4 | Max = 8, 5 | Suggested = 7, 6 | IgnoredFriend = 6, 7 | Ignored = 5, 8 | RequestInitiator = 4, 9 | Friend = 3, 10 | RequestRecipient = 2, 11 | Blocked = 1, 12 | None = 0 13 | } 14 | -------------------------------------------------------------------------------- /Steam/src/ItemUpdateStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum ItemUpdateStatus { 4 | CommittingChanges = 5, 5 | UploadingPreviewFile = 4, 6 | UploadingContent = 3, 7 | PreparingContent = 2, 8 | PreparingConfig = 1, 9 | Invalid = 0 10 | } 11 | -------------------------------------------------------------------------------- /Steam/src/Lobby.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.ExceptionServices; 5 | 6 | public class Lobby : IDisposable { 7 | 8 | public int randomID; // Duck Game uses this... BUT IT ISN'T USED ANYWHERE IN Steam.dll?! 9 | 10 | public delegate void UserStatusChangeDelegate(User user, SteamLobbyUserStatusFlags flags, User responsibleUser); 11 | public event UserStatusChangeDelegate UserStatusChange; 12 | 13 | private CSteamID _id; 14 | public ulong id => _id.m_SteamID; 15 | 16 | public SteamLobbyJoinResult joinResult { get; private set; } 17 | 18 | public unsafe User owner { 19 | get { 20 | if (id != 0) { 21 | return User.GetUser(SteamMatchmaking.GetLobbyOwner(_id)); 22 | } 23 | return null; 24 | } 25 | } 26 | 27 | public unsafe string name { 28 | get { 29 | if (id == 0) { 30 | return ""; 31 | } 32 | return SteamMatchmaking.GetLobbyData(_id, "name"); 33 | } 34 | set { 35 | if (id != 0) { 36 | SteamMatchmaking.SetLobbyData(_id, "name", value); 37 | } 38 | } 39 | } 40 | 41 | private bool _joinable; 42 | public unsafe bool joinable { 43 | get { 44 | return id != 0 && _joinable; 45 | } 46 | set { 47 | if (id != 0) { 48 | SteamMatchmaking.SetLobbyJoinable(_id, value); 49 | _joinable = value; 50 | } 51 | } 52 | } 53 | 54 | private SteamLobbyType _type; 55 | public unsafe SteamLobbyType type { 56 | get { 57 | return _type; 58 | } 59 | set { 60 | if (id != 0) { 61 | SteamMatchmaking.SetLobbyType(_id, (ELobbyType) value); 62 | // This isn't set by the original Steam.dll... 63 | _type = value; 64 | } 65 | } 66 | } 67 | 68 | private int _maxMembers; 69 | public unsafe int maxMembers { 70 | get { 71 | if (id == 0) { 72 | return 0; 73 | } 74 | return _maxMembers = SteamMatchmaking.GetLobbyMemberLimit(_id); 75 | } 76 | set { 77 | if (id != 0) { 78 | SteamMatchmaking.SetLobbyMemberLimit(_id, _maxMembers = value); 79 | } 80 | } 81 | } 82 | 83 | public unsafe List users { 84 | get { 85 | if (id == 0) { 86 | return new List(); 87 | } 88 | 89 | return _.GetList(SteamMatchmaking.GetNumLobbyMembers(_id), i => User.GetUser(SteamMatchmaking.GetLobbyMemberByIndex(_id, i))); 90 | } 91 | } 92 | 93 | public bool processing { get; private set; } 94 | 95 | public Lobby(ulong lobbyID) 96 | : this(new CSteamID(lobbyID)) { 97 | } 98 | 99 | internal Lobby(CSteamID lobbyID) { 100 | _type = SteamLobbyType.FriendsOnly; 101 | processing = true; 102 | _id = lobbyID; 103 | _joinable = true; 104 | } 105 | 106 | public Lobby(SteamLobbyType lobbyTypeVal, int maxMembersVal) { 107 | _type = lobbyTypeVal; 108 | // This is set to this.maxMembers by the original Steam.dll... 109 | _maxMembers = maxMembersVal; 110 | processing = true; 111 | _id = new CSteamID(); 112 | _joinable = true; 113 | } 114 | 115 | public void OnProcessingComplete(ulong idVal, SteamLobbyJoinResult result) { 116 | _id = new CSteamID(idVal); 117 | joinResult = result; 118 | processing = false; 119 | } 120 | 121 | public void OnUserStatusChange(User user, SteamLobbyUserStatusFlags flags, User responsibleUser) { 122 | UserStatusChange?.Invoke(user, flags, responsibleUser); 123 | } 124 | 125 | public unsafe void SetLobbyData(string name, string value) { 126 | if (id != 0) 127 | SteamMatchmaking.SetLobbyData(_id, name, value); 128 | } 129 | 130 | public unsafe string GetLobbyData(string name) { 131 | if (id == 0) 132 | return ""; 133 | return SteamMatchmaking.GetLobbyData(_id, name); 134 | } 135 | 136 | [HandleProcessCorruptedStateExceptions] 137 | protected virtual void Dispose(bool flag) { 138 | } 139 | 140 | public void Dispose() { 141 | Dispose(true); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /Steam/src/Lock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | internal class Lock : IDisposable { 5 | 6 | private object m_pObject; 7 | 8 | public Lock(object pObject) { 9 | m_pObject = pObject; 10 | Monitor.Enter(m_pObject); 11 | } 12 | 13 | ~Lock() { 14 | Dispose(true); 15 | } 16 | 17 | protected virtual void Dispose(bool disposing) { 18 | if (disposing) 19 | Monitor.Exit(m_pObject); 20 | } 21 | 22 | public void Dispose() { 23 | Dispose(true); 24 | GC.SuppressFinalize(this); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Steam/src/OfflineSelfUser.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.ExceptionServices; 5 | 6 | // THIS TYPE DOESN'T EXIST IN THE ORIGINAL Steam.dll! 7 | public class OfflineSelfUser : User { 8 | 9 | public override ulong id => 0; 10 | 11 | public override unsafe string name => "UNKNOWN"; 12 | 13 | public override unsafe byte[] avatarSmall => null; 14 | 15 | public override unsafe byte[] avatarMedium => null; 16 | 17 | public override unsafe bool inGame => true; 18 | 19 | public override unsafe bool inCurrentGame => true; 20 | 21 | protected override unsafe bool inLobby => false; 22 | 23 | public override unsafe bool inCurrentLobby => false; 24 | 25 | public override unsafe SteamUserState state => SteamUserState.Offline; 26 | 27 | public override unsafe FriendRelationship relationship => FriendRelationship.None; 28 | 29 | internal OfflineSelfUser() 30 | : base(new CSteamID()) { 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Steam/src/P2PDataSendType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum P2PDataSendType { 4 | ReliableWithBuffering = 3, 5 | Reliable = 2, 6 | UnreliableNoDelay = 1, 7 | Unreliable = 0 8 | } 9 | -------------------------------------------------------------------------------- /Steam/src/RemoteStoragePublishedFileVisibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum RemoteStoragePublishedFileVisibility { 4 | FriendsOnly = 1, 5 | Public = 0, 6 | Private = 2 7 | } 8 | -------------------------------------------------------------------------------- /Steam/src/SharpSteam.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Text; 4 | 5 | public class SharpSteam : IDisposable { 6 | 7 | public unsafe SharpSteam() { 8 | } 9 | 10 | public bool Init() { 11 | return SteamAPI.Init(); 12 | } 13 | 14 | public void Shutdown() { 15 | SteamAPI.Shutdown(); 16 | } 17 | 18 | public void SetTempDirectory(string dir) { 19 | // _tempDirectory was only used in FormatName..?! 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public unsafe bool SetAchievement(string name) { 24 | return SteamUserStats.SetAchievement(name); 25 | } 26 | 27 | public unsafe bool StoreStats() { 28 | return SteamUserStats.StoreStats(); 29 | } 30 | 31 | public unsafe bool SetStat(string name, int value) { 32 | return SteamUserStats.SetStat(name, value); 33 | } 34 | 35 | public unsafe bool IndicateAchievementProgress(string name, int cur, int maxval) { 36 | return SteamUserStats.IndicateAchievementProgress(name, (uint) cur, (uint) maxval); 37 | } 38 | 39 | public unsafe string FormatName(string name) { 40 | // Invalid signature anyway... 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public unsafe bool FileExists(string name) { 45 | return SteamRemoteStorage.FileExists(name); 46 | } 47 | 48 | public unsafe bool FileWrite(string managedname, string manageddata) { 49 | byte[] data = Encoding.ASCII.GetBytes(manageddata); 50 | return SteamRemoteStorage.FileWrite(managedname, data, data.Length); 51 | } 52 | 53 | public unsafe string FileRead(string managedname) { 54 | int size = SteamRemoteStorage.GetFileSize(managedname); 55 | byte[] data = new byte[size]; 56 | int rv = SteamRemoteStorage.FileRead(managedname, data, size); 57 | return Encoding.ASCII.GetString(data, 0, size); 58 | } 59 | 60 | public unsafe string GetUsername() { 61 | return SteamFriends.GetPersonaName(); 62 | } 63 | 64 | public unsafe bool LoadLeaderboard(string board, string type, int minRange, int maxRange) { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public unsafe bool DownloadedLeaderboard(string board) { 69 | throw new NotImplementedException(); 70 | } 71 | 72 | public unsafe bool UploadScoreFailed() { 73 | throw new NotImplementedException(); 74 | } 75 | 76 | public unsafe bool UploadScoreComplete() { 77 | throw new NotImplementedException(); 78 | } 79 | 80 | public unsafe bool DownloadScoresFailed() { 81 | throw new NotImplementedException(); 82 | } 83 | 84 | public unsafe string GetLeaderboardName(string board, int index) { 85 | throw new NotImplementedException(); 86 | // SteamUserStats.GetLeaderboardName(/*... this requires a SteamLeaderboard_t!*/); 87 | } 88 | 89 | public unsafe int GetLeaderboardScore(string board, int index) { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public unsafe int GetLeaderboardPosition(string board, int index) { 94 | throw new NotImplementedException(); 95 | } 96 | 97 | public unsafe int GetLeaderboardDetails(string board, int index) { 98 | throw new NotImplementedException(); 99 | } 100 | 101 | public unsafe bool UploadScore(string board, int score, int details) { 102 | throw new NotImplementedException(); 103 | // SteamUserStats.UploadLeaderboardScore(/*... this requires a SteamLeaderboard_t!*/, ELeaderboardUploadScoreMethod.k_ELeaderboardUploadScoreMethodKeepBest, score, /*???*/, /*???*/); 104 | } 105 | 106 | public void RunCallbacks() { 107 | SteamAPI.RunCallbacks(); 108 | } 109 | 110 | public unsafe void ThrowException(string message, string error) { 111 | throw new NotSupportedException(); 112 | // NativeMethods isn't exposed, but those would need to be called: 113 | // Steamworks.NativeMethods.SteamAPI_SetMiniDumpComment 114 | // Steamworks.NativeMethods.SteamAPI_WriteMiniDump 115 | } 116 | 117 | public unsafe void OpenInviteDialogue() { 118 | throw new NotImplementedException(); 119 | // SteamFriends.ActivateGameOverlayInviteDialog(/*requires current lobby*/); 120 | } 121 | 122 | public void CreateLobby(int lobbyType, int maxMembers) { 123 | SteamMatchmaking.CreateLobby((ELobbyType) lobbyType, maxMembers); 124 | } 125 | 126 | public unsafe bool CreateLobbyComplete() { 127 | throw new NotImplementedException(); 128 | } 129 | 130 | public unsafe ulong GetCreatedLobbyID() { 131 | throw new NotImplementedException(); 132 | } 133 | 134 | protected unsafe virtual void Dispose(bool flag) { 135 | } 136 | 137 | public void Dispose() { 138 | Dispose(true); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /Steam/src/Steam.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Steamworks; 4 | 5 | public class Steam : IDisposable { 6 | 7 | private static Dictionary _callResults = new Dictionary(); 8 | 9 | private static CallResult GetCallResult() => (CallResult) _callResults[typeof(T)]; 10 | private static void SetCallResult(CallResult.APIDispatchDelegate func) => _callResults.Add(typeof(T), new CallResult(func)); 11 | private static void SetCallResult(SteamAPICall_t call) => ((CallResult) _callResults[typeof(T)]).Set(call); 12 | 13 | private static bool _initialized; 14 | private static bool _offline; 15 | 16 | private static WorkshopItem _pendingItem; 17 | 18 | private static WorkshopItem _pendingItemDownload; 19 | 20 | // private static bool _waitingForCurrentStats; // unused 21 | 22 | private readonly static int kPacketBufferSize = 2048; // originally not readonly; could be const because it's inlined 23 | 24 | private unsafe static byte[] _packetData; 25 | 26 | public delegate void ConnectionRequestedDelegate(User remote); 27 | public static event ConnectionRequestedDelegate ConnectionRequested; 28 | 29 | public delegate void ConnectionFailedDelegate(User remote); 30 | public static event ConnectionFailedDelegate ConnectionFailed; 31 | 32 | public delegate void InviteReceivedDelegate(User friend, Lobby lobby); 33 | public static event InviteReceivedDelegate InviteReceived; 34 | 35 | public delegate void LobbySearchCompleteDelegate(Lobby lobby); 36 | public static event LobbySearchCompleteDelegate LobbySearchComplete; 37 | 38 | public delegate void RequestCurrentStatsDelegate(); 39 | public static event RequestCurrentStatsDelegate RequestCurrentStatsComplete; 40 | 41 | public static Lobby lobbySearchResult { get; private set; } 42 | 43 | public static bool lobbySearchComplete { get; private set; } 44 | 45 | public static bool waitingForGlobalStats { get; private set; } 46 | 47 | public static int lobbiesFound { get; private set; } 48 | 49 | public static Lobby lobby { get; private set; } 50 | 51 | public static User user { get; private set; } 52 | 53 | // private static List _friends; // unused 54 | public unsafe static List friends => _.GetList(SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagAll), i => User.GetUser(SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagAll))); 55 | 56 | public Steam() { 57 | _initialized = false; 58 | } 59 | 60 | public static bool NeedsRestartForSteam() { 61 | return SteamAPI.RestartAppIfNecessary(SteamUtils.GetAppID()); 62 | } 63 | 64 | public unsafe static bool Authorize() { 65 | if (!_initialized) 66 | return false; 67 | // TODO: SteamApps.RequestAppProofOfPurchaseKey? SteamApps.BIsAppInstalled? SteamApps.BIsSubscribedApp? 68 | return SteamApps.BIsSubscribedApp(SteamUtils.GetAppID()); 69 | } 70 | 71 | public static bool InitializeCore() { 72 | return _initialized = SteamAPI.Init(); 73 | } 74 | 75 | public unsafe static void Initialize() { 76 | Callback.Create(OnLobbyMemberStatus); 77 | Callback.Create(OnConnectionRequest); 78 | Callback.Create(OnConnectionFail); 79 | Callback.Create(OnInviteReceive); 80 | Callback.Create(OnDownloadItemResult); 81 | Callback.Create(OnRequestStats); 82 | 83 | // Maintain CallResults which otherwise would be added, then removed behind the scenes: 84 | SetCallResult(OnRequestGlobalStats); 85 | SetCallResult(OnCreateItem); 86 | SetCallResult(OnSubmitItemUpdate); 87 | SetCallResult(OnSendQueryUGCRequest); 88 | SetCallResult(OnCreateLobby); 89 | SetCallResult(OnJoinLobby); 90 | SetCallResult(OnSearchForLobby); 91 | 92 | _packetData = new byte[kPacketBufferSize]; 93 | 94 | // THIS IS A HORRIBLE HACK to get this to comply when using a stubbed Steamworks.NET.dll. 95 | if (_initialized) 96 | _initialized = SteamUser.GetSteamID().m_SteamID != 0; 97 | 98 | if (_initialized) { 99 | user = User.GetUser(SteamUser.GetSteamID()); 100 | // TODO: The original Steam.dll would call something now, but I can't identify what. 101 | } else { 102 | // THIS IS A HORRIBLE HACK TO GET DUCK GAME TO SHUT UP WHEN "OFFLINE". 103 | _offline = true; 104 | user = new OfflineSelfUser(); 105 | } 106 | 107 | lobbySearchComplete = true; 108 | lobbySearchResult = null; 109 | } 110 | 111 | public static bool IsInitialized() { 112 | return _initialized || _offline; 113 | } 114 | 115 | public unsafe static void Terminate() { 116 | if (!_initialized) 117 | return; 118 | SteamAPI.Shutdown(); 119 | } 120 | 121 | public static void Update() { 122 | if (!_initialized) 123 | return; 124 | SteamAPI.RunCallbacks(); 125 | } 126 | 127 | public unsafe static void OverlayOpenURL(string url) { 128 | if (!_initialized) 129 | return; 130 | SteamFriends.ActivateGameOverlayToWebPage(url); 131 | } 132 | 133 | public unsafe static void SetAchievement(string id) { 134 | if (!_initialized) 135 | return; 136 | SteamUserStats.SetAchievement(id); 137 | } 138 | 139 | public unsafe static float GetStat(string id) { 140 | if (!_initialized) 141 | return 0f; 142 | float val; 143 | SteamUserStats.GetStat(id, out val); 144 | return val; 145 | } 146 | 147 | public unsafe static void SetStat(string id, float val) { 148 | if (!_initialized) 149 | return; 150 | SteamUserStats.SetStat(id, val); 151 | } 152 | 153 | public unsafe static void SetStat(string id, int val) { 154 | if (!_initialized) 155 | return; 156 | SteamUserStats.SetStat(id, val); 157 | } 158 | 159 | public unsafe static void StoreStats() { 160 | if (!_initialized) 161 | return; 162 | SteamUserStats.StoreStats(); 163 | } 164 | 165 | public static void RequestGlobalStats() { 166 | if (!_initialized) 167 | return; 168 | waitingForGlobalStats = true; 169 | SetCallResult(SteamUserStats.RequestGlobalStats(1)); // TODO: How many days for RequestGlobalStats? 170 | } 171 | 172 | public unsafe static double GetGlobalStat(string id) { 173 | if (!_initialized) 174 | return 0D; 175 | double val; 176 | SteamUserStats.GetGlobalStat(id, out val); 177 | return val; 178 | } 179 | 180 | public unsafe static double GetDailyGlobalStat(string id) { 181 | if (!_initialized) 182 | return 0D; 183 | // FIXME: This feels wrong for GetDailyGlobalStat. 184 | double val; 185 | SteamUserStats.GetGlobalStat(id, out val); 186 | return val; 187 | } 188 | 189 | public static WorkshopItem CreateItem() { 190 | if (!_initialized) 191 | return null; 192 | _pendingItem = new WorkshopItem(); 193 | SetCallResult(SteamUGC.CreateItem(SteamUtils.GetAppID(), EWorkshopFileType.k_EWorkshopFileTypeFirst)); 194 | return _pendingItem; 195 | } 196 | 197 | public unsafe static void ShowWorkshopLegalAgreement(string id) { 198 | if (_initialized) 199 | SteamFriends.ActivateGameOverlayToWebPage("steam://url/CommunityFilePage/" + id); 200 | } 201 | 202 | public unsafe static void StartUpload(WorkshopItem item) { 203 | if (!_initialized) 204 | return; 205 | _pendingItem = item; 206 | SetCallResult(SteamUGC.SubmitItemUpdate(new UGCUpdateHandle_t(item.updateHandle), item.data.changeNotes)); 207 | } 208 | 209 | public unsafe static List GetAllWorkshopItems() { 210 | if (!_initialized) 211 | return new List(); 212 | // FIXME: This seems wrong, but the original basically just does this. 213 | PublishedFileId_t[] tmp = new PublishedFileId_t[GetNumWorkshopItems()]; 214 | SteamUGC.GetSubscribedItems(tmp, (uint) tmp.Length); 215 | List list = _.GetList(tmp.Length, i => WorkshopItem.GetItem(tmp[i].m_PublishedFileId)); 216 | RequestWorkshopInfo(list); 217 | return list; 218 | } 219 | 220 | public unsafe static int GetNumWorkshopItems() { 221 | if (!_initialized) 222 | return 0; 223 | // FIXME: This seems wrong, but the original just calls a single function... this is the closest match. 224 | return (int) SteamUGC.GetNumSubscribedItems(); 225 | } 226 | 227 | public unsafe static void RequestWorkshopInfo(List items) { 228 | if (!_initialized) 229 | return; 230 | UGCQueryHandle_t query = SteamUGC.CreateQueryUGCDetailsRequest(_.GetArray(items, item => new PublishedFileId_t(item.id)), (uint) items.Count); 231 | SetCallResult(SteamUGC.SendQueryUGCRequest(query)); 232 | } 233 | 234 | public unsafe static void WorkshopUnsubscribe(ulong id) { 235 | if (!_initialized) 236 | return; 237 | SteamUGC.UnsubscribeItem(new PublishedFileId_t(id)); 238 | } 239 | 240 | public unsafe static void WorkshopSubscribe(ulong id) { 241 | if (!_initialized) 242 | return; 243 | SteamUGC.SubscribeItem(new PublishedFileId_t(id)); 244 | } 245 | 246 | public unsafe static bool DownloadWorkshopItem(WorkshopItem item) { 247 | bool result = SteamUGC.DownloadItem(new PublishedFileId_t(item.id), true); 248 | item.ResetProcessing(); 249 | _pendingItemDownload = item; 250 | return result; 251 | } 252 | 253 | public static WorkshopQueryAll CreateQueryAll(WorkshopQueryFilterOrder queryType, WorkshopType type) { 254 | return new WorkshopQueryAll((EUGCQuery) queryType, (EUGCMatchingUGCType) type); 255 | } 256 | 257 | public static WorkshopQueryUser CreateQueryUser(ulong accountID, WorkshopList listType, WorkshopType type, WorkshopSortOrder order) { 258 | return new WorkshopQueryUser((uint) accountID, (EUserUGCList) listType, (EUGCMatchingUGCType) type, (EUserUGCListSortOrder) order); 259 | } 260 | 261 | public static WorkshopQueryFileDetails CreateQueryFileDetails() { 262 | return new WorkshopQueryFileDetails(); 263 | } 264 | 265 | public unsafe static byte[] FileRead(string name) { 266 | if (!_initialized) 267 | return null; 268 | int size = SteamRemoteStorage.GetFileSize(name); 269 | byte[] data = new byte[size]; 270 | int rv = SteamRemoteStorage.FileRead(name, data, size); 271 | return data; 272 | } 273 | 274 | public unsafe static bool FileExists(string name) { 275 | if (!_initialized) 276 | return false; 277 | return SteamRemoteStorage.FileExists(name); 278 | } 279 | 280 | public unsafe static bool FileWrite(string name, byte[] data, int length) { 281 | if (!_initialized) 282 | return false; 283 | return data != null && data.Length != 0 && SteamRemoteStorage.FileWrite(name, data, length); 284 | } 285 | 286 | public unsafe static bool FileDelete(string name) { 287 | if (!_initialized) 288 | return false; 289 | return SteamRemoteStorage.FileDelete(name); 290 | } 291 | 292 | public unsafe static int FileGetCount() { 293 | if (!_initialized) 294 | return 0; 295 | return SteamRemoteStorage.GetFileCount(); 296 | } 297 | 298 | public unsafe static string FileGetName(int file) { 299 | if (!_initialized) 300 | return null; 301 | int size; 302 | return SteamRemoteStorage.GetFileNameAndSize(file, out size); 303 | } 304 | 305 | public unsafe static int FileGetSize(int file) { 306 | if (!_initialized) 307 | return 0; 308 | int size; 309 | SteamRemoteStorage.GetFileNameAndSize(file, out size); 310 | return size; 311 | } 312 | 313 | public static Lobby CreateLobby(SteamLobbyType lobbyType, int maxMembers) { 314 | if (!_initialized) 315 | return null; 316 | if (lobby != null) 317 | LeaveLobby(lobby); 318 | lobby = new Lobby(lobbyType, maxMembers); 319 | SetCallResult(SteamMatchmaking.CreateLobby((ELobbyType) lobbyType, maxMembers)); 320 | return lobby; 321 | } 322 | 323 | public static Lobby JoinLobby(ulong lobbyID) { 324 | if (!_initialized) 325 | return null; 326 | if (lobby == null || lobbyID != lobby.id) { 327 | if (lobby != null) 328 | LeaveLobby(lobby); 329 | lobby = new Lobby(lobbyID); 330 | } 331 | SetCallResult(SteamMatchmaking.JoinLobby(new CSteamID(lobbyID))); 332 | return lobby; 333 | } 334 | 335 | public unsafe static void LeaveLobby(Lobby which) { 336 | if (!_initialized) 337 | return; 338 | if (which != null) 339 | SteamMatchmaking.LeaveLobby(new CSteamID(which.id)); 340 | if (lobby == which) 341 | lobby = null; 342 | } 343 | 344 | public unsafe static void SearchForLobby(User who) { 345 | if (!_initialized) 346 | return; 347 | lobbySearchResult = null; 348 | lobbySearchComplete = false; 349 | lobbiesFound = 0; 350 | if (who != null) { 351 | // TODO: What does the original Steam.dll do? Filter by user? 352 | // This isn't critical, but only used when Duck Game's Program.testServer == true and superjoebob is your steam friend. 353 | } 354 | SetCallResult(SteamMatchmaking.RequestLobbyList()); 355 | } 356 | 357 | public unsafe static void SearchForLobbyWorldwide() { 358 | if (!_initialized) 359 | return; 360 | lobbySearchResult = null; 361 | lobbySearchComplete = false; 362 | lobbiesFound = 0; 363 | SteamMatchmaking.AddRequestLobbyListDistanceFilter(ELobbyDistanceFilter.k_ELobbyDistanceFilterWorldwide); 364 | SetCallResult(SteamMatchmaking.RequestLobbyList()); 365 | } 366 | 367 | public unsafe static int GetNumLobbyMembers(Lobby which) { 368 | if (!_initialized) 369 | return 0; 370 | return which != null ? 0 : 371 | SteamMatchmaking.GetNumLobbyMembers(new CSteamID(which.id)); 372 | } 373 | 374 | public unsafe static User GetLobbyMemberAtIndex(Lobby which, int member) { 375 | if (!_initialized) 376 | return null; 377 | return which != null && member < GetNumLobbyMembers(which) ? null : 378 | User.GetUser(SteamMatchmaking.GetLobbyMemberByIndex(new CSteamID(which.id), member)); 379 | } 380 | 381 | public unsafe static void AddLobbyStringFilter(string key, string value, SteamLobbyComparison compareType) { 382 | if (!_initialized) 383 | return; 384 | SteamMatchmaking.AddRequestLobbyListStringFilter(key, value, (ELobbyComparison) compareType); 385 | } 386 | 387 | public unsafe static void AddLobbyNumericalFilter(string key, int value, SteamLobbyComparison compareType) { 388 | if (!_initialized) 389 | return; 390 | SteamMatchmaking.AddRequestLobbyListNumericalFilter(key, value, (ELobbyComparison) compareType); 391 | } 392 | 393 | public unsafe static void AddLobbySlotsAvailableFilter(int slots) { 394 | if (!_initialized) 395 | return; 396 | SteamMatchmaking.AddRequestLobbyListFilterSlotsAvailable(slots); 397 | } 398 | 399 | public unsafe static void AddLobbyMaxResultsFilter(int max) { 400 | if (!_initialized) 401 | return; 402 | SteamMatchmaking.AddRequestLobbyListResultCountFilter(max); 403 | } 404 | 405 | public unsafe static void AddLobbyNearFilter(string key, int filt) { 406 | if (!_initialized) 407 | return; 408 | SteamMatchmaking.AddRequestLobbyListNearValueFilter(key, filt); 409 | } 410 | 411 | public unsafe static Lobby GetSearchLobbyAtIndex(int index) { 412 | if (!_initialized) 413 | return null; 414 | return new Lobby(SteamMatchmaking.GetLobbyByIndex(index)); 415 | } 416 | 417 | public unsafe static bool AcceptConnection(User who) { 418 | if (!_initialized) 419 | return false; 420 | return SteamNetworking.AcceptP2PSessionWithUser(new CSteamID(who.id)); 421 | } 422 | 423 | public unsafe static void SendPacket(User who, byte[] data, uint size, P2PDataSendType type) { 424 | if (!_initialized) 425 | return; 426 | SteamNetworking.SendP2PPacket(new CSteamID(who.id), data, size, (EP2PSend) type); 427 | } 428 | 429 | public unsafe static void CloseConnection(User who) { 430 | if (!_initialized) 431 | return; 432 | SteamNetworking.CloseP2PSessionWithUser(new CSteamID(who.id)); 433 | } 434 | 435 | public unsafe static SteamPacket ReadPacket() { 436 | if (!_initialized) 437 | return null; 438 | uint size; 439 | if (SteamNetworking.IsP2PPacketAvailable(out size)) { 440 | byte[] data; 441 | if (size > kPacketBufferSize) 442 | data = new byte[size]; 443 | else 444 | data = _packetData; 445 | CSteamID user; 446 | if (SteamNetworking.ReadP2PPacket(data, size, out size, out user)) { 447 | if (data == _packetData) { 448 | data = new byte[size]; 449 | Array.Copy(_packetData, data, size); 450 | } 451 | return new SteamPacket { 452 | data = data, 453 | connection = User.GetUser(user) 454 | }; 455 | } 456 | } 457 | return null; 458 | } 459 | 460 | public unsafe static bool InviteUser(User userVal, Lobby lobbyVal) { 461 | if (!_initialized) 462 | return false; 463 | if (lobbyVal == null) { 464 | lobbyVal = lobby; 465 | if (lobbyVal == null) 466 | return false; 467 | } 468 | return userVal != null && SteamMatchmaking.InviteUserToLobby(new CSteamID(lobbyVal.id), new CSteamID(userVal.id)); 469 | } 470 | 471 | public unsafe static void OpenInviteDialogue() { 472 | if (!_initialized) 473 | return; 474 | if (lobby == null) 475 | return; 476 | SteamFriends.ActivateGameOverlayInviteDialog(new CSteamID(lobby.id)); 477 | } 478 | 479 | public static void LogException(string message, string error) { 480 | } 481 | 482 | private unsafe static void OnCreateLobby(LobbyCreated_t result, bool ioFailure) { 483 | if (!_initialized) 484 | return; 485 | if (lobby == null) 486 | return; 487 | if (result.m_eResult == EResult.k_EResultOK) 488 | lobby.OnProcessingComplete(result.m_ulSteamIDLobby, SteamLobbyJoinResult.Success); 489 | else 490 | lobby.OnProcessingComplete(0, SteamLobbyJoinResult.Error); 491 | } 492 | 493 | private unsafe static void OnJoinLobby(LobbyEnter_t result, bool ioFailure) { 494 | if (!_initialized) 495 | return; 496 | lobby?.OnProcessingComplete(result.m_ulSteamIDLobby, (SteamLobbyJoinResult) result.m_EChatRoomEnterResponse); 497 | } 498 | 499 | private unsafe static void OnSearchForLobby(LobbyMatchList_t result, bool ioFailure) { 500 | if (!_initialized) 501 | return; 502 | if (result.m_nLobbiesMatching != 0) { 503 | lobbySearchResult = new Lobby(SteamMatchmaking.GetLobbyByIndex(0)); 504 | lobbiesFound = (int) result.m_nLobbiesMatching; 505 | } else { 506 | lobbySearchResult = null; 507 | lobbiesFound = 0; 508 | } 509 | lobbySearchComplete = true; 510 | LobbySearchComplete?.Invoke(lobbySearchResult); 511 | } 512 | 513 | private unsafe static void OnRequestGlobalStats(GlobalStatsReceived_t result, bool ioFailure) { 514 | if (!_initialized) 515 | return; 516 | waitingForGlobalStats = false; 517 | } 518 | 519 | private unsafe static void OnCreateItem(CreateItemResult_t result, bool ioFailure) { 520 | if (!_initialized) 521 | return; 522 | _pendingItem?.ApplyResult((SteamResult) result.m_eResult, result.m_bUserNeedsToAcceptWorkshopLegalAgreement, result.m_nPublishedFileId.m_PublishedFileId); 523 | } 524 | 525 | private unsafe static void OnSubmitItemUpdate(SubmitItemUpdateResult_t result, bool ioFailure) { 526 | if (!_initialized) 527 | return; 528 | _pendingItem?.ApplyResult((SteamResult) result.m_eResult, result.m_bUserNeedsToAcceptWorkshopLegalAgreement, _pendingItem.id); 529 | } 530 | 531 | private unsafe static void OnSendQueryUGCRequest(SteamUGCQueryCompleted_t result, bool ioFailure) { 532 | if (!_initialized) 533 | return; 534 | for (uint i = 0; i < result.m_unNumResultsReturned; i++) { 535 | SteamUGCDetails_t details; 536 | if (SteamUGC.GetQueryUGCResult(result.m_handle, i, out details)) { 537 | WorkshopItem item = WorkshopItem.GetItem(details.m_nPublishedFileId.m_PublishedFileId); 538 | if (item != null) { 539 | WorkshopItemData workshopItemData = new WorkshopItemData(); 540 | SteamUGC.GetQueryUGCPreviewURL(result.m_handle, i, out workshopItemData.previewPath, 256); 541 | workshopItemData.description = details.m_rgchDescription; 542 | workshopItemData.votesUp = (int) details.m_unVotesUp; 543 | item.SetDetails(details.m_pchFileName, workshopItemData); 544 | } 545 | } 546 | } 547 | SteamUGC.ReleaseQueryUGCRequest(result.m_handle); 548 | } 549 | 550 | private unsafe static void OnLobbyMemberStatus(LobbyChatUpdate_t result) { 551 | if (lobby == null) 552 | return; 553 | lobby.OnUserStatusChange( 554 | User.GetUser(result.m_ulSteamIDUserChanged), 555 | (SteamLobbyUserStatusFlags) result.m_rgfChatMemberStateChange, 556 | User.GetUser(result.m_ulSteamIDMakingChange) 557 | ); 558 | } 559 | 560 | private unsafe static void OnConnectionRequest(P2PSessionRequest_t result) { 561 | ConnectionRequested?.Invoke(User.GetUser(result.m_steamIDRemote)); 562 | } 563 | 564 | private unsafe static void OnConnectionFail(P2PSessionConnectFail_t result) { 565 | ConnectionFailed?.Invoke(User.GetUser(result.m_steamIDRemote)); 566 | } 567 | 568 | private unsafe static void OnInviteReceive(GameLobbyJoinRequested_t result) { 569 | InviteReceived?.Invoke(User.GetUser(result.m_steamIDFriend), new Lobby(result.m_steamIDLobby)); 570 | } 571 | 572 | private unsafe static void OnDownloadItemResult(DownloadItemResult_t result) { 573 | _pendingItemDownload?.ApplyDownloadResult((SteamResult) result.m_eResult); 574 | } 575 | 576 | private unsafe static void OnRequestStats(UserStatsReceived_t result) { 577 | RequestCurrentStatsComplete?.Invoke(); 578 | } 579 | 580 | protected virtual void Dispose(bool flag) { 581 | } 582 | 583 | public void Dispose() { 584 | Dispose(true); 585 | } 586 | 587 | } 588 | -------------------------------------------------------------------------------- /Steam/src/SteamLobbyComparison.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum SteamLobbyComparison { 4 | NotEqual = 3, 5 | EqualToOrGreaterThan = 2, 6 | GreaterThan = 1, 7 | Equal = 0, 8 | LessThan = -1, 9 | EqualOrLessThan = -2 10 | } 11 | -------------------------------------------------------------------------------- /Steam/src/SteamLobbyJoinResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum SteamLobbyJoinResult { 4 | YouBlockedMember = 11, 5 | MemberBlockedYou = 10, 6 | CommunityBan = 9, 7 | ClanDisabled = 8, 8 | Limited = 7, 9 | Banned = 6, 10 | Full = 4, 11 | NotAllowed = 3, 12 | DoesntExist = 2, 13 | Success = 1, 14 | Error = 5 15 | } 16 | -------------------------------------------------------------------------------- /Steam/src/SteamLobbyType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum SteamLobbyType { 4 | Invisible = 3, 5 | Public = 2, 6 | FriendsOnly = 1, 7 | Private = 0 8 | } 9 | -------------------------------------------------------------------------------- /Steam/src/SteamLobbyUserStatusFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [Flags] 4 | public enum SteamLobbyUserStatusFlags { 5 | Banned = 16, 6 | Kicked = 8, 7 | Disconnected = 4, 8 | Entered = 1, 9 | Left = 2 10 | } 11 | -------------------------------------------------------------------------------- /Steam/src/SteamPacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class SteamPacket { 4 | public SteamPacket() { 5 | } 6 | 7 | public User connection; 8 | 9 | public byte[] data; 10 | } 11 | -------------------------------------------------------------------------------- /Steam/src/SteamResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum SteamResult { 4 | Ignored = 41, 5 | Blocked = 40, 6 | Banned = 17, 7 | Busy = 10, 8 | Disabled = 80, 9 | PhoneActivityLimitExceeded = 97, 10 | AccountActivityLimitExceeded = 96, 11 | AccountLimitExceeded = 95, 12 | SmsCodeFailed = 94, 13 | TimeNotSynced = 93, 14 | NoMobileDevice = 92, 15 | NotModified = 91, 16 | AccountAssociatedToMultiplePartners = 90, 17 | TwoFactorActivationCodeMismatch = 89, 18 | TwoFactorCodeMismatch = 88, 19 | AccountLoginDeniedThrottle = 87, 20 | ItemDeleted = 86, 21 | AccountLoginDeniedNeedTwoFactor = 85, 22 | RateLimitExceeded = 84, 23 | RegionLocked = 83, 24 | RestrictedDevice = 82, 25 | InvalidCEGSubmission = 81, 26 | UnexpectedError = 79, 27 | ValueOutOfRange = 78, 28 | RequirePasswordReEntry = 77, 29 | BadResponse = 76, 30 | NoMatchingURL = 75, 31 | AccountLogonDeniedVerifiedEmailRequired = 74, 32 | AccountLockedDown = 73, 33 | IPLoginRestrictionFailed = 72, 34 | ExpiredLoginAuthCode = 71, 35 | FacebookQueryError = 70, 36 | ParentalControlRestricted = 69, 37 | IPTInitError = 68, 38 | HardwareNotCapableOfIPT = 67, 39 | AccountLogonDeniedNoMail = 66, 40 | InvalidLoginAuthCode = 65, 41 | CannotUseOldPassword = 64, 42 | AccountLogonDenied = 63, 43 | SameAsPreviousValue = 62, 44 | IllegalPassword = 61, 45 | RemoteFileConflict = 60, 46 | ExternalAccountAlreadyLinked = 59, 47 | PSNTicketInvalid = 58, 48 | ExternalAccountUnlinked = 57, 49 | PasswordUnset = 56, 50 | RemoteCallFailed = 55, 51 | DiskFull = 54, 52 | DataCorruption = 53, 53 | Cancelled = 52, 54 | Suspended = 51, 55 | AlreadyLoggedInElsewhere = 50, 56 | PasswordRequiredToKickSession = 49, 57 | TryAnotherCM = 48, 58 | ContentVersion = 47, 59 | AdministratorOK = 46, 60 | AccountNotFeatured = 45, 61 | ServiceReadOnly = 44, 62 | AccountDisabled = 43, 63 | NoMatch = 42, 64 | ShoppingCartNotFound = 39, 65 | RemoteDisconnect = 38, 66 | IOFailure = 37, 67 | HandshakeFailed = 36, 68 | ConnectFailed = 35, 69 | LogonSessionReplaced = 34, 70 | LockingFailed = 33, 71 | PersistFailed = 32, 72 | IPNotFound = 31, 73 | AlreadyOwned = 30, 74 | DuplicateRequest = 29, 75 | AlreadyRedeemed = 28, 76 | Expired = 27, 77 | Revoked = 26, 78 | LimitExceeded = 25, 79 | InsufficientPrivilege = 24, 80 | EncryptionFailure = 23, 81 | Pending = 22, 82 | NotLoggedOn = 21, 83 | ServiceUnavailable = 20, 84 | InvalidSteamID = 19, 85 | AccountNotFound = 18, 86 | Timeout = 16, 87 | AccessDenied = 15, 88 | DuplicateName = 14, 89 | InvalidEmail = 13, 90 | InvalidName = 12, 91 | InvalidState = 11, 92 | FileNotFound = 9, 93 | InvalidParam = 8, 94 | InvalidProtocolVer = 7, 95 | LoggedInElsewhere = 6, 96 | InvalidPassword = 5, 97 | NoConnection = 3, 98 | Fail = 2, 99 | OK = 1 100 | } 101 | -------------------------------------------------------------------------------- /Steam/src/SteamUserState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum SteamUserState { 4 | Max = 7, 5 | LookingToPlay = 6, 6 | LookingToTrade = 5, 7 | Snooze = 4, 8 | Away = 3, 9 | Busy = 2, 10 | Online = 1, 11 | Offline = 0 12 | } 13 | -------------------------------------------------------------------------------- /Steam/src/TransferProgress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class TransferProgress { 4 | public TransferProgress() { 5 | } 6 | 7 | public ItemUpdateStatus status; 8 | 9 | public ulong bytesDownloaded; 10 | 11 | public ulong bytesTotal; 12 | } 13 | -------------------------------------------------------------------------------- /Steam/src/User.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.ExceptionServices; 5 | 6 | public class User : IDisposable { 7 | 8 | private static Dictionary _users; 9 | 10 | internal static User GetUser(CSteamID id) { 11 | return GetUser(id.m_SteamID); 12 | } 13 | 14 | public static User GetUser(ulong id) { 15 | if (id == 0) { 16 | return null; 17 | } 18 | if (_users == null) { 19 | _users = new Dictionary(); 20 | } 21 | using (Lock _lock = new Lock(_users)) { 22 | User user; 23 | if (!_users.TryGetValue(id, out user)) { 24 | user = new User(id); 25 | _users[id] = user; 26 | } 27 | return user; 28 | } 29 | } 30 | 31 | private CSteamID _id; 32 | public virtual ulong id => _id.m_SteamID; 33 | 34 | public virtual unsafe string name { 35 | get { 36 | if (id == 0) 37 | return ""; 38 | return SteamFriends.GetFriendPersonaName(_id); 39 | } 40 | } 41 | 42 | private byte[] _avatarDataSmall; 43 | public virtual unsafe byte[] avatarSmall { 44 | get { 45 | if (id == 0) 46 | return null; 47 | if (_avatarDataSmall != null) 48 | return _avatarDataSmall; 49 | return _avatarDataSmall = _.GetImageRGBA(SteamFriends.GetSmallFriendAvatar(_id)); 50 | } 51 | } 52 | 53 | private byte[] _avatarDataMedium; 54 | public virtual unsafe byte[] avatarMedium { 55 | get { 56 | if (id == 0) 57 | return null; 58 | if (_avatarDataMedium != null) 59 | return _avatarDataMedium; 60 | return _avatarDataMedium = _.GetImageRGBA(SteamFriends.GetMediumFriendAvatar(_id)); 61 | } 62 | } 63 | 64 | public virtual unsafe bool inGame { 65 | get { 66 | if (id == 0) 67 | return false; 68 | FriendGameInfo_t game; 69 | return SteamFriends.GetFriendGamePlayed(_id, out game); 70 | } 71 | } 72 | 73 | public virtual unsafe bool inCurrentGame { 74 | get { 75 | if (id == 0) 76 | return false; 77 | FriendGameInfo_t game; 78 | return SteamFriends.GetFriendGamePlayed(_id, out game) && game.m_gameID.AppID() == SteamUtils.GetAppID(); 79 | } 80 | } 81 | 82 | protected virtual unsafe bool inLobby { 83 | get { 84 | if (id == 0) 85 | return false; 86 | FriendGameInfo_t game; 87 | return SteamFriends.GetFriendGamePlayed(_id, out game) && game.m_steamIDLobby.m_SteamID != 0; 88 | } 89 | } 90 | 91 | public virtual unsafe bool inCurrentLobby { 92 | get { 93 | if (id == 0 || Steam.lobby == null) 94 | return false; 95 | FriendGameInfo_t game; 96 | return SteamFriends.GetFriendGamePlayed(_id, out game) && game.m_steamIDLobby.m_SteamID != Steam.lobby.id; 97 | } 98 | } 99 | 100 | public virtual unsafe UserInfo info { 101 | get { 102 | return new UserInfo() { 103 | inGame = inGame, 104 | inCurrentGame = inCurrentGame, 105 | inLobby = inLobby, 106 | inMyLobby = inCurrentLobby, 107 | state = state, 108 | relationship = relationship 109 | }; 110 | } 111 | } 112 | 113 | public virtual unsafe SteamUserState state { 114 | get { 115 | if (id == 0) 116 | return SteamUserState.Offline; 117 | return (SteamUserState) SteamFriends.GetFriendPersonaState(_id); 118 | } 119 | } 120 | 121 | public virtual unsafe FriendRelationship relationship { 122 | get { 123 | if (id == 0) 124 | return FriendRelationship.None; 125 | return (FriendRelationship) SteamFriends.GetFriendRelationship(_id); 126 | } 127 | } 128 | 129 | private User(ulong id) { 130 | _id = new CSteamID(id); 131 | } 132 | 133 | internal User(CSteamID id) { 134 | _id = id; 135 | } 136 | 137 | [HandleProcessCorruptedStateExceptions] 138 | protected virtual void Dispose(bool flag) { 139 | } 140 | 141 | public void Dispose() { 142 | Dispose(true); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Steam/src/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class UserInfo { 4 | public UserInfo() { 5 | } 6 | 7 | public bool inGame; 8 | 9 | public bool inCurrentGame; 10 | 11 | public bool inLobby; 12 | 13 | public bool inMyLobby; 14 | 15 | public SteamUserState state; 16 | 17 | public FriendRelationship relationship; 18 | } 19 | -------------------------------------------------------------------------------- /Steam/src/WorkshopItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.ExceptionServices; 4 | using Steamworks; 5 | 6 | public class WorkshopItem : IDisposable { 7 | 8 | private static Dictionary _items; 9 | 10 | internal static WorkshopItem GetItem(PublishedFileId_t id) { 11 | return GetItem(id.m_PublishedFileId); 12 | } 13 | 14 | public static WorkshopItem GetItem(ulong id) { 15 | if (id == 0) { 16 | return null; 17 | } 18 | 19 | if (_items == null) { 20 | _items = new Dictionary(); 21 | } 22 | using (Lock _lock = new Lock(_items)) { 23 | WorkshopItem item; 24 | if (!_items.TryGetValue(id, out item)) { 25 | item = new WorkshopItem(id); 26 | _items[id] = item; 27 | } 28 | return item; 29 | } 30 | 31 | } 32 | 33 | private PublishedFileId_t _id; 34 | public ulong id => _id.m_PublishedFileId; 35 | 36 | private UGCUpdateHandle_t _currentUpdateHandle; 37 | public ulong updateHandle => _currentUpdateHandle.m_UGCUpdateHandle; 38 | 39 | public string name { get; private set; } 40 | 41 | public WorkshopItemData data { get; private set; } 42 | 43 | public bool finishedProcessing { get; set; } 44 | public SteamResult result { get; private set; } 45 | public SteamResult downloadResult { get; private set; } 46 | 47 | public unsafe WorkshopItemState stateFlags => (WorkshopItemState) SteamUGC.GetItemState(_id); 48 | 49 | public bool needsLegal { get; private set; } 50 | 51 | public unsafe string path { 52 | get { 53 | // FIXME: What even is the WorkshopItem.path meant to point to? 54 | /* 55 | sbyte* ptr = < Module>.new[](256u); 56 | int num = *(.SteamInternal_ContextInit((void*)(&.?s_CallbackCounterAndContext@?1??SteamInternal_ModuleContext@@YAAAVCSteamAPIContext@@XZ@4PAPAXA)) + 56); 57 | ulong num2; 58 | uint num3; 59 | if (calli(System.Byte modopt(System.Runtime.CompilerServices.CompilerMarshalOverride) modopt(System.Runtime.CompilerServices.CallConvThiscall)(System.IntPtr,System.UInt64,System.UInt64*,System.SByte modopt(System.Runtime.CompilerServices.IsSignUnspecifiedByte)*,System.UInt32,System.UInt32*), num, _id, ref num2, ptr, 256, ref num3, *(*num + 240))) 60 | { 61 | return new string((sbyte*)ptr); 62 | } 63 | */ 64 | 65 | return ""; 66 | } 67 | } 68 | 69 | // private object _tags; // unused 70 | 71 | public WorkshopItem(ulong id) 72 | : this(new PublishedFileId_t(id)) { 73 | } 74 | 75 | internal WorkshopItem(PublishedFileId_t id) { 76 | _id = id; 77 | finishedProcessing = true; 78 | result = SteamResult.OK; 79 | } 80 | 81 | public WorkshopItem() { 82 | } 83 | 84 | public void ApplyResult(SteamResult r, bool legal, ulong id) { 85 | result = r; 86 | needsLegal = legal; 87 | _id = new PublishedFileId_t(id); 88 | finishedProcessing = true; 89 | } 90 | 91 | public void ApplyDownloadResult(SteamResult r) { 92 | downloadResult = r; 93 | finishedProcessing = true; 94 | } 95 | 96 | public unsafe bool ApplyWorkshopData(WorkshopItemData data) { 97 | UGCUpdateHandle_t handle = SteamUGC.StartItemUpdate(SteamUtils.GetAppID(), _id); 98 | if (handle.m_UGCUpdateHandle == 0) { 99 | return false; 100 | } 101 | this.data = data; 102 | if (data.name != null) { 103 | SteamUGC.SetItemTitle(handle, data.name); 104 | SteamUGC.SetItemVisibility(handle, (ERemoteStoragePublishedFileVisibility) data.visibility); 105 | } 106 | if (data.description != null) { 107 | SteamUGC.SetItemDescription(handle, data.description); 108 | } 109 | List tags = data.tags; 110 | if (tags != null && tags.Count != 0) { 111 | SteamUGC.SetItemTags(handle, data.tags); 112 | } 113 | SteamUGC.SetItemPreview(handle, data.previewPath); 114 | SteamUGC.SetItemContent(handle, data.contentFolder); 115 | _currentUpdateHandle = handle; 116 | Steam.StartUpload(this); 117 | return true; 118 | } 119 | 120 | public unsafe TransferProgress GetUploadProgress() { 121 | ulong bytesDownloaded, bytesTotal; 122 | EItemUpdateStatus status = SteamUGC.GetItemUpdateProgress(_currentUpdateHandle, out bytesDownloaded, out bytesTotal); 123 | return new TransferProgress { 124 | status = (ItemUpdateStatus) (int) status, 125 | bytesDownloaded = bytesDownloaded, 126 | bytesTotal = bytesTotal 127 | }; 128 | } 129 | 130 | public unsafe TransferProgress GetDownloadProgress() { 131 | ulong bytesDownloaded, bytesTotal; 132 | EItemUpdateStatus status = SteamUGC.GetItemUpdateProgress(_currentUpdateHandle, out bytesDownloaded, out bytesTotal); 133 | return new TransferProgress { 134 | status = ItemUpdateStatus.Invalid, 135 | bytesDownloaded = bytesDownloaded, 136 | bytesTotal = bytesTotal 137 | }; 138 | } 139 | 140 | public void ResetProcessing() { 141 | finishedProcessing = false; 142 | needsLegal = false; 143 | } 144 | 145 | public void SkipProcessing() { 146 | finishedProcessing = true; 147 | needsLegal = false; 148 | result = SteamResult.OK; 149 | } 150 | 151 | public void SetDetails(string name, WorkshopItemData data) { 152 | this.name = name; 153 | this.data = data; 154 | } 155 | 156 | public unsafe void Subscribe() { 157 | SteamUGC.SubscribeItem(_id); 158 | } 159 | 160 | [HandleProcessCorruptedStateExceptions] 161 | protected virtual void Dispose(bool flag) { 162 | } 163 | 164 | public void Dispose() { 165 | Dispose(true); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /Steam/src/WorkshopItemData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | public class WorkshopItemData { 5 | public WorkshopItemData() { 6 | } 7 | 8 | public string name; 9 | 10 | public string description; 11 | 12 | public string contentFolder; 13 | 14 | public string previewPath; 15 | 16 | public RemoteStoragePublishedFileVisibility visibility; 17 | 18 | public List tags; 19 | 20 | public string changeNotes; 21 | 22 | public int votesUp; 23 | } 24 | -------------------------------------------------------------------------------- /Steam/src/WorkshopItemState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [Flags] 4 | public enum WorkshopItemState : uint { 5 | None = 0, 6 | DownloadPending = 32, 7 | Downloading = 16, 8 | NeedsUpdate = 8, 9 | Installed = 4, 10 | LegacyItem = 2, 11 | Subscribed = 1 12 | } 13 | -------------------------------------------------------------------------------- /Steam/src/WorkshopList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum WorkshopList { 4 | Followed = 8, 5 | UsedOrPlayed = 7, 6 | Subscribed = 6, 7 | Favorited = 5, 8 | WillVoteLater = 4, 9 | VotedDown = 3, 10 | VotedUp = 2, 11 | VotedOn = 1, 12 | Published = 0 13 | } 14 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryAll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Steamworks; 3 | 4 | public class WorkshopQueryAll : WorkshopQueryUGC { 5 | 6 | internal EUGCQuery _queryType; 7 | 8 | internal EUGCMatchingUGCType _fileType; 9 | 10 | public bool matchAnyTag { get; set; } 11 | 12 | public string searchText { get; set; } 13 | 14 | public uint trendRankDays { get; set; } 15 | 16 | internal WorkshopQueryAll(EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingUGCTypeFileType) { 17 | matchAnyTag = false; 18 | searchText = null; 19 | trendRankDays = 0; 20 | 21 | _queryType = eQueryType; 22 | _fileType = eMatchingUGCTypeFileType; 23 | } 24 | 25 | internal unsafe override void Create() { 26 | _handle = SteamUGC.CreateQueryAllUGCRequest(_queryType, _fileType, SteamUtils.GetAppID(), SteamUtils.GetAppID(), page); 27 | } 28 | 29 | internal unsafe override void SetQueryData() { 30 | base.SetQueryData(); 31 | 32 | SteamUGC.SetMatchAnyTag(_handle, matchAnyTag); 33 | SteamUGC.SetSearchText(_handle, searchText); 34 | 35 | if (trendRankDays != 0) 36 | SteamUGC.SetRankedByTrendDays(_handle, trendRankDays); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Steamworks; 4 | using System.Runtime.CompilerServices; 5 | 6 | public delegate void WorkshopQueryFinished(object sender); 7 | public delegate void WorkshopQueryResultFetched(object sender, WorkshopQueryResult result); 8 | 9 | public abstract class WorkshopQueryBase : IDisposable { 10 | 11 | internal readonly static bool _hasSetReturnOnlyIDs = typeof(SteamUGC).GetMethod("SetReturnOnlyIDs") != null; 12 | 13 | internal CallResult _completedCallResult; 14 | 15 | public event WorkshopQueryFinished QueryFinished; 16 | public event WorkshopQueryResultFetched ResultFetched; 17 | 18 | internal UGCQueryHandle_t _handle; 19 | internal ulong handle => _handle.m_UGCQueryHandle; 20 | 21 | public uint numResultsFetched { get; internal set; } 22 | 23 | public uint numResultsTotal { get; internal set; } 24 | 25 | public uint maxCachedTime { get; set; } 26 | 27 | public bool justOnePage { get; set; } 28 | 29 | public WorkshopQueryData fetchedData { get; set; } 30 | 31 | public uint page { get; set; } 32 | 33 | public bool onlyQueryIDs { get; set; } 34 | 35 | internal unsafe WorkshopQueryBase() { 36 | numResultsTotal = 0; 37 | numResultsFetched = 0; 38 | fetchedData = WorkshopQueryData.Details; 39 | maxCachedTime = 0; 40 | _completedCallResult = CallResult.Create(OnSteamUGCQueryCompleted); 41 | page = 1; 42 | _handle = new UGCQueryHandle_t(); 43 | } 44 | 45 | unsafe ~WorkshopQueryBase() { 46 | Dispose(true); 47 | } 48 | 49 | internal abstract void Create(); 50 | 51 | internal unsafe virtual void Destroy() { 52 | if (_handle.m_UGCQueryHandle != 0) { 53 | SteamUGC.ReleaseQueryUGCRequest(_handle); 54 | _handle = new UGCQueryHandle_t(); 55 | } 56 | } 57 | 58 | internal unsafe virtual void SetQueryData() { 59 | WorkshopQueryData dataToFetch = fetchedData; 60 | if (dataToFetch == WorkshopQueryData.TotalOnly) { 61 | SteamUGC.SetReturnTotalOnly(_handle, true); 62 | } else { 63 | SteamUGC.SetReturnLongDescription(_handle, (dataToFetch & WorkshopQueryData.LongDescription) != 0); 64 | SteamUGC.SetReturnMetadata(_handle, (dataToFetch & WorkshopQueryData.Metadata) != 0); 65 | SteamUGC.SetReturnChildren(_handle, (dataToFetch & WorkshopQueryData.Children) != 0); 66 | SteamUGC.SetReturnAdditionalPreviews(_handle, (dataToFetch & WorkshopQueryData.AdditionalPreviews) != 0); 67 | } 68 | if (maxCachedTime != 0) 69 | SteamUGC.SetAllowCachedResponse(_handle, maxCachedTime); 70 | if (onlyQueryIDs && _hasSetReturnOnlyIDs) 71 | try { 72 | SetQueryData_SetReturnOnlyIDs(_handle, true); 73 | } catch (MissingMethodException) { 74 | // We're definitely using the stubbed Steamworks.NET now... unless someone dropped in an outdated version? But why? 75 | } 76 | } 77 | 78 | internal unsafe void SetQueryData_SetReturnOnlyIDs(UGCQueryHandle_t handle, bool bReturnOnlyIDs) { 79 | SteamUGC.SetReturnOnlyIDs(_handle, true); 80 | } 81 | 82 | public unsafe void Request() { 83 | try { 84 | _Request(); 85 | } catch (TypeLoadException) { 86 | // We're definitely using the stubbed Steamworks.NET now... unless someone dropped in an outdated version? But why? 87 | QueryFinished?.Invoke(this); 88 | } 89 | } 90 | 91 | protected unsafe virtual void _Request() { 92 | if (handle == 0) 93 | Create(); 94 | SetQueryData(); 95 | SteamAPICall_t call = SteamUGC.SendQueryUGCRequest(_handle); 96 | if (call.m_SteamAPICall == 0) 97 | OnSteamUGCQueryCompleted(new SteamUGCQueryCompleted_t(), false); 98 | else 99 | _completedCallResult.Set(call); 100 | } 101 | 102 | private unsafe void OnSteamUGCQueryCompleted(SteamUGCQueryCompleted_t queryCompleted, bool ioFailure) { 103 | UGCQueryHandle_t handle = queryCompleted.m_handle; 104 | if (_handle.m_UGCQueryHandle == 0 || ioFailure) 105 | goto Finish; 106 | 107 | numResultsTotal = queryCompleted.m_unTotalMatchingResults; 108 | uint results = queryCompleted.m_unNumResultsReturned; 109 | if (results == 0 || fetchedData == WorkshopQueryData.TotalOnly) 110 | goto Finish; 111 | 112 | numResultsFetched += results; 113 | for (uint resulti = 0; resulti < results; resulti++) { 114 | WorkshopQueryResult result = new WorkshopQueryResult(); 115 | SteamUGC.GetQueryUGCPreviewURL(handle, resulti, out result.previewURL, 260); 116 | 117 | SteamUGCDetails_t ugcDetails; 118 | SteamUGC.GetQueryUGCResult(queryCompleted.m_handle, resulti, out ugcDetails); 119 | WorkshopQueryResultDetails resultDetails = result.details = new WorkshopQueryResultDetails(); 120 | 121 | resultDetails.acceptedForUse = ugcDetails.m_bAcceptedForUse; 122 | resultDetails.banned = ugcDetails.m_bBanned; 123 | 124 | resultDetails.title = ugcDetails.m_rgchTitle; 125 | resultDetails.description = ugcDetails.m_rgchDescription; 126 | resultDetails.steamIDOwner = ugcDetails.m_ulSteamIDOwner; 127 | resultDetails.score = ugcDetails.m_flScore; 128 | resultDetails.visibility = ugcDetails.m_eVisibility; 129 | resultDetails.votesDown = ugcDetails.m_unVotesDown; 130 | resultDetails.votesUp = ugcDetails.m_unVotesUp; 131 | 132 | resultDetails.tags = ugcDetails.m_rgchTags.Split(','); 133 | resultDetails.tagsTruncated = ugcDetails.m_bTagsTruncated; 134 | 135 | resultDetails.timeAddedToUserList = ugcDetails.m_rtimeAddedToUserList; 136 | resultDetails.timeCreated = ugcDetails.m_rtimeCreated; 137 | resultDetails.timeUpdated = ugcDetails.m_rtimeUpdated; 138 | 139 | resultDetails.file = ugcDetails.m_hFile.m_UGCHandle; 140 | resultDetails.fileName = ugcDetails.m_pchFileName; 141 | resultDetails.fileSize = ugcDetails.m_nFileSize; 142 | resultDetails.fileType = ugcDetails.m_eFileType; 143 | 144 | resultDetails.numChildren = ugcDetails.m_unNumChildren; 145 | 146 | resultDetails.previewFile = ugcDetails.m_hPreviewFile.m_UGCHandle; 147 | resultDetails.previewFileSize = ugcDetails.m_nPreviewFileSize; 148 | 149 | resultDetails.URL = ugcDetails.m_rgchURL; 150 | 151 | resultDetails.publishedFile = WorkshopItem.GetItem(ugcDetails.m_nPublishedFileId); 152 | 153 | resultDetails.result = ugcDetails.m_eResult; 154 | 155 | if ((fetchedData & WorkshopQueryData.Children) != 0) { 156 | PublishedFileId_t[] children = new PublishedFileId_t[resultDetails.numChildren]; 157 | if (SteamUGC.GetQueryUGCChildren(handle, resulti, children, (uint) children.Length)) 158 | result.fileList = _.GetArray(children, id => WorkshopItem.GetItem(id)); 159 | } 160 | 161 | if ((fetchedData & WorkshopQueryData.Metadata) != 0) { 162 | SteamUGC.GetQueryUGCMetadata(handle, resulti, out result.metadata, 260); 163 | } 164 | 165 | if ((fetchedData & WorkshopQueryData.AdditionalPreviews) != 0) { 166 | WorkshopQueryResultAdditionalPreview[] previews = result.additionalPreviews = new WorkshopQueryResultAdditionalPreview[SteamUGC.GetQueryUGCNumAdditionalPreviews(handle, resulti)]; 167 | for (uint previewi = 0; previewi < previews.Length; previewi++) { 168 | string url; 169 | string name; 170 | EItemPreviewType type; 171 | if (SteamUGC.GetQueryUGCAdditionalPreview(handle, resulti, previewi, out url, 260, out name, 260, out type)) 172 | previews[previewi] = new WorkshopQueryResultAdditionalPreview(type == EItemPreviewType.k_EItemPreviewType_Image, url); 173 | } 174 | } 175 | 176 | if ((fetchedData & WorkshopQueryData.Statistics) != 0) { 177 | uint[] stats = result.statistics = new uint[8]; 178 | for (WorkshopResultStatistic stat = WorkshopResultStatistic.NumSubscriptions; (int) stat < stats.Length; stat++) { 179 | ulong val; 180 | if (SteamUGC.GetQueryUGCStatistic(handle, resulti, (EItemStatistic) stat, out val)) 181 | stats[(int) stat] = (uint) val; 182 | } 183 | } 184 | 185 | ResultFetched?.Invoke(this, result); 186 | } 187 | 188 | if (numResultsFetched != numResultsTotal && !justOnePage) { 189 | Destroy(); 190 | page++; 191 | Create(); 192 | Request(); 193 | return; 194 | } 195 | 196 | Finish: 197 | QueryFinished?.Invoke(this); 198 | } 199 | 200 | protected virtual void Dispose(bool flag) { 201 | Destroy(); 202 | 203 | _completedCallResult?.Cancel(); 204 | (_completedCallResult as IDisposable)?.Dispose(); 205 | _completedCallResult = null; 206 | } 207 | 208 | public void Dispose() { 209 | Dispose(true); 210 | GC.SuppressFinalize(this); 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [Flags] 4 | public enum WorkshopQueryData { 5 | Statistics = 32, 6 | LongDescription = 16, 7 | AdditionalPreviews = 8, 8 | Children = 4, 9 | Metadata = 2, 10 | PreviewURL = 1, 11 | Details = 0, 12 | TotalOnly = -1 13 | } 14 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryFileDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Steamworks; 4 | 5 | public class WorkshopQueryFileDetails : WorkshopQueryBase { 6 | 7 | public IList files { get; internal set; } 8 | 9 | public WorkshopQueryFileDetails() { 10 | files = new List(); 11 | } 12 | 13 | internal unsafe override void Create() { 14 | _handle = SteamUGC.CreateQueryUGCDetailsRequest(_.GetArray(files, id => new PublishedFileId_t(id)), (uint) files.Count); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryFilterOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum WorkshopQueryFilterOrder { 4 | RankedByTotalUniqueSubscriptions = 12, 5 | RankedByTextSearch = 11, 6 | RankedByVotesUp = 10, 7 | RankedByTotalVotesAsc = 9, 8 | NotYetRated = 8, 9 | CreatedByFollowedUsersRankedByPublicationDate = 7, 10 | RankedByNumTimesReported = 6, 11 | CreatedByFriendsRankedByPublicationDate = 5, 12 | FavoritedByFriendsRankedByPublicationDate = 4, 13 | RankedByTrend = 3, 14 | AcceptedForGameRankedByAcceptanceDate = 2, 15 | RankedByPublicationDate = 1, 16 | RankedByVote = 0 17 | } 18 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class WorkshopQueryResult { 4 | public WorkshopQueryResult() { 5 | } 6 | 7 | public WorkshopQueryResultDetails details; 8 | 9 | public string previewURL; 10 | 11 | public string metadata; 12 | 13 | public WorkshopItem[] fileList; 14 | 15 | public WorkshopQueryResultAdditionalPreview[] additionalPreviews; 16 | 17 | public uint[] statistics; 18 | } 19 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryResultAdditionalPreview.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class WorkshopQueryResultAdditionalPreview { 4 | public WorkshopQueryResultAdditionalPreview(bool isImage, string urlOrVideoID) { 5 | this.isImage = isImage; 6 | this.urlOrVideoID = urlOrVideoID; 7 | } 8 | 9 | public bool isImage; 10 | 11 | public string urlOrVideoID; 12 | } 13 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryResultDetails.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | 4 | public class WorkshopQueryResultDetails { 5 | public WorkshopQueryResultDetails() { 6 | } 7 | 8 | public WorkshopItem publishedFile; 9 | 10 | public EResult result; 11 | 12 | public EWorkshopFileType fileType; 13 | 14 | public string title; 15 | 16 | public string description; 17 | 18 | public ulong steamIDOwner; 19 | 20 | public uint timeCreated; 21 | 22 | public uint timeUpdated; 23 | 24 | public uint timeAddedToUserList; 25 | 26 | public ERemoteStoragePublishedFileVisibility visibility; 27 | 28 | public bool banned; 29 | 30 | public bool acceptedForUse; 31 | 32 | public bool tagsTruncated; 33 | 34 | public string[] tags; 35 | 36 | public ulong file; 37 | 38 | public ulong previewFile; 39 | 40 | public string fileName; 41 | 42 | public int fileSize; 43 | 44 | public int previewFileSize; 45 | 46 | public string URL; 47 | 48 | public uint votesUp; 49 | 50 | public uint votesDown; 51 | 52 | public float score; 53 | 54 | public uint numChildren; 55 | } 56 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryUGC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Steamworks; 4 | 5 | public abstract class WorkshopQueryUGC : WorkshopQueryBase { 6 | 7 | public IList requiredTags { get; internal set; } 8 | 9 | public IList excludedTags { get; internal set; } 10 | 11 | internal WorkshopQueryUGC() { 12 | requiredTags = new List(); 13 | excludedTags = new List(); 14 | } 15 | 16 | internal unsafe override void SetQueryData() { 17 | base.SetQueryData(); 18 | 19 | foreach (string tag in requiredTags) 20 | SteamUGC.AddRequiredTag(_handle, tag); 21 | 22 | foreach (string tag in excludedTags) 23 | SteamUGC.AddExcludedTag(_handle, tag); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Steam/src/WorkshopQueryUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Steamworks; 3 | 4 | public class WorkshopQueryUser : WorkshopQueryUGC { 5 | 6 | internal AccountID_t _accountID; 7 | 8 | internal EUserUGCList _listType; 9 | 10 | internal EUGCMatchingUGCType _type; 11 | 12 | internal EUserUGCListSortOrder _sortOrder; 13 | 14 | public string cloudNameFileFilter { get; set; } 15 | 16 | internal WorkshopQueryUser(uint unAccountID, EUserUGCList eListType, EUGCMatchingUGCType eMatchingUGCType, EUserUGCListSortOrder eSortOrder) { 17 | _accountID = new AccountID_t(unAccountID); 18 | _listType = eListType; 19 | _type = eMatchingUGCType; 20 | _sortOrder = eSortOrder; 21 | } 22 | 23 | internal unsafe override void Create() { 24 | _handle = SteamUGC.CreateQueryUserUGCRequest(_accountID, _listType, _type, _sortOrder, SteamUtils.GetAppID(), SteamUtils.GetAppID(), page); 25 | } 26 | 27 | internal unsafe override void SetQueryData() { 28 | base.SetQueryData(); 29 | 30 | SteamUGC.SetCloudFileNameFilter(_handle, cloudNameFileFilter); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Steam/src/WorkshopResultStatistic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum WorkshopResultStatistic { 4 | Total = 8, 5 | ReportScore = 7, 6 | NumUniqueWebsiteViews = 6, 7 | NumUniqueFollowers = 5, 8 | NumUniqueFavorites = 4, 9 | NumUniqueSubscriptions = 3, 10 | NumFollowers = 2, 11 | NumFavorites = 1, 12 | NumSubscriptions = 0 13 | } 14 | -------------------------------------------------------------------------------- /Steam/src/WorkshopSortOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum WorkshopSortOrder { 4 | ForModeration = 6, 5 | VoteScoreDesc = 5, 6 | SubscriptionDateDesc = 4, 7 | LastUpdatedDesc = 3, 8 | TitleAsc = 2, 9 | CreationOrderAsc = 1, 10 | CreationOrderDesc = 0 11 | } 12 | -------------------------------------------------------------------------------- /Steam/src/WorkshopType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum WorkshopType { 4 | Collections = 3, 5 | ControllerBindings = 11, 6 | UsableInGame = 10, 7 | IntegratedGuides = 9, 8 | WebGuides = 8, 9 | AllGuides = 7, 10 | Screenshots = 6, 11 | Videos = 5, 12 | Artwork = 4, 13 | Items_ReadyToUse = 2, 14 | Items_Mtx = 1, 15 | Items = 0 16 | } 17 | -------------------------------------------------------------------------------- /Steam/src/_.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | // Common helper class that turns out to be used ~ 2 times per function... 8 | internal static class _ { 9 | 10 | public static List GetList(int count, Func get) { 11 | if (count <= 0) 12 | return new List(); 13 | List list = new List(count); 14 | for (int i = 0; i < count; i++) 15 | list.Add(get(i)); 16 | return list; 17 | } 18 | 19 | public static TOut[] GetArray(IList list, Func get) { 20 | if (list.Count <= 0) 21 | return new TOut[0]; 22 | TOut[] array = new TOut[list.Count]; 23 | for (int i = 0; i < array.Length; i++) 24 | array[i] = get(list[i]); 25 | return array; 26 | } 27 | 28 | public static byte[] GetImageRGBA(int id) { 29 | uint w; 30 | uint h; 31 | if (!SteamUtils.GetImageSize(id, out w, out h)) 32 | return null; 33 | byte[] data = new byte[w * h * 4]; 34 | if (!SteamUtils.GetImageRGBA(id, data, data.Length)) 35 | return null; 36 | return data; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Steamworks.NET/CSteamworks.bundle/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 11G63 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | CSteamworks 11 | CFBundleIdentifier 12 | com.rileylabrecque.CSteamworks 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.30 21 | CSResourcesFileMapped 22 | yes 23 | DTCompiler 24 | 25 | DTPlatformBuild 26 | 4H1503 27 | DTPlatformVersion 28 | GM 29 | DTSDKBuild 30 | 11E52 31 | DTSDKName 32 | macosx10.7 33 | DTXcode 34 | 0463 35 | DTXcodeBuild 36 | 4H1503 37 | 38 | 39 | -------------------------------------------------------------------------------- /Steamworks.NET/CSteamworks.bundle/Contents/MacOS/CSteamworks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/CSteamworks.bundle/Contents/MacOS/CSteamworks -------------------------------------------------------------------------------- /Steamworks.NET/CSteamworks.bundle/Contents/MacOS/libsteam_api.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/CSteamworks.bundle/Contents/MacOS/libsteam_api.dylib -------------------------------------------------------------------------------- /Steamworks.NET/CSteamworks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/CSteamworks.dll -------------------------------------------------------------------------------- /Steamworks.NET/Steamworks.NET-linux.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/Steamworks.NET-linux.dll -------------------------------------------------------------------------------- /Steamworks.NET/Steamworks.NET-stubbed.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/Steamworks.NET-stubbed.dll -------------------------------------------------------------------------------- /Steamworks.NET/Steamworks.NET.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/Steamworks.NET.dll -------------------------------------------------------------------------------- /Steamworks.NET/libCSteamworks.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/libCSteamworks.so -------------------------------------------------------------------------------- /Steamworks.NET/libsteam_api.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/libsteam_api.so -------------------------------------------------------------------------------- /Steamworks.NET/steam_api64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/Steamworks.NET/steam_api64.dll -------------------------------------------------------------------------------- /libs/DuckGame.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/libs/DuckGame.exe -------------------------------------------------------------------------------- /libs/FNA.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/libs/FNA.dll -------------------------------------------------------------------------------- /libs/README.md: -------------------------------------------------------------------------------- 1 | # The DuckGame.exe is mono-cil-stripped! 2 | It doesn't contain any code and is only provided to ensure that the "mod" compiles properly out of the box. 3 | If any questions arise, don't hesitate to contact me. 4 | -------------------------------------------------------------------------------- /libs/XnaToFna.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x0ade/DuckGame-Linux/a2b51193c36d440bdab402f43b7485bb940d1fbd/libs/XnaToFna.exe --------------------------------------------------------------------------------