Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
66442b7
fix: Ensure CPlayer vtable validity before virtual calls
ELDment Nov 15, 2025
66083b8
fix: Ensure options are properly locked for thread safety
ELDment Nov 15, 2025
6ca89fc
fix: Add null check for CPlayer pointer before vtable validation
ELDment Nov 15, 2025
a9d0bc9
Merge pull request #100 from ELDment/beta-2
skuzzis Nov 15, 2025
380d2ea
fix(managed): Fix DropWeapon
samyycX Nov 16, 2025
0b38fe1
feat(managed): More math methods
samyycX Nov 16, 2025
dc2ecf6
feat(steamworks): Add ToSteamIdOnline
samyycX Nov 16, 2025
802a1fb
feat(managed): Add RunCommand event
samyycX Nov 16, 2025
ff21db8
fix(managed): fix build
samyycX Nov 16, 2025
9c74ddf
fix(managed): Move steamid online to proper place
samyycX Nov 16, 2025
66c1e80
fix(generator): Clean unused method
samyycX Nov 16, 2025
2d0acb7
feat(managed): Expose soundevent guid
samyycX Nov 16, 2025
947d1bd
feat(API): OnWorldUpdate + NextWorldUpdate
skuzzis Nov 16, 2025
b414052
Merge branch 'beta' of https://github.com/swiftly-solution/swiftlys2 …
skuzzis Nov 16, 2025
8f34016
fix(events): OnWorldUpdate register
skuzzis Nov 16, 2025
f983a10
feat(BasePlugin): OnSharedInterfaceInjected
skuzzis Nov 16, 2025
ec5c4af
feat(BasePlugin): OnAllPluginsLoaded
skuzzis Nov 16, 2025
fad2892
perf(netmessages): Send player mask
skuzzis Nov 16, 2025
893d3f4
update(SetClientConvar): Use MessageID
skuzzis Nov 16, 2025
12f5c1b
update(template): update target framework to .NET 10
m3ntorsky Nov 16, 2025
c35ae56
[skip ci] Merge pull request #103 from m3ntorsky/master
skuzzis Nov 16, 2025
20bbfb7
fix(status): Map Name
skuzzis Nov 16, 2025
abcc059
revert(natives): String changes
skuzzis Nov 17, 2025
816dd56
perf(native): PostEventAbstract calls
skuzzis Nov 17, 2025
60e258f
feat(API/Engine): WorkshopId
skuzzis Nov 17, 2025
06f47a6
chore(refactor): editorconfig
oscar-wos Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
162 changes: 75 additions & 87 deletions generator/native_generator/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(self):

def add_line(self, text: str = ""):
if text.strip():
self.lines.append(" " * self.indent_level + text)
self.lines.append(" " * self.indent_level + text)
else:
self.lines.append("")

Expand All @@ -62,15 +62,14 @@ def dedent(self):
self.indent_level = max(0, self.indent_level - 1)

def add_block(self, header: str, content_func):
self.add_line(header)
self.add_line("{")
self.add_line(header + " {")
self.indent()
content_func()
self.dedent()
self.add_line("}")

def get_code(self) -> str:
return "\r\n".join(self.lines)
return "\n".join(self.lines)


def split_by_last_dot(value: str):
Expand All @@ -95,6 +94,7 @@ def parse_native(lines: list[str]):
writer.add_line("#pragma warning disable CS0649")
writer.add_line("#pragma warning disable CS0169")
writer.add_line()
writer.add_line("using System.Buffers;")
writer.add_line("using System.Text;")
writer.add_line("using System.Threading;")
writer.add_line("using SwiftlyS2.Shared.Natives;")
Expand Down Expand Up @@ -158,68 +158,24 @@ def write_method_content():
if is_marked_sync:
writer.add_block("if (Thread.CurrentThread.ManagedThreadId != _MainThreadID)", lambda: writer.add_line('throw new InvalidOperationException("This method can only be called from the main thread.");'))

