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
38static 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
48static 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
58static 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
65static 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
75static 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
87static 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
97static 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
135static 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
158static 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
177static 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
190static 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
215static void readConfig(Config* config) {}
216
217#endif
218
219static 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
227static 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
264static 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
278static void reset(Config* config)
279{
280 setDefault(config);
281 saveConfig(config, true);
282}
283
284static 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
293static const char OptionsDatPath[] = TIC_LOCAL_VERSION "options.dat";
294
295static 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
305void 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
338void 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