@@ -122,64 +122,81 @@ void RepoManager::fetch(const std::string& url) {
122122}
123123
124124std::optional<Game> RepoManager::parseGameFromJson (const std::string& json) {
125- auto jg = nlohmann::json::parse (json);
126- if (!jg.contains (" name" ) || (!jg.contains (" titleIds" ) && !jg.contains (" title_ids" )) || !jg.contains (" mods" )) {
127- LOG_WARN (" RepoManager: skipping game - missing required fields" );
128- return std::nullopt ;
129- }
130-
131- Game game;
132- game.name = jg[" name" ].get <std::string>();
133- game.icon = jg.value (" icon" , " " );
134- auto tids = jg.contains (" titleIds" ) ? jg[" titleIds" ] : jg[" title_ids" ];
135- for (auto & tid : tids) game.titleIds .push_back (tid.get <std::string>());
136-
137- for (auto & jm : jg[" mods" ]) {
138- if (!jm.contains (" id" ) || !jm.contains (" name" ) ||
139- !jm.contains (" version" ) || !jm.contains (" download" )) {
140- LOG_WARN (" RepoManager: skipping mod - missing required fields" );
141- continue ;
125+ try {
126+ auto jg = nlohmann::json::parse (json);
127+ if (!jg.contains (" name" ) || (!jg.contains (" titleIds" ) && !jg.contains (" title_ids" )) || !jg.contains (" mods" )) {
128+ LOG_WARN (" RepoManager: skipping game - missing required fields" );
129+ return std::nullopt ;
142130 }
143- Mod mod;
144- mod.id = jm[" id" ].get <std::string>();
145- bool validId = !mod.id .empty () && std::all_of (mod.id .begin (), mod.id .end (),
146- [](char c){ return isalnum (c) || c == ' -' || c == ' _' ; });
147- if (!validId) { LOG_WARN (" RepoManager: skipping mod with invalid id: %s" , mod.id .c_str ()); continue ; }
148-
149- mod.name = jm[" name" ].get <std::string>();
150- mod.version = jm[" version" ].get <std::string>();
151- mod.download = jm[" download" ].get <std::string>();
152- mod.author = jm.value (" author" , " Unknown" );
153- mod.description = jm.value (" description" , " " );
154- mod.type = jm.value (" type" , " mod" );
155- mod.thumbnail = jm.value (" thumbnail" , " " );
156-
157- if (jm.contains (" includes" ) && jm[" includes" ].is_array ())
158- for (auto & s : jm[" includes" ]) mod.includes .push_back (s.get <std::string>());
159- if (jm.contains (" screenshots" ) && jm[" screenshots" ].is_array ())
160- for (auto & s : jm[" screenshots" ]) mod.screenshots .push_back (s.get <std::string>());
161-
162- mod.releaseDate = jm.value (" releaseDate" , " " );
163- mod.changelog = jm.value (" changelog" , " " );
164- mod.license = jm.value (" license" , " " );
165- mod.fileSize = jm.value (" fileSize" , uint64_t (0 ));
166-
167- if (jm.contains (" requirements" ) && jm[" requirements" ].is_array ())
168- for (auto & s : jm[" requirements" ]) mod.requirements .push_back (s.get <std::string>());
169- if (jm.contains (" tags" ) && jm[" tags" ].is_array ())
170- for (auto & s : jm[" tags" ]) mod.tags .push_back (s.get <std::string>());
171-
172- if (!RepoManager::validateUrl (mod.download )) {
173- LOG_WARN (" RepoManager: skipping mod '%s' - invalid download URL" , mod.id .c_str ());
174- continue ;
131+ Game game;
132+ game.name = jg[" name" ].get <std::string>();
133+ game.icon = jg.value (" icon" , " " );
134+
135+ // titleIds validation
136+ auto tids = jg.contains (" titleIds" ) ? jg[" titleIds" ] : jg[" title_ids" ];
137+ if (!tids.is_array () || tids.empty ()) {
138+ LOG_WARN (" RepoManager: skipping game - titleIds must be non-empty array" );
139+ return std::nullopt ;
175140 }
176- game.mods .push_back (std::move (mod));
141+ for (auto & tid : tids) game.titleIds .push_back (tid.get <std::string>());
142+
143+ // mods validation
144+ if (!jg[" mods" ].is_array () || jg[" mods" ].empty ()) {
145+ LOG_WARN (" RepoManager: skipping game - mods must be non-empty array" );
146+ return std::nullopt ;
147+ }
148+
149+ for (auto & jm : jg[" mods" ]) {
150+ if (!jm.contains (" id" ) || !jm.contains (" name" ) ||
151+ !jm.contains (" version" ) || !jm.contains (" download" )) {
152+ LOG_WARN (" RepoManager: skipping mod - missing required fields" );
153+ continue ;
154+ }
155+ Mod mod;
156+ mod.id = jm[" id" ].get <std::string>();
157+ bool validId = !mod.id .empty () && std::all_of (mod.id .begin (), mod.id .end (),
158+ [](char c){ return isalnum (c) || c == ' -' || c == ' _' ; });
159+ if (!validId) { LOG_WARN (" RepoManager: skipping mod with invalid id: %s" , mod.id .c_str ()); continue ; }
160+ mod.name = jm[" name" ].get <std::string>();
161+ mod.version = jm[" version" ].get <std::string>();
162+ mod.download = jm[" download" ].get <std::string>();
163+ mod.author = jm.value (" author" , " Unknown" );
164+ mod.description = jm.value (" description" , " " );
165+ mod.type = jm.value (" type" , " mod" );
166+ mod.thumbnail = jm.value (" thumbnail" , " " );
167+ if (jm.contains (" includes" ) && jm[" includes" ].is_array ())
168+ for (auto & s : jm[" includes" ]) mod.includes .push_back (s.get <std::string>());
169+ if (jm.contains (" screenshots" ) && jm[" screenshots" ].is_array ())
170+ for (auto & s : jm[" screenshots" ]) mod.screenshots .push_back (s.get <std::string>());
171+ mod.releaseDate = jm.value (" releaseDate" , " " );
172+ mod.changelog = jm.value (" changelog" , " " );
173+ mod.license = jm.value (" license" , " " );
174+
175+ // fileSize with type check and bounds validation
176+ if (jm.contains (" fileSize" ) && jm[" fileSize" ].is_number ()) {
177+ int64_t size = jm[" fileSize" ].get <int64_t >();
178+ mod.fileSize = (size > 0 ) ? static_cast <uint64_t >(size) : 0 ;
179+ }
180+
181+ if (jm.contains (" requirements" ) && jm[" requirements" ].is_array ())
182+ for (auto & s : jm[" requirements" ]) mod.requirements .push_back (s.get <std::string>());
183+ if (jm.contains (" tags" ) && jm[" tags" ].is_array ())
184+ for (auto & s : jm[" tags" ]) mod.tags .push_back (s.get <std::string>());
185+ if (!RepoManager::validateUrl (mod.download )) {
186+ LOG_WARN (" RepoManager: skipping mod '%s' - invalid download URL" , mod.id .c_str ());
187+ continue ;
188+ }
189+ game.mods .push_back (std::move (mod));
190+ }
191+ if (game.mods .empty ()) return std::nullopt ;
192+ return game;
193+ } catch (const nlohmann::json::exception& e) {
194+ LOG_WARN (" RepoManager: JSON parse error: %s" , e.what ());
195+ return std::nullopt ;
177196 }
178-
179- if (game.mods .empty ()) return std::nullopt ;
180- return game;
181197}
182198
199+
183200void RepoManager::parseGame (const std::string& json) {
184201 try {
185202 auto game = parseGameFromJson (json);
0 commit comments