string_params = [n for t, n in param_signatures if t == "string"]
bytes_params = [n for t, n in param_signatures if t == "byte[]"]

for param in bytes_params:
writer.add_line(f"var {param}Length = {param}.Length;")

if not string_params:
fixed_blocks = []
for param in bytes_params:
fixed_blocks.append(f"fixed (byte* {param}BufferPtr = {param})")

def write_simple_call():
call_args = []
for t, n in param_signatures:
if t == "byte[]":
call_args.extend([f"{n}BufferPtr", f"{n}Length"])
elif t == "bool":
call_args.append(f"{n} ? (byte)1 : (byte)0")
else:
call_args.append(n)

if is_buffer_return(return_type):
first_call_args = ["null"] + call_args
writer.add_line(f"var ret = _{function_name}({', '.join(first_call_args)});")
if return_type == "string":
writer.add_line("var retBuffer = new byte[ret + 1];")
else:
writer.add_line("var retBuffer = new byte[ret];")

def write_ret_fixed():
second_call_args = ["retBufferPtr"] + call_args
writer.add_line(f"ret = _{function_name}({', '.join(second_call_args)});")
if return_type == "string":
writer.add_line("return Encoding.UTF8.GetString(retBufferPtr, ret);")
else:
writer.add_line("var retBytes = new byte[ret];")
writer.add_line("for (int i = 0; i < ret; i++) retBytes[i] = retBufferPtr[i];")
writer.add_line("return retBytes;")

writer.add_block("fixed (byte* retBufferPtr = retBuffer)", write_ret_fixed)
else:
if return_type == "void":
writer.add_line(f"_{function_name}({', '.join(call_args)});")
else:
writer.add_line(f"var ret = _{function_name}({', '.join(call_args)});")
writer.add_line("return ret == 1;" if return_type == "bool" else "return ret;")

def write_with_fixed_blocks(blocks, index=0):
if index < len(blocks):
writer.add_block(blocks[index], lambda: write_with_fixed_blocks(blocks, index + 1))
else:
write_simple_call()

if fixed_blocks:
write_with_fixed_blocks(fixed_blocks)
else:
write_simple_call()
return

for param in string_params:
writer.add_line(f"byte[] {param}Buffer = Encoding.UTF8.GetBytes({param} + \"\\0\");")
string_params = []
bytes_params = []
pool_declared = False

for t, n in param_signatures:
if t == "string":
if not pool_declared:
writer.add_line("var pool = ArrayPool<byte>.Shared;")
pool_declared = True
writer.add_line(f"var {n}Length = Encoding.UTF8.GetByteCount({n});")
writer.add_line(f"var {n}Buffer = pool.Rent({n}Length + 1);")
writer.add_line(f"Encoding.UTF8.GetBytes({n}, {n}Buffer);")
writer.add_line(f"{n}Buffer[{n}Length] = 0;")
string_params.append(n)
elif t == "byte[]":
writer.add_line(f"var {n}Length = {n}.Length;")
bytes_params.append(n)

fixed_blocks = []
for param in string_params:
fixed_blocks.append(f"fixed (byte* {param}BufferPtr = {param}Buffer)")
Expand All @@ -228,42 +184,77 @@ def write_with_fixed_blocks(blocks, index=0):

def write_native_call():
call_args = []
for t, n in param_signatures:
if t == "string":
call_args.append(f"{n}BufferPtr")
elif t == "byte[]":
call_args.extend([f"{n}BufferPtr", f"{n}Length"])
elif t == "bool":
call_args.append(f"{n} ? (byte)1 : (byte)0")
else:
call_args.append(n)

if is_buffer_return(return_type):
first_call_args = ["null"] + call_args
first_call_args = ["null"]
for t, n in param_signatures:
if t == "string":
first_call_args.append(f"{n}BufferPtr")
elif t == "byte[]":
first_call_args.extend([f"{n}BufferPtr", f"{n}Length"])
elif t == "bool":
first_call_args.append(f"{n} ? (byte)1 : (byte)0")
else:
first_call_args.append(n)

