Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added .DS_Store
Binary file not shown.
57 changes: 57 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,60 @@ root = true

[*.cs]
file_header_template = SPDX-License-Identifier: LGPL-3.0-or-later

# ---------------------------------------------------------------------------
# Path-scoped analyzer suppressions
#
# Each section below suppresses a specific rule ID in a specific file. The
# scope is deliberately file-level so that the exception cannot silently
# leak to unrelated code. If you add a new suppression, document *why* the
# rule is wrong for that file, not just what is being turned off.
# ---------------------------------------------------------------------------

# ConfigurationManagerAttributes.cs is the copy-paste template that every
# LMM/BepInEx plugin bundles into its own project. The class is discovered
# by simple type name from each plugin's bundled copy, and both this fork
# and upstream BepInEx.ConfigurationManager read values via
# Type.GetFields(BindingFlags.Instance | BindingFlags.Public). Encapsulating
# the fields as properties would break the reflection contract with every
# plugin that has already copied this template. Public fields are the
# deliberate wire format, not an oversight.
[ConfigurationManagerAttributes.cs]
dotnet_diagnostic.S1104.severity = none

# LmmSettingEntryTests.cs contains a nested stub type that intentionally
# mirrors the public-field shape of ConfigurationManagerAttributes to
# exercise SetFromAttributes' reflection path. The stub's fields are
# populated via object-initializer syntax inside the test body, but because
# the enclosing type is private, the net35 compiler does not credit that
# assignment and emits CS0649 ("field is never assigned to"). The whole
# point of the test is to simulate a foreign plugin's copy of the
# template, so rewriting the stub to silence the warning would defeat the
# test.
[LmmSettingEntryTests.cs]
dotnet_diagnostic.CS0649.severity = none

# CommonHelpers.OpenWebsite takes a URL as a plain string because the URL
# arrives as string metadata read out of the game (plugin info, mod config,
# etc.) where there is no System.Uri available. CA1054 ("URI parameters
# should not be strings") correctly flags that pattern in general, but the
# source here is already string-shaped and conversion would just push the
# same unvalidated data through an extra round-trip. Scope note: this is
# slightly wider than the previous per-member suppression — every method
# in CommonHelpers.cs is now exempt from CA1054. OpenWebsite is the only
# URL-shaped signature in the file today; if a future helper introduces a
# genuinely validatable URI parameter, prefer fixing that site over
# leaning on this scope.
[CommonHelpers.cs]
dotnet_diagnostic.CA1054.severity = none

