| 113 | } |
| 114 | |
| 115 | bool ControlsConfig::Save(const std::string& path) const |
| 116 | { |
| 117 | std::error_code ec; |
| 118 | std::filesystem::path p(path); |
| 119 | if (p.has_parent_path()) |
| 120 | std::filesystem::create_directories(p.parent_path(), ec); |
| 121 | |
| 122 | ParamFile cfg; |
| 123 | UserActionDesc* descs = InputSubsystem::GetUserActionDesc(); |
| 124 | for (int i = 0; i < UAN; i++) |
| 125 | { |
| 126 | ParamEntry* entry = cfg.AddArray(KeyName(descs[i])); |
| 127 | entry->Clear(); |
| 128 | const AutoArray<int>& slot = bindings[i]; |
| 129 | for (int j = 0; j < slot.Size(); j++) |
| 130 | { |
| 131 | // Defensive: only emit keyboard / mouse entries. Joystick |
| 132 | // bindings live in gamepad.cfg and InputSubsystem::SaveKeys |
| 133 | // is responsible for splitting GInput.userKeys[] by device |
| 134 | // class — this filter is belt-and-suspenders. |
| 135 | if (IsKbmCode(slot[j])) |
| 136 | entry->AddValue(slot[j]); |
| 137 | } |
| 138 | |
| 139 | // Only emit a modifier line when at least one slot has a real |
| 140 | // modifier — keeps the on-disk file readable and matches the |
| 141 | // forward-compat assumption (missing _mod = all -1). |
| 142 | const AutoArray<int>& mods = modifiers[i]; |
| 143 | bool any = false; |
| 144 | for (int j = 0; j < mods.Size(); j++) |
| 145 | if (mods[j] >= 0) |
| 146 | { |
| 147 | any = true; |
| 148 | break; |
| 149 | } |
| 150 | if (any) |
| 151 | { |
| 152 | ParamEntry* modEntry = cfg.AddArray(ModName(descs[i])); |
| 153 | modEntry->Clear(); |
| 154 | for (int j = 0; j < slot.Size(); j++) |
| 155 | modEntry->AddValue(j < mods.Size() ? mods[j] : -1); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | cfg.Save(RString(path.c_str())); |
| 160 | return std::filesystem::exists(path, ec); |
| 161 | } |
| 162 | |
| 163 | } // namespace Poseidon |