Do not abort startup when the config file cannot be written#1340
Conversation
ConfigFile.Save opens the file for writing with no exception handling and is invoked automatically via SaveOnConfigSet while settings are bound during startup, before logging is initialized. A read-only or otherwise unwritable config file (a read-only install directory, a file locked by another process or antivirus) therefore throws UnauthorizedAccessException out of the early startup path and shows the Failed to start BepInEx dialog. Route the automatic saves (on init, on bind, on change) through a TrySave helper that logs a warning and keeps the in-memory configuration instead of propagating IOException / UnauthorizedAccessException. Explicit Save calls are unchanged.
ManlyMarco
left a comment
There was a problem hiding this comment.
Not sure about this one. I think this is a fatal issue and should crash rather than be silently ignored but I'm open to arguments otherwise.
|
Fair concern — here's why I went with degrade-and-warn, but happy to change it if you still prefer fatal. The save isn't silent: The reason I avoided crashing here specifically: the first automatic save runs while Also, only the automatic saves go through That said, you know the project's expectations best. If you'd rather keep it fatal, one option is to log an actionable error and rethrow — though with the pre-logging timing above, a throw there still produces no log, whereas warn-and-degrade gives both the diagnostic and a launchable game. Glad to go whichever way you prefer. |
This is a very good point, I can see the utility now. The problem with only crashing on Save is that almost no plugin ever uses it, everything instead relies on automatic saving on setting set. Doing this would effectively catch all config save exceptions. Would it be enough to only catch exceptions on creation? I don't think BepInEx sets any of the settings internally, so the crash should only ever happen on creation. |
Per review: setting changes save through Save() and throw again; only the initial file write and new-entry binds use TrySave, since those can run before the logging backend is up.
429eed7 to
4459278
Compare
|
Done in 4459278 — narrowed exactly as you suggested:
Builds clean across all TFMs. |
Fixes #1339
ConfigFile.Save()opens the file for writing with no exception handling and is invoked automatically viaSaveOnConfigSet(defaulttrue) while settings are bound during startup, before logging is initialized. A read-only or otherwise unwritable config file -- a read-only install directory, or a file locked by another process / antivirus -- therefore throwsUnauthorizedAccessExceptionout of the early startup path and shows the "Failed to start BepInEx" dialog (with noLogOutput.log, since logging isn't up yet).This routes the automatic saves (on init, on bind, on change) through a
TrySavehelper that logs a warning and keeps the in-memory configuration instead of propagatingIOException/UnauthorizedAccessException. ExplicitSave()calls are unchanged, so a plugin that callsSave()directly still sees the exception.Repro: make
BepInEx/config/BepInEx.cfgread-only, then launch -- before this change BepInEx fails to start; after it, it logs a warning and continues with the in-memory config.