1use std::path::{Path, PathBuf};
2
3use rusqlite::{params_from_iter, Connection, OpenFlags, Row};
4
5use crate::{
6 error::{Error, ErrorKind, Result},
7 prelude::{Game, GameType},
8 utils::sqlite::repeat_vars,
9};
10
11pub fn read_all(file: &Path, launcher_executable: &Path) -> Result<Vec<Game>> {
12 let conn =
13 Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
14 Error::new(
15 ErrorKind::InvalidManifest,
16 format!("Invalid GOG manifest: {}", error.to_string()),
17 )
18 })?;
19
20 let mut manifest_ids = Vec::new();
21
22 {
23 let mut stmt = conn
24 .prepare("SELECT releaseKey FROM LibraryReleases WHERE releaseKey LIKE 'gog_%'")
25 .map_err(|error| {
26 Error::new(
27 ErrorKind::InvalidManifest,
28 format!(
29 "Error to read the GOG manifest (LibraryReleases): {}",
30 error.to_string()
31 ),
32 )
33 })?;
34
35 let rows = stmt
36 .query_map([], |row| parse_library_releases(row))
37 .map_err(|error| match error {
38 rusqlite::Error::QueryReturnedNoRows => Error::new(
39 ErrorKind::LibraryNotFound,
40 format!("GOG library could be empty"),
41 ),
42 _ => Error::new(
43 ErrorKind::InvalidManifest,
44 format!(
45 "Error to read the GOG manifest (LibraryReleases): {}",
46 error.to_string()
47 ),
48 ),
49 })?;
50
51 for id in rows {
52 manifest_ids.push(id?.replace("gog_", ""));
53 }
54 }
55
56 let mut stmt = conn
57 .prepare(
58 format!(
59 "SELECT * FROM LimitedDetails WHERE productId IN ({})",
60 repeat_vars(manifest_ids.len())
61 )
62 .as_str(),
63 )
64 .map_err(|error| {
65 Error::new(
66 ErrorKind::InvalidManifest,
67 format!(
68 "Error to read the GOG manifest (LimitedDetails): {}",
69 error.to_string()
70 ),
71 )
72 })?;
73
74 let game_columns = stmt
75 .column_names()
76 .into_iter()
77 .map(String::from)
78 .collect::<Vec<String>>();
79
80 let manifest_games = stmt
81 .query_map(params_from_iter(manifest_ids.iter()), |row| {
82 parse_limited_details(&game_columns, row, launcher_executable)
83 })
84 .map_err(|error| {
85 Error::new(
86 ErrorKind::InvalidManifest,
87 format!(
88 "Error to read the GOG manifest (LimitedDetails): {}",
89 error.to_string()
90 ),
91 )
92 })?;
93
94 let mut games = Vec::<Game>::new();
95
96 for game in manifest_games {
97 let mut game = game?;
98
99 let path = conn
100 .prepare("SELECT installationPath FROM InstalledBaseProducts WHERE productId = :id")
101 .and_then(|mut statement| {
102 statement.query_row(&[(":id", &game.id)], parse_installed_base_product)
103 })
104 .map_err(|error| match error {
105 rusqlite::Error::QueryReturnedNoRows => Error::new(
106 ErrorKind::LibraryNotFound,
107 format!("GOG library could be empty"),
108 ),
109 _ => Error::new(
110 ErrorKind::InvalidManifest,
111 format!(
112 "Error to read the GOG manifest (Manifests): {}",
113 error.to_string()
114 ),
115 ),
116 });
117
118 match path {
119 Ok(path) => {
120 game.path = Some(path.clone());
121 game.commands.launch = Some(vec![
122 launcher_executable.display().to_string(),
123 String::from("/command=runGame"),
124 format!("/gameId={}", &game.id),
125 format!("/path={}", &path.display().to_string()),
126 ]);
127 game.state.installed = true;
128 }
129 Err(error) => match error.kind() {
130 ErrorKind::LibraryNotFound => {
131 game.state.installed = false;
132 }
133 _ => {
134 return Err(error);
135 }
136 },
137 }
138
139 games.push(game);
140 }
141
142 return Ok(games);
143}
144
145pub fn read(id: &str, file: &Path, launcher_executable: &Path) -> Result<Game> {
146 let conn =
147 Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
148 Error::new(
149 ErrorKind::InvalidManifest,
150 format!("Invalid GOG manifest: {}", error.to_string()),
151 )
152 })?;
153
154 let mut stmt = conn
155 .prepare("SELECT * FROM LimitedDetails WHERE productId = :id")
156 .map_err(|error| {
157 Error::new(
158 ErrorKind::InvalidManifest,
159 format!(
160 "Error to read the GOG manifest (LimitedDetails): {}",
161 error.to_string()
162 ),
163 )
164 })?;
165
166 let game_columns = stmt
167 .column_names()
168 .into_iter()
169 .map(String::from)
170 .collect::<Vec<String>>();
171
172 let mut game = stmt
173 .query_row(&[(":id", &id)], |row| {
174 parse_limited_details(&game_columns, row, launcher_executable)
175 })
176 .map_err(|error| {
177 Error::new(
178 ErrorKind::InvalidManifest,
179 format!(
180 "Error to read the GOG manifest (LimitedDetails): {}",
181 error.to_string()
182 ),
183 )
184 })?;
185
186 let path = conn
187 .prepare("SELECT installationPath FROM InstalledBaseProducts WHERE productId = :id")
188 .map_err(|error| {
189 Error::new(
190 ErrorKind::InvalidManifest,
191 format!(
192 "Error to read the GOG manifest (Manifests): {}",
193 error.to_string()
194 ),
195 )
196 })
197 .and_then(|mut statement| {
198 statement
199 .query_row(&[(":id", &game.id)], parse_installed_base_product)
200 .map_err(|error| match error {
201 rusqlite::Error::QueryReturnedNoRows => Error::new(
202 ErrorKind::LibraryNotFound,
203 format!("GOG library could be empty"),
204 ),
205 _ => Error::new(
206 ErrorKind::InvalidManifest,
207 format!(
208 "Error to read the GOG manifest (Manifests): {}",
209 error.to_string()
210 ),
211 ),
212 })
213 });
214
215 match path {
216 Ok(path) => {
217 game.path = Some(path.clone());
218 game.commands.launch = Some(vec![
219 launcher_executable.display().to_string(),
220 String::from("/command=runGame"),
221 format!("/gameId={}", &game.id),
222 format!("/path={}", &path.display().to_string()),
223 ]);
224
225 if cfg!(target_os = "windows") {
226 game.commands.uninstall =
227 Some(vec![path.join("unins000.exe").display().to_string()]);
228 }
229
230 game.state.installed = true;
231 }
232 Err(error) => match error.kind() {
233 ErrorKind::LibraryNotFound => {
234 game.state.installed = false;
235 }
236 _ => {
237 return Err(error);
238 }
239 },
240 }
241
242 Ok(game)
243}
244
245fn parse_library_releases(row: &Row) -> rusqlite::Result<String> {
246 row.get::<_, String>(0)
247}
248
249fn parse_limited_details(
250 columns: &Vec<String>,
251 row: &Row,
252 launcher_executable: &Path,
253) -> rusqlite::Result<Game> {
254 let mut game = Game::default();
255 game._type = GameType::GOG.to_string();
256
257 for col in 0..columns.len() {
258 let name = columns.get(col).unwrap();
259
260 match name.as_str() {
261 "productId" => game.id = row.get::<_, i64>(col)?.to_string(),
262 "title" => game.name = row.get(col)?,
263 _ => {}
264 }
265 }
266
267 game.commands.install = Some(vec![
268 launcher_executable.display().to_string(),
269 String::from("/command=installGame"),
270 format!("/gameId={}", &game.id),
271 ]);
272
273 Ok(game)
274}
275
276fn parse_installed_base_product(row: &Row) -> rusqlite::Result<PathBuf> {
277 row.get::<_, String>(0).map(PathBuf::from)
278}