Files
oniagent/scripts/fix_csharp.py
root 64e57b2c44 build: C# 6 compatible port + build script + csproj
- Convert all C# 7/8 features to C# 6 for mono mcs compatibility:
  - Tuple switch → if-else chain
  - Switch expressions → if-else blocks
  - Expression-bodied switch → regular switch statement
- Verified compilation succeeds (only missing game DLL refs)
- Added mod/ONIAgentBridge.csproj for dotnet SDK builds
- Added scripts/build_mod.sh for mono-based builds
  - Auto-detects ONI installation path
  - References required game DLLs
  - Outputs to mod/bin/
2026-05-22 10:39:17 +08:00

182 lines
7.0 KiB
Python

#!/usr/bin/env python3
"""Convert C# 7/8 features in ONIAgentBridge.cs to C# 6 compatible code."""
import re
with open('mod/ONIAgentBridge.cs', 'r') as f:
content = f.read()
# 1. Convert tuple switch at ProcessRequest to if-else chain
# Find the switch and replace it
old_switch_start = """ switch (path, method)
{"""
# Read the entire switch block and reconstruct
lines = content.split('\n')
new_lines = []
i = 0
while i < len(lines):
line = lines[i]
# Find the C# 7 tuple switch
if 'switch (path, method)' in line and '{' in lines[i+1]:
new_lines.append(' // --- C# 6 compatible dispatch ---')
new_lines.append(' bool _handled = false;')
i += 2 # skip "switch (path, method)" and "{"
while i < len(lines):
stripped = lines[i].strip()
# Check for case pattern: case ("...", "..."):
m = re.match(r'case \("([^"]+)", "([^"]+)"\):', stripped)
if m:
endpoint = m.group(1)
method = m.group(2)
indent = lines[i][:len(lines[i]) - len(lines[i].lstrip())]
new_lines.append(f'{indent}else if (path == "{endpoint}" && method == "{method}")')
new_lines.append(f'{indent}{{')
new_lines.append(f'{indent} _handled = true;')
i += 1
# Copy lines until "break;"
while i < len(lines) and lines[i].strip() != 'break;':
new_lines.append(lines[i])
i += 1
new_lines.append(f'{indent}}}')
i += 1 # skip "break;"
continue
# Check for default case
if stripped == 'default:':
i += 1
# Skip the opening brace
if lines[i].strip() == '{':
i += 1
new_lines.append(' if (!_handled)')
new_lines.append(' {')
while i < len(lines):
if lines[i].strip() == '}' and not _is_end_of_method(lines, i):
# This could be the switch closing brace
# Check if next line is the method closing
break
new_lines.append(lines[i])
i += 1
new_lines.append(' }')
# Skip the closing brace of the switch
if lines[i].strip() == '}':
i += 1
continue
# Check for closing brace of switch
if stripped == '}':
i += 1
break
# Any other content inside switch (shouldn't happen)
new_lines.append(lines[i])
i += 1
continue
new_lines.append(line)
i += 1
content = '\n'.join(new_lines)
def _is_end_of_method(lines, idx):
"""Check if this } ends a method (next non-blank is a method or class member)."""
for j in range(idx+1, min(idx+10, len(lines))):
s = lines[j].strip()
if s == '' or s.startswith('//') or s.startswith('/*'):
continue
if s.startswith('private ') or s.startswith('public ') or s.startswith('internal ') or s.startswith('static ') or s.startswith('}'):
return True
return False
return False
# 2. Fix switch expressions (C# 8): `wireType switch { ... }` -> if-else
# Pattern: string buildingId = wireType switch { "heavy" => "HeaviWatWire", ... }
def fix_switch_expr(match):
full = match.group(0)
var_name = match.group(1)
# Parse the switch arms
# Extract the expression after "= "
body = match.group(2)
return full # placeholder, we'll handle below
# Fix: string buildingId = wireType switch { "heavy" => "HeaviWatWire", _ => "Wire" };
# Convert to: string buildingId; if (wireType == "heavy") buildingId = "HeaviWatWire"; else ...
for pattern in [
(r'wireType switch\s*\{([^}]+)\}', 'wireType'),
(r'error switch\s*\{([^}]+)\}', 'error'),
(r'i switch\s*\{([^}]+)\}', 'i'),
]:
pat, varname = pattern
matches = list(re.finditer(pat, content))
for m in reversed(matches):
body = m.group(1)
arms = re.findall(r'"([^"]*)"\s*=>\s*"([^"]*)"', body)
default = re.findall(r'_\s*=>\s*(null|"[^"]*")', body)
if varname == 'i':
# label assignment
replacements = []
for key, val in arms:
replacements.append(f' if ({varname} == {key}) label = "{val}";')
if default:
replacements.append(f' else label = {default[0]};')
new_code = '\n'.join(replacements)
else:
# Find the variable being assigned
start = m.start()
# Look backwards to find the variable name
line_start = content.rfind('\n', 0, start) + 1
line_before = content[line_start:start]
var_match = re.match(r'\s*(?:\w+\s+)?(\w+)\s*=\s*$', line_before)
indent = ' ' * 16
if varname == 'wireType':
# Multiple instances in wire building, find each by context
context_before = content[max(0,start-200):start]
if 'bridgeId' in context_before:
varname_full = 'bridgeId'
else:
varname_full = 'buildingId'
else:
varname_full = varname
lines_code = [f' string {varname_full};']
first = True
for key, val in arms:
prefix = 'if' if first else 'else if'
lines_code.append(f' {prefix} ({varname} == "{key}") {varname_full} = "{val}";')
first = False
if default:
dval = default[0]
lines_code.append(f' else {varname_full} = {dval};')
new_code = '\n'.join(lines_code)
# Replace the assignment line + switch expression
# Find the full assignment
assign_start = content.rfind('\n', 0, start)
assign_end = m.end()
while assign_end < len(content) and content[assign_end] != '\n':
assign_end += 1
old = content[assign_start:assign_end]
content = content[:assign_start] + '\n' + new_code + content[assign_end:]
# Write result
with open('mod/ONIAgentBridge.cs', 'w') as f:
f.write(content)
print('Done. Checking for remaining C# 7+ features...')
# Verify
remaining = re.findall(r'\bswitch\b', content)
print(f'Remaining "switch" keywords: {len(remaining)}')
# Count them
for i, line in enumerate(content.split('\n')):
if 'switch' in line and not line.strip().startswith('//') and 'ProcessRequest' not in line:
sline = line.strip()
if 'if (' not in line and 'dictionary' not in line.lower() and 'dispatch' not in line:
print(f' Line ~{i}: {sline[:80]}')