writer.add_line(f"var ret = _{function_name}({', '.join(first_call_args)});")
if return_type == "string":
writer.add_line("var retBuffer = new byte[ret + 1];")
else:
writer.add_line("var retBuffer = new byte[ret];")

if not pool_declared:
writer.add_line("var pool = ArrayPool<byte>.Shared;")
writer.add_line("var retBuffer = pool.Rent(ret + 1);")

def write_ret_fixed():
second_call_args = ["retBufferPtr"] + call_args
second_call_args = ["retBufferPtr"]
for t, n in param_signatures:
if t == "string":
second_call_args.append(f"{n}BufferPtr")
elif t == "byte[]":
second_call_args.extend([f"{n}BufferPtr", f"{n}Length"])
elif t == "bool":
second_call_args.append(f"{n} ? (byte)1 : (byte)0")
else:
second_call_args.append(n)

writer.add_line(f"ret = _{function_name}({', '.join(second_call_args)});")

if return_type == "string":
writer.add_line("return Encoding.UTF8.GetString(retBufferPtr, ret);")
writer.add_line("var retString = Encoding.UTF8.GetString(retBufferPtr, ret);")
writer.add_line("pool.Return(retBuffer);")
for param in string_params:
writer.add_line(f"pool.Return({param}Buffer);")
writer.add_line("return retString;")
else:
writer.add_line("var retBytes = new byte[ret];")
writer.add_line("for (int i = 0; i < ret; i++) retBytes[i] = retBufferPtr[i];")
writer.add_line("pool.Return(retBuffer);")
for param in string_params:
writer.add_line(f"pool.Return({param}Buffer);")
writer.add_line("return retBytes;")

writer.add_block("fixed (byte* retBufferPtr = retBuffer)", write_ret_fixed)

else:
for t, n in param_signatures:
if t == "string":
call_args.append(f"{n}BufferPtr")
elif t == "byte[]":
call_args.extend([f"{n}BufferPtr", f"{n}Length"])
elif t == "bool":
call_args.append(f"{n} ? (byte)1 : (byte)0")
else:
call_args.append(n)

if return_type == "void":
writer.add_line(f"_{function_name}({', '.join(call_args)});")
else:
writer.add_line(f"var ret = _{function_name}({', '.join(call_args)});")
writer.add_line("return ret == 1;" if return_type == "bool" else "return ret;")


for param in string_params:
writer.add_line(f"pool.Return({param}Buffer);")

if return_type != "void":
writer.add_line("return ret == 1;" if return_type == "bool" else f"return ret;")

def write_with_fixed_blocks(blocks, index=0):
if index < len(blocks):
writer.add_block(blocks[index], lambda: write_with_fixed_blocks(blocks, index + 1))
Expand All @@ -276,10 +267,7 @@ def write_with_fixed_blocks(blocks, index=0):
write_native_call()

writer.add_block(f"public unsafe static {RETURN_TYPE_MAP[return_type]} {function_name}({method_signature})", write_method_content)

# Benchmark class should be public for external access
access_modifier = "public" if class_name == "Benchmark" else "internal"
writer.add_block(f"{access_modifier} static class Native{class_name}", write_class_content)
writer.add_block(f"internal static class Native{class_name}", write_class_content)

with open(out_path, "w", encoding="utf-8", newline="") as f:
f.write(writer.get_code())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public EUniverse GetEUniverse()
return (EUniverse)((m_SteamID >> 56) & 0xFFul);
}


public bool IsValid()
{
if (GetEAccountType() <= EAccountType.k_EAccountTypeInvalid || GetEAccountType() >= EAccountType.k_EAccountTypeMax)
Expand Down
Loading
Loading