feat: add game SDK and harden client login and game rules

- Add costume burst upgrades with durable replay and fix permanent SR equipment gacha.
- Add server game.json rules for optional collaboration UR weapons, with packaging and documentation.
- Add GameNames/GameSdk NuGet tooling, readable types, reobfuscation, complete source/PDB navigation and shared-runtime installation.
- Split plugin responsibilities, prefer typeof/nameof, centralize MSBuild settings and ignore local paths; remove the unused LoginUI test project.
- Fix login recovery and TLS through native platform HTTP and a private game relay; read device region directly from the OS time zone.

Validation: go test ./... -count=1, go vet ./..., plugin Release builds, SDK/runtime self-tests, Roslyn embedded-source navigation, gofmt check and git diff --check.
This commit is contained in:
2026-10-03 23:04:32 +08:00
parent d05da41d5e
commit 5d86657c75
83 changed files with 8970 additions and 2828 deletions
+30 -3
View File
@@ -23,6 +23,7 @@ type Spec struct {
var (
LocalIdentity = Spec{fileName: "BD2LocalIdentity.dll"}
LoginUI = Spec{fileName: "BD2LoginUI.dll"}
GameNames = Spec{fileName: "BD2.GameNames.dll"}
)
func (s Spec) FileName() string { return s.fileName }
@@ -54,8 +55,8 @@ func ResolvePackaged(spec Spec, explicit string) (string, error) {
}
// Install verifies that the user installed BepInEx, then atomically stages the
// packaged plugin into its plugins directory. It never installs or downloads
// BepInEx itself.
// packaged plugin and its adjacent shared game-names runtime into its plugins
// directory. It never installs or downloads BepInEx itself.
func Install(spec Spec, gameDir, source string) (Result, error) {
if err := spec.validate(); err != nil {
return Result{}, err
@@ -80,7 +81,33 @@ func Install(spec Spec, gameDir, source string) (Result, error) {
if len(sourceData) == 0 {
return Result{}, fmt.Errorf("clientplugin: packaged %s is empty", spec.fileName)
}
pluginDir := installation.Plugins
// Read both artifacts before touching the installation. The runtime table
// library is shipped beside every plugin, including explicit development paths.
var runtimeData []byte
if spec == LocalIdentity || spec == LoginUI {
runtimeSource := filepath.Join(filepath.Dir(source), GameNames.fileName)
runtimeData, err = os.ReadFile(runtimeSource)
if err != nil {
return Result{}, fmt.Errorf("clientplugin: read shared runtime %s: %w", runtimeSource, err)
}
if len(runtimeData) == 0 {
return Result{}, fmt.Errorf("clientplugin: packaged %s is empty", GameNames.fileName)
}
}
changed := false
if runtimeData != nil {
runtimeResult, err := installData(GameNames, installation.Plugins, runtimeData)
if err != nil {
return Result{}, err
}
changed = runtimeResult.Changed
}
result, err := installData(spec, installation.Plugins, sourceData)
result.Changed = result.Changed || changed
return result, err
}
func installData(spec Spec, pluginDir string, sourceData []byte) (Result, error) {
destination := filepath.Join(pluginDir, spec.fileName)
if installed, err := os.ReadFile(destination); err == nil {
if bytes.Equal(hash(installed), hash(sourceData)) {