summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Charles Lombardo2023-03-07 21:36:42 -0500
committerGravatar bunnei2023-06-03 00:05:38 -0700
commit0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2 (patch)
tree1ff3bc8292d256138282f3260dbd37115ada1f60
parentandroid: Convert SettingsFrameLayout to Kotlin (diff)
downloadyuzu-0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2.tar.gz
yuzu-0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2.tar.xz
yuzu-0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2.zip
android: Convert SettingsFile to Kotlin
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java272
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt245
2 files changed, 245 insertions, 272 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java
deleted file mode 100644
index 2b3d257a3..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.java
+++ /dev/null
@@ -1,272 +0,0 @@
1package org.yuzu.yuzu_emu.features.settings.utils;
2
3import androidx.annotation.NonNull;
4
5import org.yuzu.yuzu_emu.YuzuApplication;
6import org.yuzu.yuzu_emu.NativeLibrary;
7import org.yuzu.yuzu_emu.R;
8import org.yuzu.yuzu_emu.features.settings.model.FloatSetting;
9import org.yuzu.yuzu_emu.features.settings.model.IntSetting;
10import org.yuzu.yuzu_emu.features.settings.model.Setting;
11import org.yuzu.yuzu_emu.features.settings.model.SettingSection;
12import org.yuzu.yuzu_emu.features.settings.model.Settings;
13import org.yuzu.yuzu_emu.features.settings.model.StringSetting;
14import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView;
15import org.yuzu.yuzu_emu.utils.BiMap;
16import org.yuzu.yuzu_emu.utils.DirectoryInitialization;
17import org.yuzu.yuzu_emu.utils.Log;
18import org.ini4j.Wini;
19
20import java.io.BufferedReader;
21import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.FileReader;
24import java.io.IOException;
25import java.util.HashMap;
26import java.util.Set;
27import java.util.TreeMap;
28import java.util.TreeSet;
29
30/**
31 * Contains static methods for interacting with .ini files in which settings are stored.
32 */
33public final class SettingsFile {
34 public static final String FILE_NAME_CONFIG = "config";
35
36 public static final String KEY_DESIGN = "design";
37
38 // CPU
39 public static final String KEY_CPU_ACCURACY = "cpu_accuracy";
40 // System
41 public static final String KEY_USE_DOCKED_MODE = "use_docked_mode";
42 public static final String KEY_REGION_INDEX = "region_index";
43 public static final String KEY_LANGUAGE_INDEX = "language_index";
44 public static final String KEY_RENDERER_BACKEND = "backend";
45 // Renderer
46 public static final String KEY_RENDERER_RESOLUTION = "resolution_setup";
47 public static final String KEY_RENDERER_ASPECT_RATIO = "aspect_ratio";
48 public static final String KEY_RENDERER_ACCURACY = "gpu_accuracy";
49 public static final String KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders";
50 public static final String KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock";
51 public static final String KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit";
52 public static final String KEY_RENDERER_DEBUG = "debug";
53 public static final String KEY_RENDERER_SPEED_LIMIT = "speed_limit";
54 // Audio
55 public static final String KEY_AUDIO_VOLUME = "volume";
56
57 private static BiMap<String, String> sectionsMap = new BiMap<>();
58
59 static {
60 //TODO: Add members to sectionsMap when game-specific settings are added
61 }
62
63
64 private SettingsFile() {
65 }
66
67 /**
68 * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
69 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
70 * failed.
71 *
72 * @param ini The ini file to load the settings from
73 * @param isCustomGame
74 * @param view The current view.
75 * @return An Observable that emits a HashMap of the file's contents, then completes.
76 */
77 static HashMap<String, SettingSection> readFile(final File ini, boolean isCustomGame, SettingsActivityView view) {
78 HashMap<String, SettingSection> sections = new Settings.SettingsSectionMap();
79
80 BufferedReader reader = null;
81
82 try {
83 reader = new BufferedReader(new FileReader(ini));
84
85 SettingSection current = null;
86 for (String line; (line = reader.readLine()) != null; ) {
87 if (line.startsWith("[") && line.endsWith("]")) {
88 current = sectionFromLine(line, isCustomGame);
89 sections.put(current.getName(), current);
90 } else if ((current != null)) {
91 Setting setting = settingFromLine(current, line);
92 if (setting != null) {
93 current.putSetting(setting);
94 }
95 }
96 }
97 } catch (FileNotFoundException e) {
98 Log.error("[SettingsFile] File not found: " + e.getMessage());
99 if (view != null)
100 view.onSettingsFileNotFound();
101 } catch (IOException e) {
102 Log.error("[SettingsFile] Error reading from: " + e.getMessage());
103 if (view != null)
104 view.onSettingsFileNotFound();
105 } finally {
106 if (reader != null) {
107 try {
108 reader.close();
109 } catch (IOException e) {
110 Log.error("[SettingsFile] Error closing: " + e.getMessage());
111 }
112 }
113 }
114
115 return sections;
116 }
117
118 public static HashMap<String, SettingSection> readFile(final String fileName, SettingsActivityView view) {
119 return readFile(getSettingsFile(fileName), false, view);
120 }
121
122 /**
123 * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
124 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
125 * failed.
126 *
127 * @param gameId the id of the game to load it's settings.
128 * @param view The current view.
129 */
130 public static HashMap<String, SettingSection> readCustomGameSettings(final String gameId, SettingsActivityView view) {
131 return readFile(getCustomGameSettingsFile(gameId), true, view);
132 }
133
134 /**
135 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
136 * telling why it failed.
137 *
138 * @param fileName The target filename without a path or extension.
139 * @param sections The HashMap containing the Settings we want to serialize.
140 * @param view The current view.
141 */
142 public static void saveFile(final String fileName, TreeMap<String, SettingSection> sections,
143 SettingsActivityView view) {
144 File ini = getSettingsFile(fileName);
145
146 try {
147 Wini writer = new Wini(ini);
148
149 Set<String> keySet = sections.keySet();
150 for (String key : keySet) {
151 SettingSection section = sections.get(key);
152 writeSection(writer, section);
153 }
154 writer.store();
155 } catch (IOException e) {
156 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage());
157 view.showToastMessage(YuzuApplication.getAppContext().getString(R.string.error_saving, fileName, e.getMessage()), false);
158 }
159 }
160
161
162 public static void saveCustomGameSettings(final String gameId, final HashMap<String, SettingSection> sections) {
163 Set<String> sortedSections = new TreeSet<>(sections.keySet());
164
165 for (String sectionKey : sortedSections) {
166 SettingSection section = sections.get(sectionKey);
167
168 HashMap<String, Setting> settings = section.getSettings();
169 Set<String> sortedKeySet = new TreeSet<>(settings.keySet());
170
171 for (String settingKey : sortedKeySet) {
172 Setting setting = settings.get(settingKey);
173 NativeLibrary.SetUserSetting(gameId, mapSectionNameFromIni(section.getName()), setting.getKey(), setting.getValueAsString());
174 }
175 }
176 }
177
178 private static String mapSectionNameFromIni(String generalSectionName) {
179 if (sectionsMap.getForward(generalSectionName) != null) {
180 return sectionsMap.getForward(generalSectionName);
181 }
182
183 return generalSectionName;
184 }
185
186 private static String mapSectionNameToIni(String generalSectionName) {
187 if (sectionsMap.getBackward(generalSectionName) != null) {
188 return sectionsMap.getBackward(generalSectionName);
189 }
190
191 return generalSectionName;
192 }
193
194 @NonNull
195 private static File getSettingsFile(String fileName) {
196 return new File(
197 DirectoryInitialization.getUserDirectory() + "/config/" + fileName + ".ini");
198 }
199
200 private static File getCustomGameSettingsFile(String gameId) {
201 return new File(DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini");
202 }
203
204 private static SettingSection sectionFromLine(String line, boolean isCustomGame) {
205 String sectionName = line.substring(1, line.length() - 1);
206 if (isCustomGame) {
207 sectionName = mapSectionNameToIni(sectionName);
208 }
209 return new SettingSection(sectionName);
210 }
211
212 /**
213 * For a line of text, determines what type of data is being represented, and returns
214 * a Setting object containing this data.
215 *
216 * @param current The section currently being parsed by the consuming method.
217 * @param line The line of text being parsed.
218 * @return A typed Setting containing the key/value contained in the line.
219 */
220 private static Setting settingFromLine(SettingSection current, String line) {
221 String[] splitLine = line.split("=");
222
223 if (splitLine.length != 2) {
224 Log.warning("Skipping invalid config line \"" + line + "\"");
225 return null;
226 }
227
228 String key = splitLine[0].trim();
229 String value = splitLine[1].trim();
230
231 if (value.isEmpty()) {
232 Log.warning("Skipping null value in config line \"" + line + "\"");
233 return null;
234 }
235
236 try {
237 int valueAsInt = Integer.parseInt(value);
238
239 return new IntSetting(key, current.getName(), valueAsInt);
240 } catch (NumberFormatException ex) {
241 }
242
243 try {
244 float valueAsFloat = Float.parseFloat(value);
245
246 return new FloatSetting(key, current.getName(), valueAsFloat);
247 } catch (NumberFormatException ex) {
248 }
249
250 return new StringSetting(key, current.getName(), value);
251 }
252
253 /**
254 * Writes the contents of a Section HashMap to disk.
255 *
256 * @param parser A Wini pointed at a file on disk.
257 * @param section A section containing settings to be written to the file.
258 */
259 private static void writeSection(Wini parser, SettingSection section) {
260 // Write the section header.
261 String header = section.getName();
262
263 // Write this section's values.
264 HashMap<String, Setting> settings = section.getSettings();
265 Set<String> keySet = settings.keySet();
266
267 for (String key : keySet) {
268 Setting setting = settings.get(key);
269 parser.put(header, setting.getKey(), setting.getValueAsString());
270 }
271 }
272}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
new file mode 100644
index 000000000..8e21c65f0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -0,0 +1,245 @@
1package org.yuzu.yuzu_emu.features.settings.utils
2
3import org.ini4j.Wini
4import org.yuzu.yuzu_emu.NativeLibrary
5import org.yuzu.yuzu_emu.R
6import org.yuzu.yuzu_emu.YuzuApplication
7import org.yuzu.yuzu_emu.features.settings.model.*
8import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
10import org.yuzu.yuzu_emu.utils.BiMap
11import org.yuzu.yuzu_emu.utils.DirectoryInitialization
12import org.yuzu.yuzu_emu.utils.Log
13import java.io.*
14import java.util.*
15
16/**
17 * Contains static methods for interacting with .ini files in which settings are stored.
18 */
19object SettingsFile {
20 const val FILE_NAME_CONFIG = "config"
21 const val KEY_DESIGN = "design"
22
23 // CPU
24 const val KEY_CPU_ACCURACY = "cpu_accuracy"
25
26 // System
27 const val KEY_USE_DOCKED_MODE = "use_docked_mode"
28 const val KEY_REGION_INDEX = "region_index"
29 const val KEY_LANGUAGE_INDEX = "language_index"
30 const val KEY_RENDERER_BACKEND = "backend"
31
32 // Renderer
33 const val KEY_RENDERER_RESOLUTION = "resolution_setup"
34 const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio"
35 const val KEY_RENDERER_ACCURACY = "gpu_accuracy"
36 const val KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders"
37 const val KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock"
38 const val KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit"
39 const val KEY_RENDERER_DEBUG = "debug"
40 const val KEY_RENDERER_SPEED_LIMIT = "speed_limit"
41
42 // Audio
43 const val KEY_AUDIO_VOLUME = "volume"
44 private val sectionsMap = BiMap<String?, String?>()
45
46 /**
47 * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
48 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
49 * failed.
50 *
51 * @param ini The ini file to load the settings from
52 * @param isCustomGame
53 * @param view The current view.
54 * @return An Observable that emits a HashMap of the file's contents, then completes.
55 */
56 private fun readFile(
57 ini: File?,
58 isCustomGame: Boolean,
59 view: SettingsActivityView?
60 ): HashMap<String, SettingSection?> {
61 val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
62 var reader: BufferedReader? = null
63 try {
64 reader = BufferedReader(FileReader(ini))
65 var current: SettingSection? = null
66 var line: String?
67 while (reader.readLine().also { line = it } != null) {
68 if (line!!.startsWith("[") && line!!.endsWith("]")) {
69 current = sectionFromLine(line!!, isCustomGame)
70 sections[current.name] = current
71 } else if (current != null) {
72 val setting = settingFromLine(current, line!!)
73 if (setting != null) {
74 current.putSetting(setting)
75 }
76 }
77 }
78 } catch (e: FileNotFoundException) {
79 Log.error("[SettingsFile] File not found: " + e.message)
80 view?.onSettingsFileNotFound()
81 } catch (e: IOException) {
82 Log.error("[SettingsFile] Error reading from: " + e.message)
83 view?.onSettingsFileNotFound()
84 } finally {
85 if (reader != null) {
86 try {
87 reader.close()
88 } catch (e: IOException) {
89 Log.error("[SettingsFile] Error closing: " + e.message)
90 }
91 }
92 }
93 return sections
94 }
95
96 fun readFile(fileName: String, view: SettingsActivityView): HashMap<String, SettingSection?> {
97 return readFile(getSettingsFile(fileName), false, view)
98 }
99
100 /**
101 * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
102 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
103 * failed.
104 *
105 * @param gameId the id of the game to load it's settings.
106 * @param view The current view.
107 */
108 fun readCustomGameSettings(
109 gameId: String,
110 view: SettingsActivityView
111 ): HashMap<String, SettingSection?> {
112 return readFile(getCustomGameSettingsFile(gameId), true, view)
113 }
114
115 /**
116 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
117 * telling why it failed.
118 *
119 * @param fileName The target filename without a path or extension.
120 * @param sections The HashMap containing the Settings we want to serialize.
121 * @param view The current view.
122 */
123 fun saveFile(
124 fileName: String,
125 sections: TreeMap<String, SettingSection>,
126 view: SettingsActivityView
127 ) {
128 val ini = getSettingsFile(fileName)
129 try {
130 val writer = Wini(ini)
131 val keySet: Set<String> = sections.keys
132 for (key in keySet) {
133 val section = sections[key]
134 writeSection(writer, section!!)
135 }
136 writer.store()
137 } catch (e: IOException) {
138 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
139 view.showToastMessage(
140 YuzuApplication.appContext
141 .getString(R.string.error_saving, fileName, e.message),
142 false
143 )
144 }
145 }
146
147 fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
148 val sortedSections: Set<String> = TreeSet(sections.keys)
149 for (sectionKey in sortedSections) {
150 val section = sections[sectionKey]
151 val settings = section!!.settings
152 val sortedKeySet: Set<String> = TreeSet(settings.keys)
153 for (settingKey in sortedKeySet) {
154 val setting = settings[settingKey]
155 NativeLibrary.SetUserSetting(
156 gameId, mapSectionNameFromIni(
157 section.name
158 ), setting!!.key, setting.valueAsString
159 )
160 }
161 }
162 }
163
164 private fun mapSectionNameFromIni(generalSectionName: String): String? {
165 return if (sectionsMap.getForward(generalSectionName) != null) {
166 sectionsMap.getForward(generalSectionName)
167 } else generalSectionName
168 }
169
170 private fun mapSectionNameToIni(generalSectionName: String): String {
171 return if (sectionsMap.getBackward(generalSectionName) != null) {
172 sectionsMap.getBackward(generalSectionName).toString()
173 } else generalSectionName
174 }
175
176 private fun getSettingsFile(fileName: String): File {
177 return File(
178 DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
179 )
180 }
181
182 private fun getCustomGameSettingsFile(gameId: String): File {
183 return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
184 }
185
186 private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
187 var sectionName: String = line.substring(1, line.length - 1)
188 if (isCustomGame) {
189 sectionName = mapSectionNameToIni(sectionName)
190 }
191 return SettingSection(sectionName)
192 }
193
194 /**
195 * For a line of text, determines what type of data is being represented, and returns
196 * a Setting object containing this data.
197 *
198 * @param current The section currently being parsed by the consuming method.
199 * @param line The line of text being parsed.
200 * @return A typed Setting containing the key/value contained in the line.
201 */
202 private fun settingFromLine(current: SettingSection, line: String): Setting? {
203 val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
204 if (splitLine.size != 2) {
205 Log.warning("Skipping invalid config line \"$line\"")
206 return null
207 }
208 val key = splitLine[0].trim { it <= ' ' }
209 val value = splitLine[1].trim { it <= ' ' }
210 if (value.isEmpty()) {
211 Log.warning("Skipping null value in config line \"$line\"")
212 return null
213 }
214 try {
215 val valueAsInt = value.toInt()
216 return IntSetting(key, current.name, valueAsInt)
217 } catch (_: NumberFormatException) {
218 }
219 try {
220 val valueAsFloat = value.toFloat()
221 return FloatSetting(key, current.name, valueAsFloat)
222 } catch (_: NumberFormatException) {
223 }
224 return StringSetting(key, current.name, value)
225 }
226
227 /**
228 * Writes the contents of a Section HashMap to disk.
229 *
230 * @param parser A Wini pointed at a file on disk.
231 * @param section A section containing settings to be written to the file.
232 */
233 private fun writeSection(parser: Wini, section: SettingSection) {
234 // Write the section header.
235 val header = section.name
236
237 // Write this section's values.
238 val settings = section.settings
239 val keySet: Set<String> = settings.keys
240 for (key in keySet) {
241 val setting = settings[key]
242 parser.put(header, setting!!.key, setting.valueAsString)
243 }
244 }
245}