369 lines10 KBC
1/*
2store.c: see store.h.
3
4Install targets (v1):
5 homebrew /dev_hdd0/packages/<file>.pkg, for Package Manager > Install Package Files
6 theme /dev_hdd0/theme/<file>.p3t, where the XMB lists themes (Settings > Theme
7 Settings > Theme), as themes.ps3.world's guide
8 avatar /dev_hdd0/home/<profile>/psn_avatar/<file>.edat, as avatars.ps3.world's guide
9*/
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14#include "mbedtls/sha256.h"
15#include "plat.h"
16#include "download.h"
17#include "store.h"
18#include "lang.h"
19
20#define MAX_RECORDS 4096
21#define HOME_DIR "/dev_hdd0/home"
22
23typedef struct {
24 char id[64];
25 char sha[65];
26 char path[300];
27 char title[128]; // (lines from before 0.6.0 have none)
28 long long when; // time() it finished, 0 if not known
29 long long size; // on disk, read at start
30} record_t;
31
32static record_t *records;
33static int num_records;
34static char avatar_profile[64];
35static char cache_dir[512], icons_dir[512], previews_dir[512], files_dir[512];
36
37static void Hex(const unsigned char *in, char *out)
38{
39 for (int i = 0; i < 32; i++) snprintf(out + i * 2, 3, "%02x", in[i]);
40}
41
42/*
43==================
44Settings and records
45==================
46*/
47static void Settings_Load(void)
48{
49 char path[512];
50 snprintf(path, sizeof(path), "%s/settings.txt", plat_save_dir());
51 char *s = (char *)plat_read_file(path, NULL);
52 if (!s) return;
53 const char *v = strstr(s, "avatar_profile=");
54 if (v) {
55 v += 15;
56 int n = 0;
57 while (v[n] && v[n] != '\n' && v[n] != '\r' && n < (int)sizeof(avatar_profile) - 1) n++;
58 snprintf(avatar_profile, sizeof(avatar_profile), "%.*s", n, v);
59 }
60 free(s);
61}
62
63static void Settings_Save(void)
64{
65 char path[512], buf[128];
66 snprintf(path, sizeof(path), "%s/settings.txt", plat_save_dir());
67 int n = snprintf(buf, sizeof(buf), "avatar_profile=%s\n", avatar_profile);
68 plat_write_file(path, buf, n);
69}
70
71static void Records_Load(void)
72{
73 char path[512];
74 snprintf(path, sizeof(path), "%s/downloaded.txt", plat_save_dir());
75 char *s = (char *)plat_read_file(path, NULL);
76 if (!s) return;
77 for (char *line = s, *next; line && *line && num_records < MAX_RECORDS; line = next) {
78 next = strchr(line, '\n');
79 if (next) *next++ = 0;
80 char *cr = strchr(line, '\r');
81 if (cr) *cr = 0;
82 // id, sha256, path, then (0.6.0 on) when and the title
83 char *f[5] = {line};
84 int nf = 1;
85 for (char *t = line; nf < 5 && (t = strchr(t, '\t')); nf++) { *t++ = 0; f[nf] = t; }
86 if (nf < 3) continue;
87 record_t *r = &records[num_records];
88 memset(r, 0, sizeof(*r));
89 snprintf(r->id, sizeof(r->id), "%s", f[0]);
90 snprintf(r->sha, sizeof(r->sha), "%s", f[1]);
91 snprintf(r->path, sizeof(r->path), "%s", f[2]);
92 if (nf > 3) r->when = atoll(f[3]);
93 if (nf > 4) snprintf(r->title, sizeof(r->title), "%s", f[4]);
94 if ((r->size = plat_file_size(r->path)) >= 0) // only what's still there
95 num_records++;
96 }
97 free(s);
98 plat_log("store: %d downloads recorded", num_records);
99}
100
101static void Records_Save(void)
102{
103 char path[512];
104 size_t cap = (size_t)num_records * (sizeof(record_t) + 32) + 1, n = 0; // a line: the fields, a number and tabs
105 char *buf = malloc(cap);
106 if (!buf) return;
107 for (int i = 0; i < num_records; i++) {
108 int r = snprintf(buf + n, cap - n, "%s\t%s\t%s\t%lld\t%s\n", records[i].id, records[i].sha, records[i].path,
109 records[i].when, records[i].title);
110 if (r < 0 || (size_t)r >= cap - n) break;
111 n += r;
112 }
113 snprintf(path, sizeof(path), "%s/downloaded.txt", plat_save_dir());
114 if (!plat_write_file(path, buf, n))
115 plat_log("store: couldn't write %s", path);
116 free(buf);
117}
118
119void store_init(void)
120{
121 records = calloc(MAX_RECORDS, sizeof(record_t));
122 snprintf(cache_dir, sizeof(cache_dir), "%s/cache", plat_save_dir());
123 snprintf(icons_dir, sizeof(icons_dir), "%s/icons", cache_dir);
124 snprintf(previews_dir, sizeof(previews_dir), "%s/previews", cache_dir);
125 snprintf(files_dir, sizeof(files_dir), "%s/files", cache_dir);
126 plat_mkdirs(icons_dir);
127 plat_mkdirs(previews_dir);
128 plat_mkdirs(files_dir);
129 Settings_Load();
130 if (records) Records_Load();
131}
132
133const char *store_downloaded(const cat_item_t *it)
134{
135 char sha[65];
136 Hex(it->sha256, sha);
137 for (int i = num_records - 1; i >= 0; i--)
138 if (!strcmp(records[i].id, it->id))
139 return strcmp(records[i].sha, sha) ? NULL : records[i].path; // an older version isn't this one
140 return NULL;
141}
142
143void store_mark_downloaded(const cat_item_t *it, const char *path)
144{
145 int i;
146 if (!records) return;
147 for (i = 0; i < num_records; i++)
148 if (!strcmp(records[i].id, it->id)) break;
149 if (i == num_records) {
150 if (num_records == MAX_RECORDS) return;
151 num_records++;
152 }
153 record_t *r = &records[i];
154 snprintf(r->id, sizeof(r->id), "%s", it->id);
155 Hex(it->sha256, r->sha);
156 snprintf(r->path, sizeof(r->path), "%s", path);
157 snprintf(r->title, sizeof(r->title), "%s", it->title);
158 for (char *c = r->title; *c; c++) // (a field of a tab-separated line)
159 if (*c == '\t' || *c == '\n' || *c == '\r') *c = ' ';
160 r->when = (long long)time(NULL);
161 r->size = it->size;
162 Records_Save();
163}
164
165int store_num_downloads(void) { return num_records; }
166
167// newest first
168int store_download_at(int i, store_rec_t *out)
169{
170 if (i < 0 || i >= num_records) return 0;
171 const record_t *r = &records[num_records - 1 - i];
172 out->id = r->id;
173 out->sha = r->sha;
174 out->path = r->path;
175 out->title = r->title;
176 out->when = r->when;
177 out->size = r->size;
178 return 1;
179}
180
181int store_delete_download(int i)
182{
183 if (i < 0 || i >= num_records) return 0;
184 int k = num_records - 1 - i;
185 if (remove(records[k].path) != 0 && plat_file_size(records[k].path) >= 0) {
186 plat_log("store: couldn't delete %s", records[k].path);
187 return 0;
188 }
189 plat_log("store: deleted %s", records[k].path);
190 memmove(&records[k], &records[k + 1], (num_records - 1 - k) * sizeof(record_t));
191 num_records--;
192 Records_Save();
193 return 1;
194}
195
196/*
197==================
198Cache
199==================
200*/
201cat_index_t *store_load_index(void)
202{
203 char path[600], err[160];
204 size_t len;
205 snprintf(path, sizeof(path), "%s/catalog.json", cache_dir);
206 char *s = (char *)plat_read_file(path, &len);
207 if (!s) return NULL;
208 cat_index_t *ix = catalog_parse_index(s, len, err, sizeof(err));
209 free(s);
210 if (!ix) plat_log("store: cached catalogue unusable: %s", err);
211 return ix;
212}
213
214void store_save_index(const char *json, size_t len)
215{
216 char path[600];
217 snprintf(path, sizeof(path), "%s/catalog.json", cache_dir);
218 if (!plat_write_file(path, json, len))
219 plat_log("store: couldn't cache the catalogue in %s", path);
220}
221
222char *store_get_file(const cat_file_t *f, size_t *len, volatile int *cancel, int *err_kind, char *err, int errsize)
223{
224 char path[600], hex[65], url[300];
225 unsigned char got[32];
226 Hex(f->sha256, hex);
227 snprintf(path, sizeof(path), "%s/%s", files_dir, hex);
228 *err_kind = ERR_NONE;
229 err[0] = 0;
230
231 char *s = (char *)plat_read_file(path, len);
232 if (s) {
233 mbedtls_sha256((const unsigned char *)s, *len, got, 0);
234 if (!memcmp(got, f->sha256, 32))
235 return s;
236 free(s); // damaged: fetch it again
237 }
238 http_res_t res;
239 snprintf(url, sizeof(url), "%s%s", CATALOG_BASE, f->url);
240 s = http_fetch(NULL, url, NULL, NULL, 30, cancel, len, &res);
241 if (!s || res.status != 200) {
242 *err_kind = download_error_kind(res.err, s ? res.status : 0);
243 snprintf(err, errsize, "%s", s ? "HTTP error" : res.err);
244 if (s) snprintf(err, errsize, "HTTP %d", res.status);
245 free(s);
246 return NULL;
247 }
248 mbedtls_sha256((const unsigned char *)s, *len, got, 0);
249 if (memcmp(got, f->sha256, 32)) {
250 // the index and the page are from different uploads (or the upload is half done)
251 *err_kind = ERR_CHECKSUM;
252 snprintf(err, errsize, tr("%s doesn't match the catalogue (it may be being updated): try again later"), f->url);
253 free(s);
254 return NULL;
255 }
256 if (!plat_write_file(path, s, *len))
257 plat_log("store: couldn't cache %s", path);
258 return s;
259}
260
261// a cached picture is named by its item and where it came from: when the catalogue
262// points it somewhere new (a sharper one), it's fetched again
263static unsigned Url_Hash(const char *s)
264{
265 unsigned h = 2166136261u;
266 for (; *s; s++) h = (h ^ (unsigned char)*s) * 16777619u;
267 return h;
268}
269
270void store_icon_path(const cat_item_t *it, char *out, int size)
271{
272 snprintf(out, size, "%s/%s-%08x.jpg", icons_dir, it->id, Url_Hash(it->icon));
273}
274
275void store_preview_path(const cat_item_t *it, char *out, int size)
276{
277 snprintf(out, size, "%s/%s-%08x", previews_dir, it->id, Url_Hash(it->preview));
278}
279
280/*
281==================
282Install targets
283==================
284*/
285
286// the file name from the URL's last part: %XX decoded, and nothing FAT32 refuses
287static void File_Name(const char *url, char *out, int size)
288{
289 const char *b = strrchr(url, '/');
290 b = b ? b + 1 : url;
291 int o = 0;
292 for (; *b && o < size - 1; b++) {
293 char ch = *b;
294 if (ch == '%' && b[1] && b[2]) {
295 char hex[3] = {b[1], b[2], 0};
296 ch = (char)strtol(hex, NULL, 16);
297 b += 2;
298 }
299 if ((unsigned char)ch < 0x20 || strchr("\\/:*?\"<>|", ch)) ch = '_';
300 out[o++] = ch;
301 }
302 out[o] = 0;
303 if (!o) snprintf(out, size, "download");
304}
305
306int store_profiles(char (*names)[64], int max)
307{
308 char all[16][64];
309 int n = plat_list_dirs(HOME_DIR, all, 16), k = 0;
310 for (int i = 0; i < n && k < max; i++)
311 if (strlen(all[i]) == 8 && strspn(all[i], "0123456789") == 8) // 00000001 ...
312 snprintf(names[k++], 64, "%s", all[i]);
313 return k;
314}
315
316const char *store_avatar_profile(void)
317{
318 if (avatar_profile[0]) {
319 char p[128];
320 snprintf(p, sizeof(p), HOME_DIR "/%s", avatar_profile);
321 if (plat_dir_exists(p)) return avatar_profile;
322 }
323 char names[16][64];
324 if (store_profiles(names, 16) != 1)
325 return ""; // none, or more than one: the user picks
326 snprintf(avatar_profile, sizeof(avatar_profile), "%s", names[0]);
327 return avatar_profile;
328}
329
330void store_set_avatar_profile(const char *name)
331{
332 snprintf(avatar_profile, sizeof(avatar_profile), "%s", name);
333 Settings_Save();
334}
335
336int store_dest(const cat_item_t *it, char *out, int size)
337{
338 char name[256];
339 File_Name(it->url, name, sizeof(name));
340 switch (it->type) {
341 case TYPE_HOMEBREW:
342 snprintf(out, size, "/dev_hdd0/packages/%s", name);
343 return DEST_OK;
344 case TYPE_THEME:
345 snprintf(out, size, "/dev_hdd0/theme/%s", name);
346 return DEST_OK;
347 case TYPE_AVATAR: {
348 const char *p = store_avatar_profile();
349 if (!p[0]) {
350 char names[16][64];
351 return store_profiles(names, 16) ? DEST_PICK_PROFILE : DEST_NO_PROFILE;
352 }
353 snprintf(out, size, HOME_DIR "/%s/psn_avatar/%s", p, name);
354 return DEST_OK;
355 }
356 }
357 return DEST_NO_PROFILE;
358}
359
360const char *store_dest_message(int code)
361{
362 switch (code) {
363 case DEST_NO_PROFILE:
364 return "No user profiles were found in /dev_hdd0/home.";
365 case DEST_PICK_PROFILE:
366 return "Choose which user the avatar is for.";
367 }
368 return "";
369}