Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9b6bb11
Add Any CPU support, inspired by @Chicken-Bones
Akarinnnnn Aug 18, 2025
2f1c2e2
Merge remote-tracking branch 'origin/master' into anycpu-2
Akarinnnnn Aug 18, 2025
4f9526c
Imporve Any CPU compatibility
Akarinnnnn Aug 19, 2025
a1a7e88
Pack steam natives we building against into AnyCPU package
Akarinnnnn Sep 3, 2025
4795f76
Fix anycpu hook coverage and a typo.
Akarinnnnn Sep 20, 2025
e14303f
Changed when to check steam native requester. Fix indentation.
Akarinnnnn Oct 1, 2025
68e72a4
Explain why not check platform at initialization stage.
Akarinnnnn Oct 1, 2025
b3fbffb
Fix most callback system related marshal issues.
Akarinnnnn Oct 2, 2025
34f1c9b
add a github workflow to pack and push nuget package
ryan-linehan Oct 29, 2025
58372e6
update workflow to take inputs for version and publishing
ryan-linehan Oct 29, 2025
70d6595
update action name
ryan-linehan Oct 29, 2025
b4f588c
add tag versioning for publishing nuget package + add approver step f…
ryan-linehan Oct 30, 2025
9accc2c
default version to 0.0.0 so build can still run on push
ryan-linehan Oct 30, 2025
9dce2a3
remove branch trigger on push to + utilize proper variable syntax for…
ryan-linehan Oct 30, 2025
c0a00b5
Add workflow for packing and publishing steamworks Steamworks.NET.Any…
Akarinnnnn Oct 30, 2025
f74fd46
Merge upstream changes.
Akarinnnnn Nov 1, 2025
6e89074
Create bug report template
Akarinnnnn Nov 15, 2025
924a234
Create bug report template (#11)
Akarinnnnn Nov 15, 2025
9c01d44
Simplify marshal fix review (#12)
Akarinnnnn Dec 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: bug
assignees: Akarinnnnn

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 22]

**Steamworks.NET.AnyCPU Version (Version String For NuGet, Commit For Source Built)**


**Additional context**
Add any other context about the problem here.
92 changes: 92 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Build & Publish Steamworks.NET.AnyCPU
on:
workflow_dispatch:
inputs:
version:
description: "Package version (e.g., 2025.162.1)"
required: false
type: string
push:
tags:
- "[0-9][0-9][0-9][0-9].[0-9]*.[0-9]*" # Matches YYYY.*.* format like 2025.162.5
- "[0-9][0-9][0-9][0-9].[0-9]*.[0-9]*-*" # Matches YYYY.*.*-* format like 2025.162.5-beta1

permissions:
id-token: write # Required for OIDC authentication
contents: read # Required to checkout code

jobs:
build:
name: Pack and create artifact
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: true

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x

- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION"
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
echo "Using tag version: $VERSION"
else
VERSION=""
fi

# Set default version if empty
if [ -z "$VERSION" ] || [ "$VERSION" = "" ]; then
VERSION="0.0.0"
echo "Version was empty, using default: $VERSION"
fi

echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Pack Steamworks.NET.AnyCPU
run: |
dotnet pack ./Standalone3.0/Steamworks.NET.csproj \
--configuration Release \
-p:Version=${{ steps.version.outputs.version }}

- name: Upload NuGet package artifact
uses: actions/upload-artifact@v4
with:
name: nuget-package
path: ./Standalone3.0/bin/Release/*.nupkg
retention-days: 30

publish:
name: Publish ${{ needs.build.outputs.version }} to nuget.org
runs-on: ubuntu-latest
environment: prod
needs: build
if: needs.build.outputs.version != '0.0.0'

steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: nuget-package
path: ./packages

- name: NuGet login
uses: NuGet/login@v1
id: login
with:
user: ${{ vars.NUGET_USER }} # Set this variable in workflows > vars

- name: NuGet push
run: |
dotnet nuget push ./packages/*.nupkg --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
95 changes: 88 additions & 7 deletions CodeGen/src/structs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import sys
from copy import deepcopy
from SteamworksParser import steamworksparser

g_TypeConversionDict = {
Expand Down Expand Up @@ -102,11 +103,14 @@ def main(parser):

lines = []
callbacklines = []

anyCpuConditionalMarshallerLines = [] # Contains conditional marshaller code only

for f in parser.files:
for struct in f.structs:
lines.extend(parse(struct))
lines.extend(parse(struct, True, anyCpuConditionalMarshallerLines))
for callback in f.callbacks:
callbacklines.extend(parse(callback))
callbacklines.extend(parse(callback, True, anyCpuConditionalMarshallerLines))

with open("../com.rlabrecque.steamworks.net/Runtime/autogen/SteamStructs.cs", "wb") as out:
with open("templates/header.txt", "r") as f:
Expand All @@ -125,8 +129,23 @@ def main(parser):
out.write(bytes(line + "\n", "utf-8"))
out.write(bytes("}\n\n", "utf-8"))
out.write(bytes("#endif // !DISABLESTEAMWORKS\n", "utf-8"))

with open("../Standalone3.0/ConditionalMarshallerTable.g.cs", "wb") as out:
with open("templates/header.txt", "r") as f:
out.write(bytes(f.read(), "utf-8"))

with open("templates/anycpu/ConditionalMarshallerTable.head.cs", "r") as f:
out.write(bytes(f.read(), "utf-8"))

for line in anyCpuConditionalMarshallerLines:
out.write(bytes("\t\t\t" + line + "\n", "utf-8"))

with open("templates/anycpu/ConditionalMarshallerTable.tail.cs", "r") as f:
out.write(bytes(f.read(), "utf-8"))

out.write(bytes("#endif // !DISABLESTEAMWORKS\n", "utf-8"))

def parse(struct):
def parse(struct, isMainStruct, marshalTableLines: list[str]):
if struct.name in g_SkippedStructs:
return []

Expand All @@ -136,11 +155,13 @@ def parse(struct):
continue
lines.append("\t" + comment)

structname = struct.name
structname: str = struct.name

packsize = g_CustomPackSize.get(structname, "Packsize.value")
isExplicitStruct = False
if g_ExplicitStructs.get(structname, False):
lines.append("\t[StructLayout(LayoutKind.Explicit, Pack = " + packsize + ")]")
isExplicitStruct = True
elif struct.packsize:
customsize = ""
if len(struct.fields) == 0:
Expand All @@ -155,15 +176,24 @@ def parse(struct):
lines.append("\t[StructLayout(LayoutKind.Sequential)]")
break

lines.append("\tpublic struct " + structname + " {")
if isMainStruct:
lines.append("\tpublic struct " + structname + " {")
else:
lines.append("\tinternal struct " + structname + " {")


lines.extend(insert_constructors(structname))

if struct.callbackid:
lines.append("\t\tpublic const int k_iCallback = Constants." + struct.callbackid + ";")

for field in struct.fields:
lines.extend(parse_field(field, structname))
fieldHandlingStructName = structname

if "_LargePack" in structname or "_SmallPack" in structname:
fieldHandlingStructName = fieldHandlingStructName[:structname.rindex("_")]

lines.extend(parse_field(field, fieldHandlingStructName))

if struct.endcomments:
for comment in struct.endcomments.rawprecomments:
Expand All @@ -172,11 +202,62 @@ def parse(struct):
else:
lines.append("\t" + comment)

# Generate Any CPU marshal helper
if isMainStruct and packsize == "Packsize.value" and not isExplicitStruct:
marshalTableLines.append(f"marshallers.Add(typeof({structname}), (unmanaged) => {{")
marshalTableLines.append(f"\t{structname} result = default;")
marshalTableLines.append("")
marshalTableLines.append("\tif (Packsize.IsLargePack) {")
marshalTableLines.append(f"\t\tvar value = System.Runtime.InteropServices.Marshal.PtrToStructure<{structname}_LargePack>(unmanaged);")

for field in struct.fields:
gen_fieldcopycode(field, structname, marshalTableLines)

marshalTableLines.append("\t} else {")
marshalTableLines.append(f"\t\tvar value = System.Runtime.InteropServices.Marshal.PtrToStructure<{structname}_SmallPack>(unmanaged);")

for field in struct.fields:
gen_fieldcopycode(field, structname, marshalTableLines)

marshalTableLines.append("\t}")
marshalTableLines.append("")
marshalTableLines.append("\treturn result;")
marshalTableLines.append("});")

pass

lines.append("\t}")
lines.append("")

# Generate Any CPU struct variant for default pack-sized structs
if isMainStruct and packsize == "Packsize.value" and not isExplicitStruct:
lines.append("\t#if STEAMWORKS_ANYCPU")

largePackStruct = struct
largePackStruct.name = structname + "_LargePack"
largePackStruct.packsize = 8
lines.extend(parse(largePackStruct, False, marshalTableLines))

lines.append("")

smallPackStruct = struct
smallPackStruct.name = structname + "_SmallPack"
smallPackStruct.packsize = 4
lines.extend(parse(smallPackStruct, False, marshalTableLines))

lines.append("\t#endif")

return lines

def gen_fieldcopycode(field, structname, marshalTableLines):
fieldtype = g_TypeConversionDict.get(field.type, field.type)
fieldtype = g_SpecialFieldTypes.get(structname, dict()).get(field.name, fieldtype)

if field.arraysize and fieldtype == "string":
marshalTableLines.append(f"\t\tresult.{field.name}_ = value.{field.name}_;")
else:
marshalTableLines.append(f"\t\tresult.{field.name} = value.{field.name};")

def parse_field(field, structname):
lines = []
for comment in field.c.rawprecomments:
Expand Down Expand Up @@ -213,7 +294,7 @@ def parse_field(field, structname):
lines.append("\t\t[MarshalAs(UnmanagedType.I1)]")

if field.arraysize and fieldtype == "string[]":
lines.append("\t\tprivate byte[] " + field.name + "_;")
lines.append("\t\tinternal byte[] " + field.name + "_;")
lines.append("\t\tpublic string " + field.name + comment)
lines.append("\t\t{")
lines.append("\t\t\tget { return InteropHelp.ByteArrayToStringUTF8(" + field.name + "_); }")
Expand Down
19 changes: 19 additions & 0 deletions CodeGen/templates/anycpu/ConditionalMarshallerTable.head.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <auto-generated/>
// This file is generated by CodeGen/src/struct.py

#if !STEAMWORKS_ANYCPU
#error This file for Any CPU variant, not meaningful to any other platform.
#endif

using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Steamworks {
internal static partial class ConditionalMarshallerTable {
static ConditionalMarshallerTable() {
Dictionary<Type, Func<IntPtr, object>> marshallers = new();

4 changes: 4 additions & 0 deletions CodeGen/templates/anycpu/ConditionalMarshallerTable.tail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s_marshallerLookupTable = FrozenDictionary.ToFrozenDictionary(marshallers);
}
}
}
19 changes: 19 additions & 0 deletions Standalone3.0/ConditionalMarshallerTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;

namespace Steamworks
{
internal static partial class ConditionalMarshallerTable
{
private static readonly FrozenDictionary<Type, Func<IntPtr, object>> s_marshallerLookupTable;

// partial, in generated file
// static ConditionalMarshallerTable();

public static T Marshal<T>(IntPtr unmanagetype)
{
return (T)s_marshallerLookupTable[typeof(T)](unmanagetype);
}
}
}
Loading