# ExcludeFromCodeCoverageAttribute.cs is a net35 BCL polyfill: the real
# System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute was
# added in .NET 4.0, so this project ships its own copy under the BCL
# namespace. IDE0130 ("namespace should match folder structure") fires
# because the file lives under Polyfills/ but declares
# namespace System.Diagnostics.CodeAnalysis — that mismatch is the whole
# point of a polyfill and matching the folder name would break the
# compatibility story.
[ExcludeFromCodeCoverageAttribute.cs]
dotnet_diagnostic.IDE0130.severity = none
30 changes: 26 additions & 4 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ Tests live in `LobCorp.ConfigurationManager.Test` (xunit.v3, Moq, AwesomeAsserti

**Settings discovery (`Implementations/SettingSearcher.cs`):**
- LMM mods register via `Config/LmmConfigRegistration.cs` static API
- Auto-scans `BaseMods/{modId}/config.cfg` files
- Auto-scans `{persistentDataPath}/LobotomyBaseMod/{modId}/config.cfg` files
- Discovers BepInEx plugins via reflection (`Implementations/BepInExInterop.cs`) — no hard dependency
- Mod authors who want an optional-dependency path (settings UI when ConfigurationManager is installed, in-memory fallback otherwise) use the separately published [`LobotomyCorporation.Mods.ConfigurationManager.Integration`](https://github.com/open-lobotomy/LobotomyCorporation.Mods.ConfigurationManager.Integration) source-generator package. That repo owns the generator, the sample mod, and the author-facing docs.

**Configuration model (`Config/`):**
- `LmmConfigFile` — file I/O and parsing for `config.cfg` files
Expand All @@ -43,14 +44,14 @@ Tests live in `LobCorp.ConfigurationManager.Test` (xunit.v3, Moq, AwesomeAsserti
- Type-specific controls: checkboxes, sliders, dropdowns, color pickers, hotkey capture
- `ConfigurationManagerAttributes` controls display (order, visibility, custom drawers)

**`ConfigurationManagerAttributes` is a copy-paste template, not a referenced API.** Each plugin bundles its own copy of the class and assigns values to instances that get passed as tags to setting descriptions. `SettingEntryBase.SetFromAttributes()` reads these via reflection (`Type.GetProperties`, matching by simple type name — not assembly identity). This fork uses **public auto-properties**; upstream BepInEx.ConfigurationManager uses **public fields**, so upstream's template is not directly compatible — if copying from upstream, convert the fields to auto-properties.
**`ConfigurationManagerAttributes` is a copy-paste template, not a referenced API.** Each plugin bundles its own copy of the class and assigns values to instances that get passed as tags to setting descriptions. `SettingEntryBase.SetFromAttributes()` reads these via reflection (`Type.GetFields`, matching by simple type name — not assembly identity). This fork uses **public fields**, matching upstream BepInEx.ConfigurationManager, so a template copied from either source works unchanged.

## Key Constraints

- **net35 target**: no LINQ extensions beyond what's available, no `System.ValueTuple`, limited BCL. `LangVersion` is set to `latest` so C# syntax features work but BCL APIs are restricted.
- **RootNamespace and AssemblyName are both `ConfigurationManager`** (not `LobCorp.ConfigurationManager`) — intentionally matches upstream BepInEx.ConfigurationManager. This is a **DLL-name / namespace collision prevention** mechanism only: the identical DLL name stops both from loading simultaneously, and the shared root namespace avoids dual-load conflicts (double UI entries, duplicate `ConfigurationManagerAttributes` processing). This is **not** a public-API-compatibility contract — the fork can freely change its internal shape (e.g. `ConfigurationManagerAttributes` was moved from fields to properties). Do not change `RootNamespace` or `AssemblyName` without accounting for the loader-collision implications.
- **RootNamespace and AssemblyName are both `ConfigurationManager`** (not `LobCorp.ConfigurationManager`) — intentionally matches upstream BepInEx.ConfigurationManager. This is a **DLL-name / namespace collision prevention** mechanism only: the identical DLL name stops both from loading simultaneously, and the shared root namespace avoids dual-load conflicts (double UI entries, duplicate `ConfigurationManagerAttributes` processing). This is **not** a public-API-compatibility contract — the fork can freely change its internal shape. Do not change `RootNamespace` or `AssemblyName` without accounting for the loader-collision implications.
- **`Harmony_Patch` class name is load-bearing** — every LMM mod must expose an entry type named `Harmony_Patch`. The analyzer package (`LobotomyCorporation.Mods.Analyzers` globalconfig) suppresses S101 and CA1707 repo-wide so this pattern doesn't trip naming rules.
- **Game assembly references are `Private=false`** — none are copied to output since they exist in the game's managed folder at runtime. The `LobotomyCorporation.Mods.Common` PackageReference does copy to output, as it must be deployed alongside the mod.
- **Game assembly references are `Private=false`** — none are copied to output since they exist in the game's managed folder at runtime. No other runtime DLLs are copied alongside `ConfigurationManager.dll` today (the previous `LobotomyCorporation.Mods.Common` bridge has been removed).
- **Implicit usings and nullable are disabled.**
- `Microsoft.NETFramework.ReferenceAssemblies` is pulled in implicitly by the SDK for net35 — do not add it to `Directory.Packages.props`.

Expand All @@ -64,3 +65,24 @@ NuGet package publishing is planned for v1.0.0 but not yet implemented.
## Analyzers

Global analyzers (`LobotomyCorporation.Mods.Analyzers`, `OpenLobotomy.Standards`) are configured in `Directory.Packages.props`. All Sonar rules run at their default severity — there are no global suppressions in `.editorconfig`. Rule exceptions that apply to all LMM mods (e.g. S101/CA1707 for the `Harmony_Patch` entry point) live in the shared `LobotomyCorporation.Mods.Analyzers` globalconfig, not here. Fix violations rather than suppressing them; if a suppression is truly needed, scope it as narrowly as possible (file-local `#pragma` or per-member `[SuppressMessage]`).

## Audience & Language

This repo ships two audience-facing surfaces:

- **`ConfigurationManager.dll`** — installed by players as an LMM BaseMod. End-user audience (installers, not coders); release notes and the in-game UI should be readable without developer vocabulary.
- **Mod-author docs in this repo** (primarily `README.md`) — for authors who take a **direct runtime dependency** on `ConfigurationManager.dll` via `LmmConfigRegistration`. This is the "hard dependency" path.

The **optional-dependency** path — settings UI when ConfigurationManager is installed, in-memory fallback when not — is owned by the separate [`LobotomyCorporation.Mods.ConfigurationManager.Integration`](https://github.com/open-lobotomy/LobotomyCorporation.Mods.ConfigurationManager.Integration) repo, along with the sample mod. Author-facing docs, NuGet packaging, and analyzer diagnostics for that path live there, not here.

**Assume the reader is a first-time mod author whose first language is not English.** Most consumers are Korean-speaking modders reading English as a second language or through machine translation, and many have no prior professional development experience. Every error message, README, and code comment that an author will see must pass that bar before shipping.

### Project facts that shape documentation

- **Lobotomy Corporation itself will never update.** The base game is final. Do not pitch wrappers, adapters, or analyzers on "survives game updates" or "keeps working when the game changes" — those claims are factually wrong and will mislead readers. The honest value props for typed wrappers over reflection are: (a) the compiler checks names and types at build time, so typos fail before you run the game; (b) typed code is shorter and easier to read; (c) the package is community-maintained, so fixes land once for everyone. What *does* still change is LMM (the mod loader) and other mods that patch the same game code via Harmony — if a doc needs to explain why a wrapper helps mods coexist, that is the real reason, not game updates.

### Writing Style

**User-facing text** (README, error messages, release notes): use short sentences with active voice and explicit subjects. Avoid idioms, slang, and culturally specific references. Define technical terms inline or use simpler words. Write in a style that survives machine translation — no ambiguous pronouns, no noun stacking.

**Developer-facing text** (code comments, commit messages): technical terminology is fine, but prefer direct, concise phrasing over unnecessarily complex language.
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
dotnet-version: 10.x

- name: Authenticate GitHub Packages
run: dotnet nuget update source github --username open-lobotomy --password ${{ secrets.PACKAGES_TOKEN }} --store-password-in-clear-text || true
run: dotnet nuget update source github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text || true

- name: Extract version from tag
id: version
Expand All @@ -48,7 +48,6 @@ jobs:
run: |
mkdir -p staging/ConfigurationManager/Info/en
cp LobCorp.ConfigurationManager/bin/net35/ConfigurationManager.dll staging/ConfigurationManager/
cp LobCorp.ConfigurationManager/bin/net35/LobotomyCorporation.Mods.Common.*.dll staging/ConfigurationManager/
cp LobCorp.ConfigurationManager/bin/net35/Info/GlobalInfo.xml staging/ConfigurationManager/Info/
cp LobCorp.ConfigurationManager/bin/net35/Info/en/Info.xml staging/ConfigurationManager/Info/en/

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ _pkginfo.txt
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
*.lscache

# Others
ClientBin/
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
<!-- Sets the 'File description' field on the dll file -->
<AssemblyTitle>In-game configuration manager for LMM and BepInEx mods</AssemblyTitle>
<!-- Sets the 'Company' field on the dll file -->
<Authors>https://github.com/BepInEx/BepInEx.ConfigurationManager</Authors>
<Authors>BepInEx contributors, Open Lobotomy</Authors>
<!-- Sets the 'Copyright' field on the dll file -->
<Copyright>Copyright 2019 / LGPL-3.0</Copyright>
<Copyright>Copyright 2019 BepInEx contributors; Copyright 2026 Open Lobotomy</Copyright>

<CentralPackageVersionOverrideEnabled>true</CentralPackageVersionOverrideEnabled>
<OutputPath>bin\</OutputPath>
Expand Down
5 changes: 0 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<LobotomyCorporationModsCommonVersion>0.1.0-preview.20</LobotomyCorporationModsCommonVersion>
</PropertyGroup>

<ItemGroup>
<PackageVersion
Include="LobotomyCorporation.Mods.Common"
Version="$(LobotomyCorporationModsCommonVersion)"
/>
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit3" Version="4.19.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
<TargetFramework>net10.0</TargetFramework>
<OutputType>Library</OutputType>
<NoWarn>$(NoWarn);NU1702</NoWarn>
<!-- Common's net35 assembly contains an ExcludeFromCodeCoverageAttribute polyfill that
conflicts with the BCL type in xUnit auto-generated entry points. -->
<!-- CM's net35 assembly defines an ExcludeFromCodeCoverageAttribute polyfill.
Although declared internal, xUnit's auto-generated entry point can still
react to it when analyzing the referenced assembly, so keep this disabled. -->
<XunitAutoGeneratedEntryPoint>false</XunitAutoGeneratedEntryPoint>
<GenerateSelfRegisteredExtensions>false</GenerateSelfRegisteredExtensions>
<Nullable>enable</Nullable>
Expand All @@ -17,19 +18,11 @@
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="Moq" />
<PackageReference
Include="LobotomyCorporation.Mods.Common"
GeneratePathProperty="true"
ExcludeAssets="All"
/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LobCorp.ConfigurationManager\LobCorp.ConfigurationManager.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="LobotomyCorporation.Mods.Common">
<HintPath>$(PkgLobotomyCorporation_Mods_Common)\lib\net35\LobotomyCorporation.Mods.Common.$(LobotomyCorporationModsCommonVersion).dll</HintPath>
</Reference>
<Reference Include="0Harmony">
<HintPath>..\external\LobotomyCorp_Data\Managed\0Harmony.dll</HintPath>
</Reference>
Expand Down
Loading
Loading