1 | // MIT License |
2 | |
3 | // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "config.h" |
24 | #include "fs.h" |
25 | #include "cart.h" |
26 | |
27 | #if defined(__EMSCRIPTEN__) |
28 | #define DEFAULT_VSYNC 0 |
29 | #else |
30 | #define DEFAULT_VSYNC 1 |
31 | #endif |
32 | |
33 | #if defined (TIC_BUILD_WITH_LUA) |
34 | #include <lua.h> |
35 | #include <lauxlib.h> |
36 | #include <lualib.h> |
37 | |
38 | static void readBool(lua_State* lua, const char* name, bool* val) |
39 | { |
40 | lua_getfield(lua, -1, name); |
41 | |
42 | if (lua_isboolean(lua, -1)) |
43 | *val = lua_toboolean(lua, -1); |
44 | |
45 | lua_pop(lua, 1); |
46 | } |
47 | |
48 | static void readInteger(lua_State* lua, const char* name, s32* val) |
49 | { |
50 | lua_getfield(lua, -1, name); |
51 | |
52 | if (lua_isinteger(lua, -1)) |
53 | *val = lua_tointeger(lua, -1); |
54 | |
55 | lua_pop(lua, 1); |
56 | } |
57 | |
58 | static void readByte(lua_State* lua, const char* name, u8* val) |
59 | { |
60 | s32 res = *val; |
61 | readInteger(lua, name, &res); |
62 | *val = res; |
63 | } |
64 | |
65 | static void readGlobalInteger(lua_State* lua, const char* name, s32* val) |
66 | { |
67 | lua_getglobal(lua, name); |
68 | |
69 | if (lua_isinteger(lua, -1)) |
70 | *val = lua_tointeger(lua, -1); |
71 | |
72 | lua_pop(lua, 1); |
73 | } |
74 | |
75 | static void readGlobalBool(lua_State* lua, const char* name, bool* val) |
76 | { |
77 | lua_getglobal(lua, name); |
78 | |
79 | if (lua_isboolean(lua, -1)) |
80 | *val = lua_toboolean(lua, -1); |
81 | |
82 | lua_pop(lua, 1); |
83 | } |
84 | |
85 | #if defined(CRT_SHADER_SUPPORT) |
86 | |
87 | static void readString(lua_State* lua, const char* name, const char** val) |
88 | { |
89 | lua_getfield(lua, -1, name); |
90 | |
91 | if (lua_isstring(lua, -1)) |
92 | *val = strdup(lua_tostring(lua, -1)); |
93 | |
94 | lua_pop(lua, 1); |
95 | } |
96 | |
97 | static void readConfigCrtShader(Config* config, lua_State* lua) |
98 | { |
99 | lua_getglobal(lua, "CRT_SHADER" ); |
100 | |
101 | if(lua_type(lua, -1) == LUA_TTABLE) |
102 | { |
103 | readString(lua, "VERTEX" , &config->data.shader.vertex); |
104 | readString(lua, "PIXEL" , &config->data.shader.pixel); |
105 | } |
106 | |
107 | #if defined (EMSCRIPTEN) |
108 | // WebGL supports only version 100 shaders. |
109 | // Luckily, the format is nearly identical. |
110 | // This code detects the incompatible line(s) at |
111 | // the beginning of each shader and patches them |
112 | // in-place in memory. |
113 | char *s = (char *)config->data.shader.vertex; |
114 | if (strncmp("\t\t#version 110" , s, 14) == 0) { |
115 | // replace the two tabs, with a "//" comment, disabling the #version tag. |
116 | s[0] = '/'; |
117 | s[1] = '/'; |
118 | } |
119 | s = (char *)config->data.shader.pixel; |
120 | if (strncmp("\t\t#version 110\n\t\t//precision highp float;" , s, 41) == 0) { |
121 | // replace the two tabs, with a "//" comment, disabling the #version tag. |
122 | s[0] = '/'; |
123 | s[1] = '/'; |
124 | // replace the "//" comment with spaces, enabling the precision statement. |
125 | s[17] = ' '; |
126 | s[18] = ' '; |
127 | } |
128 | #endif |
129 | |
130 | lua_pop(lua, 1); |
131 | } |
132 | |
133 | #endif |
134 | |
135 | static void readCodeTheme(Config* config, lua_State* lua) |
136 | { |
137 | lua_getfield(lua, -1, "CODE" ); |
138 | |
139 | if(lua_type(lua, -1) == LUA_TTABLE) |
140 | { |
141 | |
142 | #define CODE_COLOR_DEF(VAR) readByte(lua, #VAR, &config->data.theme.code.VAR); |
143 | CODE_COLORS_LIST(CODE_COLOR_DEF) |
144 | #undef CODE_COLOR_DEF |
145 | |
146 | readByte(lua, "SELECT" , &config->data.theme.code.select); |
147 | readByte(lua, "CURSOR" , &config->data.theme.code.cursor); |
148 | |
149 | readBool(lua, "SHADOW" , &config->data.theme.code.shadow); |
150 | readBool(lua, "ALT_FONT" , &config->data.theme.code.altFont); |
151 | readBool(lua, "MATCH_DELIMITERS" , &config->data.theme.code.matchDelimiters); |
152 | readBool(lua, "AUTO_DELIMITERS" , &config->data.theme.code.autoDelimiters); |
153 | } |
154 | |
155 | lua_pop(lua, 1); |
156 | } |
157 | |
158 | static void readGamepadTheme(Config* config, lua_State* lua) |
159 | { |
160 | lua_getfield(lua, -1, "GAMEPAD" ); |
161 | |
162 | if(lua_type(lua, -1) == LUA_TTABLE) |
163 | { |
164 | lua_getfield(lua, -1, "TOUCH" ); |
165 | |
166 | if(lua_type(lua, -1) == LUA_TTABLE) |
167 | { |
168 | readByte(lua, "ALPHA" , &config->data.theme.gamepad.touch.alpha); |
169 | } |
170 | |
171 | lua_pop(lua, 1); |
172 | } |
173 | |
174 | lua_pop(lua, 1); |
175 | } |
176 | |
177 | static void readTheme(Config* config, lua_State* lua) |
178 | { |
179 | lua_getglobal(lua, "THEME" ); |
180 | |
181 | if(lua_type(lua, -1) == LUA_TTABLE) |
182 | { |
183 | readCodeTheme(config, lua); |
184 | readGamepadTheme(config, lua); |
185 | } |
186 | |
187 | lua_pop(lua, 1); |
188 | } |
189 | |
190 | static void readConfig(Config* config) |
191 | { |
192 | lua_State* lua = luaL_newstate(); |
193 | |
194 | if(lua) |
195 | { |
196 | if(luaL_loadstring(lua, config->cart->code.data) == LUA_OK && lua_pcall(lua, 0, LUA_MULTRET, 0) == LUA_OK) |
197 | { |
198 | readGlobalInteger(lua, "GIF_LENGTH" , &config->data.gifLength); |
199 | readGlobalInteger(lua, "GIF_SCALE" , &config->data.gifScale); |
200 | readGlobalBool(lua, "CHECK_NEW_VERSION" , &config->data.checkNewVersion); |
201 | readGlobalInteger(lua, "UI_SCALE" , &config->data.uiScale); |
202 | readGlobalBool(lua, "SOFTWARE_RENDERING" , &config->data.soft); |
203 | |
204 | #if defined(CRT_SHADER_SUPPORT) |
205 | readConfigCrtShader(config, lua); |
206 | #endif |
207 | readTheme(config, lua); |
208 | } |
209 | |
210 | lua_close(lua); |
211 | } |
212 | } |
213 | #else |
214 | |
215 | static void readConfig(Config* config) {} |
216 | |
217 | #endif |
218 | |
219 | static void update(Config* config, const u8* buffer, s32 size) |
220 | { |
221 | tic_cart_load(config->cart, buffer, size); |
222 | |
223 | readConfig(config); |
224 | studioConfigChanged(config->studio); |
225 | } |
226 | |
227 | static void setDefault(Config* config) |
228 | { |
229 | config->data = (StudioConfig) |
230 | { |
231 | .cart = config->cart, |
232 | .uiScale = 4, |
233 | .options = |
234 | { |
235 | #if defined(CRT_SHADER_SUPPORT) |
236 | .crt = false, |
237 | #endif |
238 | .volume = MAX_VOLUME, |
239 | .vsync = DEFAULT_VSYNC, |
240 | .fullscreen = false, |
241 | #if defined(BUILD_EDITORS) |
242 | .devmode = false, |
243 | #endif |
244 | }, |
245 | }; |
246 | |
247 | tic_sys_default_mapping(&config->data.options.mapping); |
248 | |
249 | { |
250 | static const u8 ConfigZip[] = |
251 | { |
252 | #include "../build/assets/config.tic.dat" |
253 | }; |
254 | |
255 | u8* data = malloc(sizeof(tic_cartridge)); |
256 | |
257 | SCOPE(free(data)) |
258 | { |
259 | update(config, data, tic_tool_unzip(data, sizeof(tic_cartridge), ConfigZip, sizeof ConfigZip)); |
260 | } |
261 | } |
262 | } |
263 | |
264 | static void saveConfig(Config* config, bool overwrite) |
265 | { |
266 | u8* buffer = malloc(sizeof(tic_cartridge)); |
267 | |
268 | if(buffer) |
269 | { |
270 | s32 size = tic_cart_save(config->data.cart, buffer); |
271 | |
272 | tic_fs_saveroot(config->fs, CONFIG_TIC_PATH, buffer, size, overwrite); |
273 | |
274 | free(buffer); |
275 | } |
276 | } |
277 | |
278 | static void reset(Config* config) |
279 | { |
280 | setDefault(config); |
281 | saveConfig(config, true); |
282 | } |
283 | |
284 | static void save(Config* config) |
285 | { |
286 | *config->cart = config->tic->cart; |
287 | readConfig(config); |
288 | saveConfig(config, true); |
289 | |
290 | studioConfigChanged(config->studio); |
291 | } |
292 | |
293 | static const char OptionsDatPath[] = TIC_LOCAL_VERSION "options.dat" ; |
294 | |
295 | static void loadConfigData(tic_fs* fs, const char* path, void* dst, s32 size) |
296 | { |
297 | s32 dataSize = 0; |
298 | u8* data = (u8*)tic_fs_loadroot(fs, path, &dataSize); |
299 | |
300 | if(data) SCOPE(free(data)) |
301 | if(dataSize == size) |
302 | memcpy(dst, data, size); |
303 | } |
304 | |
305 | void initConfig(Config* config, Studio* studio, tic_fs* fs) |
306 | { |
307 | *config = (Config) |
308 | { |
309 | .studio = studio, |
310 | .tic = getMemory(studio), |
311 | .cart = realloc(config->cart, sizeof(tic_cartridge)), |
312 | .save = save, |
313 | .reset = reset, |
314 | .fs = fs, |
315 | }; |
316 | |
317 | setDefault(config); |
318 | |
319 | // read config.tic |
320 | { |
321 | s32 size = 0; |
322 | u8* data = (u8*)tic_fs_loadroot(fs, CONFIG_TIC_PATH, &size); |
323 | |
324 | if(data) |
325 | { |
326 | update(config, data, size); |
327 | |
328 | free(data); |
329 | } |
330 | else saveConfig(config, false); |
331 | } |
332 | |
333 | loadConfigData(fs, OptionsDatPath, &config->data.options, sizeof config->data.options); |
334 | |
335 | tic_api_reset(config->tic); |
336 | } |
337 | |
338 | void freeConfig(Config* config) |
339 | { |
340 | tic_fs_saveroot(config->fs, OptionsDatPath, &config->data.options, sizeof config->data.options, true); |
341 | |
342 | free(config->cart); |
343 | |
344 | #if defined(CRT_SHADER_SUPPORT) |
345 | |
346 | free((void*)config->data.shader.vertex); |
347 | free((void*)config->data.shader.pixel); |
348 | #endif |
349 | |
350 | free(config); |
351 | } |
352 | |