From 0ef50e1f404485ea726fb7f8a62f1f344166cd73 Mon Sep 17 00:00:00 2001 From: Steven Smith Date: Thu, 21 Apr 2016 21:21:22 -0700 Subject: [PATCH] Add QR code installation over HTTP, other misc fixes and cleanups. --- buildtools | 2 +- .../{smdh_info_box.png => meta_info_box.png} | Bin ...ox_shadow.png => meta_info_box_shadow.png} | Bin source/main.c | 7 +- source/quirc/LICENSE | 16 + source/quirc/decode.c | 884 ++++++++++++ source/quirc/identify.c | 1186 +++++++++++++++++ source/quirc/quirc.c | 81 ++ source/quirc/quirc.h | 145 ++ source/quirc/quirc_internal.h | 103 ++ source/quirc/version_db.c | 421 ++++++ source/screen.c | 19 +- source/screen.h | 4 +- source/ui/info.c | 5 +- source/ui/mainmenu.c | 3 +- source/ui/section/extsavedata.c | 1 + source/ui/section/files.c | 1 + source/ui/section/networkinstall.c | 4 +- source/ui/section/pendingtitles.c | 1 + source/ui/section/qrinstall.c | 385 ++++++ source/ui/section/section.h | 1 + source/ui/section/systemsavedata.c | 1 + source/ui/section/task/capturecam.c | 163 +++ source/ui/section/task/listextsavedata.c | 23 +- source/ui/section/task/listfiles.c | 104 +- source/ui/section/task/listpendingtitles.c | 6 +- source/ui/section/task/listsystemsavedata.c | 6 +- source/ui/section/task/listtickets.c | 6 +- source/ui/section/task/listtitles.c | 478 ++++--- source/ui/section/task/task.h | 35 +- source/ui/section/tickets.c | 1 + source/ui/section/titles.c | 1 + source/ui/ui.c | 212 +-- source/ui/ui.h | 3 +- 34 files changed, 3860 insertions(+), 448 deletions(-) rename romfs/{smdh_info_box.png => meta_info_box.png} (100%) rename romfs/{smdh_info_box_shadow.png => meta_info_box_shadow.png} (100%) create mode 100644 source/quirc/LICENSE create mode 100644 source/quirc/decode.c create mode 100644 source/quirc/identify.c create mode 100644 source/quirc/quirc.c create mode 100644 source/quirc/quirc.h create mode 100644 source/quirc/quirc_internal.h create mode 100644 source/quirc/version_db.c create mode 100644 source/ui/section/qrinstall.c create mode 100644 source/ui/section/task/capturecam.c diff --git a/buildtools b/buildtools index 8ed050d..8d891f1 160000 --- a/buildtools +++ b/buildtools @@ -1 +1 @@ -Subproject commit 8ed050d92a96c81bef4deab42e85c4351c05d1ef +Subproject commit 8d891f1ab9f37df9d9edad1d215d090f31ebbb11 diff --git a/romfs/smdh_info_box.png b/romfs/meta_info_box.png similarity index 100% rename from romfs/smdh_info_box.png rename to romfs/meta_info_box.png diff --git a/romfs/smdh_info_box_shadow.png b/romfs/meta_info_box_shadow.png similarity index 100% rename from romfs/smdh_info_box_shadow.png rename to romfs/meta_info_box_shadow.png diff --git a/source/main.c b/source/main.c index bb5e776..52cd8df 100644 --- a/source/main.c +++ b/source/main.c @@ -26,6 +26,7 @@ void cleanup() { } amExit(); + httpcExit(); ptmuExit(); acExit(); cfguExit(); @@ -57,6 +58,7 @@ int main(int argc, const char* argv[]) { cfguInit(); acInit(); ptmuInit(); + httpcInit(0); amInit(); AM_InitializeExternalTitleDatabase(false); @@ -71,10 +73,7 @@ int main(int argc, const char* argv[]) { mainmenu_open(); - while(aptMainLoop() && ui_top() != NULL) { - ui_update(); - ui_draw(); - } + while(aptMainLoop() && ui_update()); cleanup(); return 0; diff --git a/source/quirc/LICENSE b/source/quirc/LICENSE new file mode 100644 index 0000000..d47c026 --- /dev/null +++ b/source/quirc/LICENSE @@ -0,0 +1,16 @@ +quirc -- QR-code recognition library +Copyright (C) 2010-2012 Daniel Beer + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted, provided that the +above copyright notice and this permission notice appear in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/source/quirc/decode.c b/source/quirc/decode.c new file mode 100644 index 0000000..45e1e3c --- /dev/null +++ b/source/quirc/decode.c @@ -0,0 +1,884 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "quirc_internal.h" + +#include +#include + +#define MAX_POLY 64 + +/************************************************************************ + * Galois fields + */ + +struct galois_field { + int p; + const uint8_t *log; + const uint8_t *exp; +}; + +static const uint8_t gf16_exp[16] = { + 0x01, 0x02, 0x04, 0x08, 0x03, 0x06, 0x0c, 0x0b, + 0x05, 0x0a, 0x07, 0x0e, 0x0f, 0x0d, 0x09, 0x01 +}; + +static const uint8_t gf16_log[16] = { + 0x00, 0x0f, 0x01, 0x04, 0x02, 0x08, 0x05, 0x0a, + 0x03, 0x0e, 0x09, 0x07, 0x06, 0x0d, 0x0b, 0x0c +}; + +static const struct galois_field gf16 = { + .p = 15, + .log = gf16_log, + .exp = gf16_exp +}; + +static const uint8_t gf256_exp[256] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, + 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, + 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, + 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, + 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, + 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, + 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, + 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, + 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, + 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, + 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, + 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, + 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, + 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, + 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, + 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, + 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, + 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, + 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, + 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, + 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, + 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, + 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, + 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, + 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, + 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, + 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01 +}; + +static const uint8_t gf256_log[256] = { + 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, + 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, + 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, + 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, + 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, + 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, + 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, + 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, + 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, + 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, + 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, + 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, + 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, + 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, + 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, + 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, + 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, + 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, + 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, + 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, + 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, + 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, + 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, + 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, + 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, + 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, + 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, + 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, + 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, + 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, + 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf +}; + +const static struct galois_field gf256 = { + .p = 255, + .log = gf256_log, + .exp = gf256_exp +}; + +/************************************************************************ + * Polynomial operations + */ + +static void poly_mult(uint8_t *r, const uint8_t *a, const uint8_t *b, + const struct galois_field *gf) +{ + int i; + + memset(r, 0, MAX_POLY); + + for (i = 0; i < MAX_POLY; i++) { + int j; + + for (j = 0; j + i < MAX_POLY; j++) { + uint8_t ca = a[i]; + uint8_t cb = b[j]; + + if (!(ca && cb)) + continue; + + r[i + j] ^= gf->exp[(gf->log[ca] + + gf->log[cb]) % + gf->p]; + } + } +} + +static void poly_add(uint8_t *dst, const uint8_t *src, uint8_t c, + int shift, const struct galois_field *gf) +{ + int i; + int log_c = gf->log[c]; + + if (!c) + return; + + for (i = 0; i < MAX_POLY; i++) { + int p = i + shift; + uint8_t v = src[i]; + + if (p < 0 || p >= MAX_POLY) + continue; + if (!v) + continue; + + dst[p] ^= gf->exp[(gf->log[v] + log_c) % gf->p]; + } +} + +static uint8_t poly_eval(const uint8_t *s, uint8_t x, + const struct galois_field *gf) +{ + int i; + uint8_t sum = 0; + uint8_t log_x = gf->log[x]; + + if (!x) + return s[0]; + + for (i = 0; i < MAX_POLY; i++) { + uint8_t c = s[i]; + + if (!c) + continue; + + sum ^= gf->exp[(gf->log[c] + log_x * i) % gf->p]; + } + + return sum; +} + +/************************************************************************ + * Berlekamp-Massey algorithm for finding error locator polynomials. + */ + +static void berlekamp_massey(const uint8_t *s, int N, + const struct galois_field *gf, + uint8_t *sigma) +{ + uint8_t C[MAX_POLY]; + uint8_t B[MAX_POLY]; + int L = 0; + int m = 1; + uint8_t b = 1; + int n; + + memset(B, 0, sizeof(B)); + memset(C, 0, sizeof(C)); + B[0] = 1; + C[0] = 1; + + for (n = 0; n < N; n++) { + uint8_t d = s[n]; + uint8_t mult; + int i; + + for (i = 1; i <= L; i++) { + if (!(C[i] && s[n - i])) + continue; + + d ^= gf->exp[(gf->log[C[i]] + + gf->log[s[n - i]]) % + gf->p]; + } + + mult = gf->exp[(gf->p - gf->log[b] + gf->log[d]) % gf->p]; + + if (!d) { + m++; + } else if (L * 2 <= n) { + uint8_t T[MAX_POLY]; + + memcpy(T, C, sizeof(T)); + poly_add(C, B, mult, m, gf); + memcpy(B, T, sizeof(B)); + L = n + 1 - L; + b = d; + m = 1; + } else { + poly_add(C, B, mult, m, gf); + m++; + } + } + + memcpy(sigma, C, MAX_POLY); +} + +/************************************************************************ + * Code stream error correction + * + * Generator polynomial for GF(2^8) is x^8 + x^4 + x^3 + x^2 + 1 + */ + +static int block_syndromes(const uint8_t *data, int bs, int npar, uint8_t *s) +{ + int nonzero = 0; + int i; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < npar; i++) { + int j; + + for (j = 0; j < bs; j++) { + uint8_t c = data[bs - j - 1]; + + if (!c) + continue; + + s[i] ^= gf256_exp[((int)gf256_log[c] + + (i + 1) * j) % 255]; + } + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static quirc_decode_error_t correct_block(uint8_t *data, const struct quirc_rs_params *ecc) +{ + int npar = ecc->ce; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + uint8_t sigma_deriv[MAX_POLY]; + uint8_t omega[MAX_POLY]; + int i; + + /* Compute syndrome vector */ + if (!block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, npar, &gf256, sigma); + + /* Compute derivative of sigma */ + memset(sigma_deriv, 0, MAX_POLY); + for (i = 0; i + 1 < MAX_POLY; i += 2) + sigma_deriv[i] = sigma[i + 1]; + + /* Compute error evaluator polynomial */ + poly_mult(omega, sigma, s, &gf256); + memset(omega + npar, 0, MAX_POLY - npar); + + /* Find error locations and magnitudes */ + for (i = 0; i < ecc->bs; i++) { + uint8_t xinv = gf256_exp[255 - i]; + + if (!poly_eval(sigma, xinv, &gf256)) { + uint8_t sd_x = poly_eval(sigma_deriv, xinv, &gf256); + uint8_t omega_x = poly_eval(omega, xinv, &gf256); + uint8_t error = gf256_exp[(255 - gf256_log[sd_x] + + gf256_log[omega_x]) % 255]; + + data[ecc->bs - i - 1] ^= error; + } + } + + if (block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_ERROR_DATA_ECC; + + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Format value error correction + * + * Generator polynomial for GF(2^4) is x^4 + x + 1 + */ + +#define FORMAT_MAX_ERROR 3 +#define FORMAT_SYNDROMES (FORMAT_MAX_ERROR * 2) +#define FORMAT_BITS 15 + +static int format_syndromes(uint16_t u, uint8_t *s) +{ + int i; + int nonzero = 0; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < FORMAT_SYNDROMES; i++) { + int j; + + s[i] = 0; + for (j = 0; j < FORMAT_BITS; j++) + if (u & (1 << j)) + s[i] ^= gf16_exp[((i + 1) * j) % 15]; + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static quirc_decode_error_t correct_format(uint16_t *f_ret) +{ + uint16_t u = *f_ret; + int i; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + + /* Evaluate U (received codeword) at each of alpha_1 .. alpha_6 + * to get S_1 .. S_6 (but we index them from 0). + */ + if (!format_syndromes(u, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, FORMAT_SYNDROMES, &gf16, sigma); + + /* Now, find the roots of the polynomial */ + for (i = 0; i < 15; i++) + if (!poly_eval(sigma, gf16_exp[15 - i], &gf16)) + u ^= (1 << i); + + if (format_syndromes(u, s)) + return QUIRC_ERROR_FORMAT_ECC; + + *f_ret = u; + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Decoder algorithm + */ + +struct datastream { + uint8_t raw[QUIRC_MAX_PAYLOAD]; + int data_bits; + int ptr; + + uint8_t data[QUIRC_MAX_PAYLOAD]; +}; + +static inline int grid_bit(const struct quirc_code *code, int x, int y) +{ + int p = y * code->size + x; + + return (code->cell_bitmap[p >> 3] >> (p & 7)) & 1; +} + +static quirc_decode_error_t read_format(const struct quirc_code *code, + struct quirc_data *data, int which) +{ + int i; + uint16_t format = 0; + uint16_t fdata; + quirc_decode_error_t err; + + if (which) { + for (i = 0; i < 7; i++) + format = (format << 1) | + grid_bit(code, 8, code->size - 1 - i); + for (i = 0; i < 8; i++) + format = (format << 1) | + grid_bit(code, code->size - 8 + i, 8); + } else { + static const int xs[15] = { + 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 + }; + static const int ys[15] = { + 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + for (i = 14; i >= 0; i--) + format = (format << 1) | grid_bit(code, xs[i], ys[i]); + } + + format ^= 0x5412; + + err = correct_format(&format); + if (err) + return err; + + fdata = format >> 10; + data->ecc_level = fdata >> 3; + data->mask = fdata & 7; + + return QUIRC_SUCCESS; +} + +static int mask_bit(int mask, int i, int j) +{ + switch (mask) { + case 0: return !((i + j) % 2); + case 1: return !(i % 2); + case 2: return !(j % 3); + case 3: return !((i + j) % 3); + case 4: return !(((i / 2) + (j / 3)) % 2); + case 5: return !((i * j) % 2 + (i * j) % 3); + case 6: return !(((i * j) % 2 + (i * j) % 3) % 2); + case 7: return !(((i * j) % 3 + (i + j) % 2) % 2); + } + + return 0; +} + +static int reserved_cell(int version, int i, int j) +{ + const struct quirc_version_info *ver = &quirc_version_db[version]; + int size = version * 4 + 17; + int ai = -1, aj = -1, a; + + /* Finder + format: top left */ + if (i < 9 && j < 9) + return 1; + + /* Finder + format: bottom left */ + if (i + 8 >= size && j < 9) + return 1; + + /* Finder + format: top right */ + if (i < 9 && j + 8 >= size) + return 1; + + /* Exclude timing patterns */ + if (i == 6 || j == 6) + return 1; + + /* Exclude version info, if it exists. Version info sits adjacent to + * the top-right and bottom-left finders in three rows, bounded by + * the timing pattern. + */ + if (version >= 7) { + if (i < 6 && j + 11 >= size) + return 1; + if (i + 11 >= size && j < 6) + return 1; + } + + /* Exclude alignment patterns */ + for (a = 0; a < QUIRC_MAX_ALIGNMENT && ver->apat[a]; a++) { + int p = ver->apat[a]; + + if (abs(p - i) < 3) + ai = a; + if (abs(p - j) < 3) + aj = a; + } + + if (ai >= 0 && aj >= 0) { + a--; + if (ai > 0 && ai < a) + return 1; + if (aj > 0 && aj < a) + return 1; + if (aj == a && ai == a) + return 1; + } + + return 0; +} + +static void read_bit(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds, int i, int j) +{ + int bitpos = ds->data_bits & 7; + int bytepos = ds->data_bits >> 3; + int v = grid_bit(code, j, i); + + if (mask_bit(data->mask, i, j)) + v ^= 1; + + if (v) + ds->raw[bytepos] |= (0x80 >> bitpos); + + ds->data_bits++; +} + +static void read_data(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds) +{ + int y = code->size - 1; + int x = code->size - 1; + int dir = -1; + + while (x > 0) { + if (x == 6) + x--; + + if (!reserved_cell(data->version, y, x)) + read_bit(code, data, ds, y, x); + + if (!reserved_cell(data->version, y, x - 1)) + read_bit(code, data, ds, y, x - 1); + + y += dir; + if (y < 0 || y >= code->size) { + dir = -dir; + x -= 2; + y += dir; + } + } +} + +static quirc_decode_error_t codestream_ecc(struct quirc_data *data, + struct datastream *ds) +{ + const struct quirc_version_info *ver = + &quirc_version_db[data->version]; + const struct quirc_rs_params *sb_ecc = &ver->ecc[data->ecc_level]; + struct quirc_rs_params lb_ecc; + int bc = ver->data_bytes / sb_ecc->bs; + int dst_offset = 0; + int lb_count = ver->data_bytes - bc * sb_ecc->bs; + int small_dw_total = bc * sb_ecc->dw; + int i; + + memcpy(&lb_ecc, sb_ecc, sizeof(lb_ecc)); + lb_ecc.dw++; + lb_ecc.bs++; + + for (i = 0; i < bc; i++) { + uint8_t *dst = ds->data + dst_offset; + const struct quirc_rs_params *ecc = sb_ecc; + quirc_decode_error_t err; + int j = 0; + int k; + + for (k = 0; k < sb_ecc->dw; k++) + dst[j++] = ds->raw[k * bc + i]; + + if (i + lb_count >= bc) { + dst[j++] = ds->raw[small_dw_total + i - lb_count]; + ecc = &lb_ecc; + } + + for (k = 0; k < sb_ecc->bs - sb_ecc->dw; k++) + dst[j++] = ds->raw[small_dw_total + lb_count + i + + k * bc]; + + err = correct_block(dst, ecc); + if (err) + return err; + + dst_offset += ecc->dw; + } + + ds->data_bits = dst_offset * 8; + + return QUIRC_SUCCESS; +} + +static inline int bits_remaining(const struct datastream *ds) +{ + return ds->data_bits - ds->ptr; +} + +static int take_bits(struct datastream *ds, int len) +{ + int ret = 0; + + while (len && (ds->ptr < ds->data_bits)) { + uint8_t b = ds->data[ds->ptr >> 3]; + int bitpos = ds->ptr & 7; + + ret <<= 1; + if ((b << bitpos) & 0x80) + ret |= 1; + + ds->ptr++; + len--; + } + + return ret; +} + +static int numeric_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = digits - 1; i >= 0; i--) { + data->payload[data->payload_len + i] = tuple % 10 + '0'; + tuple /= 10; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_numeric(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 14; + int count; + + if (data->version < 10) + bits = 10; + else if (data->version < 27) + bits = 12; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 3) { + if (numeric_tuple(data, ds, 10, 3) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 3; + } + + if (count >= 2) { + if (numeric_tuple(data, ds, 7, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (numeric_tuple(data, ds, 4, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static int alpha_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = 0; i < digits; i++) { + static const char *alpha_map = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + data->payload[data->payload_len + digits - i - 1] = + alpha_map[tuple % 45]; + tuple /= 45; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_alpha(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 13; + int count; + + if (data->version < 7) + bits = 9; + else if (data->version < 11) + bits = 10; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 2) { + if (alpha_tuple(data, ds, 11, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (alpha_tuple(data, ds, 6, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_byte(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 16; + int count; + int i; + + if (data->version < 10) + bits = 8; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) + data->payload[data->payload_len++] = take_bits(ds, 8); + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_kanji(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 12; + int count; + int i; + + if (data->version < 10) + bits = 8; + else if (data->version < 27) + bits = 10; + + count = take_bits(ds, bits); + if (data->payload_len + count * 2 + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 13) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) { + int d = take_bits(ds, 13); + uint16_t sjw; + + if (d + 0x8140 >= 0x9ffc) + sjw = d + 0x8140; + else + sjw = d + 0xc140; + + data->payload[data->payload_len++] = sjw >> 8; + data->payload[data->payload_len++] = sjw & 0xff; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_payload(struct quirc_data *data, + struct datastream *ds) +{ + while (bits_remaining(ds) >= 4) { + quirc_decode_error_t err = QUIRC_SUCCESS; + int type = take_bits(ds, 4); + + switch (type) { + case QUIRC_DATA_TYPE_NUMERIC: + err = decode_numeric(data, ds); + break; + + case QUIRC_DATA_TYPE_ALPHA: + err = decode_alpha(data, ds); + break; + + case QUIRC_DATA_TYPE_BYTE: + err = decode_byte(data, ds); + break; + + case QUIRC_DATA_TYPE_KANJI: + err = decode_kanji(data, ds); + break; + + default: + goto done; + } + + if (err) + return err; + + if (type > data->data_type) + data->data_type = type; + } +done: + + /* Add nul terminator to all payloads */ + if (data->payload_len >= sizeof(data->payload)) + data->payload_len--; + data->payload[data->payload_len] = 0; + + return QUIRC_SUCCESS; +} + +quirc_decode_error_t quirc_decode(const struct quirc_code *code, + struct quirc_data *data) +{ + quirc_decode_error_t err; + struct datastream ds; + + if ((code->size - 17) % 4) + return QUIRC_ERROR_INVALID_GRID_SIZE; + + memset(data, 0, sizeof(*data)); + memset(&ds, 0, sizeof(ds)); + + data->version = (code->size - 17) / 4; + + if (data->version < 1 || + data->version > QUIRC_MAX_VERSION) + return QUIRC_ERROR_INVALID_VERSION; + + /* Read format information -- try both locations */ + err = read_format(code, data, 0); + if (err) + err = read_format(code, data, 1); + if (err) + return err; + + read_data(code, data, &ds); + err = codestream_ecc(data, &ds); + if (err) + return err; + + err = decode_payload(data, &ds); + if (err) + return err; + + return QUIRC_SUCCESS; +} diff --git a/source/quirc/identify.c b/source/quirc/identify.c new file mode 100644 index 0000000..21f76f0 --- /dev/null +++ b/source/quirc/identify.c @@ -0,0 +1,1186 @@ +/* quirc - QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include "quirc_internal.h" + +/************************************************************************ + * Linear algebra routines + */ + +static int line_intersect(const struct quirc_point *p0, + const struct quirc_point *p1, + const struct quirc_point *q0, + const struct quirc_point *q1, + struct quirc_point *r) +{ + /* (a, b) is perpendicular to line p */ + int a = -(p1->y - p0->y); + int b = p1->x - p0->x; + + /* (c, d) is perpendicular to line q */ + int c = -(q1->y - q0->y); + int d = q1->x - q0->x; + + /* e and f are dot products of the respective vectors with p and q */ + int e = a * p1->x + b * p1->y; + int f = c * q1->x + d * q1->y; + + /* Now we need to solve: + * [a b] [rx] [e] + * [c d] [ry] = [f] + * + * We do this by inverting the matrix and applying it to (e, f): + * [ d -b] [e] [rx] + * 1/det [-c a] [f] = [ry] + */ + int det = (a * d) - (b * c); + + if (!det) + return 0; + + r->x = (d * e - b * f) / det; + r->y = (-c * e + a * f) / det; + + return 1; +} + +static void perspective_setup(double *c, + const struct quirc_point *rect, + double w, double h) +{ + double x0 = rect[0].x; + double y0 = rect[0].y; + double x1 = rect[1].x; + double y1 = rect[1].y; + double x2 = rect[2].x; + double y2 = rect[2].y; + double x3 = rect[3].x; + double y3 = rect[3].y; + + double wden = w * (x2*y3 - x3*y2 + (x3-x2)*y1 + x1*(y2-y3)); + double hden = h * (x2*y3 + x1*(y2-y3) - x3*y2 + (x3-x2)*y1); + + c[0] = (x1*(x2*y3-x3*y2) + x0*(-x2*y3+x3*y2+(x2-x3)*y1) + + x1*(x3-x2)*y0) / wden; + c[1] = -(x0*(x2*y3+x1*(y2-y3)-x2*y1) - x1*x3*y2 + x2*x3*y1 + + (x1*x3-x2*x3)*y0) / hden; + c[2] = x0; + c[3] = (y0*(x1*(y3-y2)-x2*y3+x3*y2) + y1*(x2*y3-x3*y2) + + x0*y1*(y2-y3)) / wden; + c[4] = (x0*(y1*y3-y2*y3) + x1*y2*y3 - x2*y1*y3 + + y0*(x3*y2-x1*y2+(x2-x3)*y1)) / hden; + c[5] = y0; + c[6] = (x1*(y3-y2) + x0*(y2-y3) + (x2-x3)*y1 + (x3-x2)*y0) / wden; + c[7] = (-x2*y3 + x1*y3 + x3*y2 + x0*(y1-y2) - x3*y1 + (x2-x1)*y0) / + hden; +} + +static void perspective_map(const double *c, + double u, double v, struct quirc_point *ret) +{ + double den = c[6]*u + c[7]*v + 1.0; + double x = (c[0]*u + c[1]*v + c[2]) / den; + double y = (c[3]*u + c[4]*v + c[5]) / den; + + ret->x = rint(x); + ret->y = rint(y); +} + +static void perspective_unmap(const double *c, + const struct quirc_point *in, + double *u, double *v) +{ + double x = in->x; + double y = in->y; + double den = -c[0]*c[7]*y + c[1]*c[6]*y + (c[3]*c[7]-c[4]*c[6])*x + + c[0]*c[4] - c[1]*c[3]; + + *u = -(c[1]*(y-c[5]) - c[2]*c[7]*y + (c[5]*c[7]-c[4])*x + c[2]*c[4]) / + den; + *v = (c[0]*(y-c[5]) - c[2]*c[6]*y + (c[5]*c[6]-c[3])*x + c[2]*c[3]) / + den; +} + +/************************************************************************ + * Span-based floodfill routine + */ + +typedef struct fill_queue_node_s { + int x; + int y; + + struct fill_queue_node_s* prev; + struct fill_queue_node_s* next; +} fill_queue_node; + +typedef struct { + fill_queue_node* last; +} fill_queue; + +static void fill_queue_init(fill_queue* queue) { + queue->last = NULL; +} + +static int fill_queue_is_empty(fill_queue* queue) { + return queue->last == NULL; +} + +static void fill_queue_push(fill_queue* queue, int x, int y) { + fill_queue_node* node = (fill_queue_node*) calloc(1, sizeof(fill_queue_node)); + if(node == NULL) { + return; + } + + node->x = x; + node->y = y; + node->next = NULL; + + if(queue->last == NULL) { + node->prev = NULL; + + queue->last = node; + } else { + node->prev = queue->last; + + queue->last->next = node; + queue->last = node; + } +} + +static void fill_queue_pop(fill_queue* queue, int* x, int* y) { + fill_queue_node* node = queue->last; + if(node != NULL) { + queue->last = node->prev; + + *x = node->x; + *y = node->y; + + free(node); + } +} + +typedef void (*span_func_t)(void *user_data, int y, int left, int right); + +static void flood_fill_seed(struct quirc *q, int x, int y, int from, int to, + span_func_t func, void *user_data, + int depth) +{ + fill_queue queue; + fill_queue_init(&queue); + fill_queue_push(&queue, x, y); + + while(!fill_queue_is_empty(&queue)) { + int currX = 0; + int currY = 0; + fill_queue_pop(&queue, &currX, &currY); + + int left = currX; + int right = currX; + int i; + uint8_t *row = q->image + currY * q->w; + + if(row[currX] == to) + continue; + + while (left > 0 && row[left - 1] == from) + left--; + + while (right < q->w - 1 && row[right + 1] == from) + right++; + + /* Fill the extent */ + for (i = left; i <= right; i++) + row[i] = to; + + if (func) + func(user_data, currY, left, right); + + /* Seed new flood-fills */ + if (currY < q->h - 1) { + row = q->image + (currY + 1) * q->w; + + for (i = right; i >= left; i--) + if (row[i] == from) + fill_queue_push(&queue, i, currY + 1); + } + + if (currY > 0) { + row = q->image + (currY - 1) * q->w; + + for (i = right; i >= left; i--) + if (row[i] == from) + fill_queue_push(&queue, i, currY - 1); + } + } +} + +/************************************************************************ + * Adaptive thresholding + */ + +#define THRESHOLD_S_DEN 8 +#define THRESHOLD_T 5 + +static void threshold(struct quirc *q) +{ + int x, y; + int avg_w = 0; + int avg_u = 0; + int threshold_s = q->w / THRESHOLD_S_DEN; + uint8_t *row = q->image; + + for (y = 0; y < q->h; y++) { + int row_average[q->w]; + + memset(row_average, 0, sizeof(row_average)); + + for (x = 0; x < q->w; x++) { + int w, u; + + if (y & 1) { + w = x; + u = q->w - 1 - x; + } else { + w = q->w - 1 - x; + u = x; + } + + avg_w = (avg_w * (threshold_s - 1)) / + threshold_s + row[w]; + avg_u = (avg_u * (threshold_s - 1)) / + threshold_s + row[u]; + + row_average[w] += avg_w; + row_average[u] += avg_u; + } + + for (x = 0; x < q->w; x++) { + if (row[x] < row_average[x] * + (100 - THRESHOLD_T) / (200 * threshold_s)) + row[x] = QUIRC_PIXEL_BLACK; + else + row[x] = QUIRC_PIXEL_WHITE; + } + + row += q->w; + } +} + +static void area_count(void *user_data, int y, int left, int right) +{ + ((struct quirc_region *)user_data)->count += right - left + 1; +} + +static int region_code(struct quirc *q, int x, int y) +{ + int pixel; + struct quirc_region *box; + int region; + + if (x < 0 || y < 0 || x >= q->w || y >= q->h) + return -1; + + pixel = q->image[y * q->w + x]; + + if (pixel >= QUIRC_PIXEL_REGION) + return pixel; + + if (pixel == QUIRC_PIXEL_WHITE) + return -1; + + if (q->num_regions >= QUIRC_MAX_REGIONS) + return -1; + + region = q->num_regions; + box = &q->regions[q->num_regions++]; + + memset(box, 0, sizeof(*box)); + + box->seed.x = x; + box->seed.y = y; + box->capstone = -1; + + flood_fill_seed(q, x, y, pixel, region, area_count, box, 0); + + return region; +} + +struct polygon_score_data { + struct quirc_point ref; + + int scores[4]; + struct quirc_point *corners; +}; + +static void find_one_corner(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int dy = y - psd->ref.y; + int i; + + for (i = 0; i < 2; i++) { + int dx = xs[i] - psd->ref.x; + int d = dx * dx + dy * dy; + + if (d > psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +static void find_other_corners(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int up = xs[i] * psd->ref.x + y * psd->ref.y; + int right = xs[i] * -psd->ref.y + y * psd->ref.x; + int scores[4] = {up, right, -up, -right}; + int j; + + for (j = 0; j < 4; j++) { + if (scores[j] > psd->scores[j]) { + psd->scores[j] = scores[j]; + psd->corners[j].x = xs[i]; + psd->corners[j].y = y; + } + } + } +} + +static void find_region_corners(struct quirc *q, + int rcode, const struct quirc_point *ref, + struct quirc_point *corners) +{ + struct quirc_region *region = &q->regions[rcode]; + struct polygon_score_data psd; + int i; + + memset(&psd, 0, sizeof(psd)); + psd.corners = corners; + + memcpy(&psd.ref, ref, sizeof(psd.ref)); + psd.scores[0] = -1; + flood_fill_seed(q, region->seed.x, region->seed.y, + rcode, QUIRC_PIXEL_BLACK, + find_one_corner, &psd, 0); + + psd.ref.x = psd.corners[0].x - psd.ref.x; + psd.ref.y = psd.corners[0].y - psd.ref.y; + + for (i = 0; i < 4; i++) + memcpy(&psd.corners[i], ®ion->seed, + sizeof(psd.corners[i])); + + i = region->seed.x * psd.ref.x + region->seed.y * psd.ref.y; + psd.scores[0] = i; + psd.scores[2] = -i; + i = region->seed.x * -psd.ref.y + region->seed.y * psd.ref.x; + psd.scores[1] = i; + psd.scores[3] = -i; + + flood_fill_seed(q, region->seed.x, region->seed.y, + QUIRC_PIXEL_BLACK, rcode, + find_other_corners, &psd, 0); +} + +static void record_capstone(struct quirc *q, int ring, int stone) +{ + struct quirc_region *stone_reg = &q->regions[stone]; + struct quirc_region *ring_reg = &q->regions[ring]; + struct quirc_capstone *capstone; + int cs_index; + + if (q->num_capstones >= QUIRC_MAX_CAPSTONES) + return; + + cs_index = q->num_capstones; + capstone = &q->capstones[q->num_capstones++]; + + memset(capstone, 0, sizeof(*capstone)); + + capstone->qr_grid = -1; + capstone->ring = ring; + capstone->stone = stone; + stone_reg->capstone = cs_index; + ring_reg->capstone = cs_index; + + /* Find the corners of the ring */ + find_region_corners(q, ring, &stone_reg->seed, capstone->corners); + + /* Set up the perspective transform and find the center */ + perspective_setup(capstone->c, capstone->corners, 7.0, 7.0); + perspective_map(capstone->c, 3.5, 3.5, &capstone->center); +} + +static void test_capstone(struct quirc *q, int x, int y, int *pb) +{ + int ring_right = region_code(q, x - pb[4], y); + int stone = region_code(q, x - pb[4] - pb[3] - pb[2], y); + int ring_left = region_code(q, x - pb[4] - pb[3] - + pb[2] - pb[1] - pb[0], + y); + struct quirc_region *stone_reg; + struct quirc_region *ring_reg; + int ratio; + + if (ring_left < 0 || ring_right < 0 || stone < 0) + return; + + /* Left and ring of ring should be connected */ + if (ring_left != ring_right) + return; + + /* Ring should be disconnected from stone */ + if (ring_left == stone) + return; + + stone_reg = &q->regions[stone]; + ring_reg = &q->regions[ring_left]; + + /* Already detected */ + if (stone_reg->capstone >= 0 || ring_reg->capstone >= 0) + return; + + /* Ratio should ideally be 37.5 */ + ratio = stone_reg->count * 100 / ring_reg->count; + if (ratio < 10 || ratio > 70) + return; + + record_capstone(q, ring_left, stone); +} + +static void finder_scan(struct quirc *q, int y) +{ + uint8_t *row = q->image + y * q->w; + int x; + int last_color; + int run_length = 0; + int run_count = 0; + int pb[5]; + + memset(pb, 0, sizeof(pb)); + for (x = 0; x < q->w; x++) { + int color = row[x] ? 1 : 0; + + if (x && color != last_color) { + memmove(pb, pb + 1, sizeof(pb[0]) * 4); + pb[4] = run_length; + run_length = 0; + run_count++; + + if (!color && run_count >= 5) { + static int check[5] = {1, 1, 3, 1, 1}; + int avg, err; + int i; + int ok = 1; + + avg = (pb[0] + pb[1] + pb[3] + pb[4]) / 4; + err = avg * 3 / 4; + + for (i = 0; i < 5; i++) + if (pb[i] < check[i] * avg - err || + pb[i] > check[i] * avg + err) + ok = 0; + + if (ok) + test_capstone(q, x, y, pb); + } + } + + run_length++; + last_color = color; + } +} + +static void find_alignment_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_capstone *c0 = &q->capstones[qr->caps[0]]; + struct quirc_capstone *c2 = &q->capstones[qr->caps[2]]; + struct quirc_point a; + struct quirc_point b; + struct quirc_point c; + int size_estimate; + int step_size = 1; + int dir = 0; + double u, v; + + /* Grab our previous estimate of the alignment pattern corner */ + memcpy(&b, &qr->align, sizeof(b)); + + /* Guess another two corners of the alignment pattern so that we + * can estimate its size. + */ + perspective_unmap(c0->c, &b, &u, &v); + perspective_map(c0->c, u, v + 1.0, &a); + perspective_unmap(c2->c, &b, &u, &v); + perspective_map(c2->c, u + 1.0, v, &c); + + size_estimate = abs((a.x - b.x) * -(c.y - b.y) + + (a.y - b.y) * (c.x - b.x)); + + /* Spiral outwards from the estimate point until we find something + * roughly the right size. Don't look too far from the estimate + * point. + */ + while (step_size * step_size < size_estimate * 100) { + static const int dx_map[] = {1, 0, -1, 0}; + static const int dy_map[] = {0, -1, 0, 1}; + int i; + + for (i = 0; i < step_size; i++) { + int code = region_code(q, b.x, b.y); + + if (code >= 0) { + struct quirc_region *reg = &q->regions[code]; + + if (reg->count >= size_estimate / 2 && + reg->count <= size_estimate * 2) { + qr->align_region = code; + return; + } + } + + b.x += dx_map[dir]; + b.y += dy_map[dir]; + } + + dir = (dir + 1) % 4; + if (!(dir & 1)) + step_size++; + } +} + +static void find_leftmost_to_line(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int d = -psd->ref.y * xs[i] + psd->ref.x * y; + + if (d < psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +/* Do a Bresenham scan from one point to another and count the number + * of black/white transitions. + */ +static int timing_scan(const struct quirc *q, + const struct quirc_point *p0, + const struct quirc_point *p1) +{ + int n = p1->x - p0->x; + int d = p1->y - p0->y; + int x = p0->x; + int y = p0->y; + int *dom, *nondom; + int dom_step; + int nondom_step; + int a = 0; + int i; + int run_length = 0; + int count = 0; + + if (p0->x < 0 || p0->y < 0 || p0->x >= q->w || p0->y >= q->h) + return -1; + if (p1->x < 0 || p1->y < 0 || p1->x >= q->w || p1->y >= q->h) + return -1; + + if (abs(n) > abs(d)) { + int swap = n; + + n = d; + d = swap; + + dom = &x; + nondom = &y; + } else { + dom = &y; + nondom = &x; + } + + if (n < 0) { + n = -n; + nondom_step = -1; + } else { + nondom_step = 1; + } + + if (d < 0) { + d = -d; + dom_step = -1; + } else { + dom_step = 1; + } + + x = p0->x; + y = p0->y; + for (i = 0; i <= d; i++) { + int pixel; + + if (y < 0 || y >= q->h || x < 0 || x >= q->w) + break; + + pixel = q->image[y * q->w + x]; + + if (pixel) { + if (run_length >= 2) + count++; + run_length = 0; + } else { + run_length++; + } + + a += n; + *dom += dom_step; + if (a >= d) { + *nondom += nondom_step; + a -= d; + } + } + + return count; +} + +/* Try the measure the timing pattern for a given QR code. This does + * not require the global perspective to have been set up, but it + * does require that the capstone corners have been set to their + * canonical rotation. + * + * For each capstone, we find a point in the middle of the ring band + * which is nearest the centre of the code. Using these points, we do + * a horizontal and a vertical timing scan. + */ +static int measure_timing_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int i; + int scan; + int ver; + int size; + + for (i = 0; i < 3; i++) { + static const double us[] = {6.5, 6.5, 0.5}; + static const double vs[] = {0.5, 6.5, 6.5}; + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + perspective_map(cap->c, us[i], vs[i], &qr->tpep[i]); + } + + qr->hscan = timing_scan(q, &qr->tpep[1], &qr->tpep[2]); + qr->vscan = timing_scan(q, &qr->tpep[1], &qr->tpep[0]); + + scan = qr->hscan; + if (qr->vscan > scan) + scan = qr->vscan; + + /* If neither scan worked, we can't go any further. */ + if (scan < 0) + return -1; + + /* Choose the nearest allowable grid size */ + size = scan * 2 + 13; + ver = (size - 15) / 4; + qr->grid_size = ver * 4 + 17; + + return 0; +} + +/* Read a cell from a grid using the currently set perspective + * transform. Returns +/- 1 for black/white, 0 for cells which are + * out of image bounds. + */ +static int read_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + struct quirc_point p; + + perspective_map(qr->c, x + 0.5, y + 0.5, &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + return 0; + + return q->image[p.y * q->w + p.x] ? 1 : -1; +} + +static int fitness_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + int score = 0; + int u, v; + + for (v = 0; v < 3; v++) + for (u = 0; u < 3; u++) { + static const double offsets[] = {0.3, 0.5, 0.7}; + struct quirc_point p; + + perspective_map(qr->c, x + offsets[u], + y + offsets[v], &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + continue; + + if (q->image[p.y * q->w + p.x]) + score++; + else + score--; + } + + return score; +} + +static int fitness_ring(const struct quirc *q, int index, int cx, int cy, + int radius) +{ + int i; + int score = 0; + + for (i = 0; i < radius * 2; i++) { + score += fitness_cell(q, index, cx - radius + i, cy - radius); + score += fitness_cell(q, index, cx - radius, cy + radius - i); + score += fitness_cell(q, index, cx + radius, cy - radius + i); + score += fitness_cell(q, index, cx + radius - i, cy + radius); + } + + return score; +} + +static int fitness_apat(const struct quirc *q, int index, int cx, int cy) +{ + return fitness_cell(q, index, cx, cy) - + fitness_ring(q, index, cx, cy, 1) + + fitness_ring(q, index, cx, cy, 2); +} + +static int fitness_capstone(const struct quirc *q, int index, int x, int y) +{ + x += 3; + y += 3; + + return fitness_cell(q, index, x, y) + + fitness_ring(q, index, x, y, 1) - + fitness_ring(q, index, x, y, 2) + + fitness_ring(q, index, x, y, 3); +} + +/* Compute a fitness score for the currently configured perspective + * transform, using the features we expect to find by scanning the + * grid. + */ +static int fitness_all(const struct quirc *q, int index) +{ + const struct quirc_grid *qr = &q->grids[index]; + int version = (qr->grid_size - 17) / 4; + const struct quirc_version_info *info = &quirc_version_db[version]; + int score = 0; + int i, j; + int ap_count; + + /* Check the timing pattern */ + for (i = 0; i < qr->grid_size - 14; i++) { + int expect = (i & 1) ? 1 : -1; + + score += fitness_cell(q, index, i + 7, 6) * expect; + score += fitness_cell(q, index, 6, i + 7) * expect; + } + + /* Check capstones */ + score += fitness_capstone(q, index, 0, 0); + score += fitness_capstone(q, index, qr->grid_size - 7, 0); + score += fitness_capstone(q, index, 0, qr->grid_size - 7); + + if (version < 0 || version > QUIRC_MAX_VERSION) + return score; + + /* Check alignment patterns */ + ap_count = 0; + while (info->apat[ap_count]) + ap_count++; + + for (i = 1; i + 1 < ap_count; i++) { + score += fitness_apat(q, index, 6, info->apat[i]); + score += fitness_apat(q, index, info->apat[i], 6); + } + + for (i = 1; i < ap_count; i++) + for (j = 1; j < ap_count; j++) + score += fitness_apat(q, index, + info->apat[i], info->apat[j]); + + return score; +} + +static void jiggle_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int best = fitness_all(q, index); + int pass; + double adjustments[8]; + int i; + + for (i = 0; i < 8; i++) + adjustments[i] = qr->c[i] * 0.02; + + for (pass = 0; pass < 5; pass++) { + for (i = 0; i < 16; i++) { + int j = i >> 1; + int test; + double old = qr->c[j]; + double step = adjustments[j]; + double new; + + if (i & 1) + new = old + step; + else + new = old - step; + + qr->c[j] = new; + test = fitness_all(q, index); + + if (test > best) + best = test; + else + qr->c[j] = old; + } + + for (i = 0; i < 8; i++) + adjustments[i] *= 0.5; + } +} + +/* Once the capstones are in place and an alignment point has been + * chosen, we call this function to set up a grid-reading perspective + * transform. + */ +static void setup_qr_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_point rect[4]; + + /* Set up the perspective map for reading the grid */ + memcpy(&rect[0], &q->capstones[qr->caps[1]].corners[0], + sizeof(rect[0])); + memcpy(&rect[1], &q->capstones[qr->caps[2]].corners[0], + sizeof(rect[0])); + memcpy(&rect[2], &qr->align, sizeof(rect[0])); + memcpy(&rect[3], &q->capstones[qr->caps[0]].corners[0], + sizeof(rect[0])); + perspective_setup(qr->c, rect, qr->grid_size - 7, qr->grid_size - 7); + + jiggle_perspective(q, index); +} + +/* Rotate the capstone with so that corner 0 is the leftmost with respect + * to the given reference line. + */ +static void rotate_capstone(struct quirc_capstone *cap, + const struct quirc_point *h0, + const struct quirc_point *hd) +{ + struct quirc_point copy[4]; + int j; + int best = 0; + int best_score = 0; + + for (j = 0; j < 4; j++) { + struct quirc_point *p = &cap->corners[j]; + int score = (p->x - h0->x) * -hd->y + + (p->y - h0->y) * hd->x; + + if (!j || score < best_score) { + best = j; + best_score = score; + } + } + + /* Rotate the capstone */ + for (j = 0; j < 4; j++) + memcpy(©[j], &cap->corners[(j + best) % 4], + sizeof(copy[j])); + memcpy(cap->corners, copy, sizeof(cap->corners)); + perspective_setup(cap->c, cap->corners, 7.0, 7.0); +} + +static void record_qr_grid(struct quirc *q, int a, int b, int c) +{ + struct quirc_point h0, hd; + int i; + int qr_index; + struct quirc_grid *qr; + + if (q->num_grids >= QUIRC_MAX_GRIDS) + return; + + /* Construct the hypotenuse line from A to C. B should be to + * the left of this line. + */ + memcpy(&h0, &q->capstones[a].center, sizeof(h0)); + hd.x = q->capstones[c].center.x - q->capstones[a].center.x; + hd.y = q->capstones[c].center.y - q->capstones[a].center.y; + + /* Make sure A-B-C is clockwise */ + if ((q->capstones[b].center.x - h0.x) * -hd.y + + (q->capstones[b].center.y - h0.y) * hd.x > 0) { + int swap = a; + + a = c; + c = swap; + hd.x = -hd.x; + hd.y = -hd.y; + } + + /* Record the grid and its components */ + qr_index = q->num_grids; + qr = &q->grids[q->num_grids++]; + + memset(qr, 0, sizeof(*qr)); + qr->caps[0] = a; + qr->caps[1] = b; + qr->caps[2] = c; + qr->align_region = -1; + + /* Rotate each capstone so that corner 0 is top-left with respect + * to the grid. + */ + for (i = 0; i < 3; i++) { + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + rotate_capstone(cap, &h0, &hd); + cap->qr_grid = qr_index; + } + + /* Check the timing pattern. This doesn't require a perspective + * transform. + */ + if (measure_timing_pattern(q, qr_index) < 0) + goto fail; + + /* Make an estimate based for the alignment pattern based on extending + * lines from capstones A and C. + */ + if (!line_intersect(&q->capstones[a].corners[0], + &q->capstones[a].corners[1], + &q->capstones[c].corners[0], + &q->capstones[c].corners[3], + &qr->align)) + goto fail; + + /* On V2+ grids, we should use the alignment pattern. */ + if (qr->grid_size > 21) { + /* Try to find the actual location of the alignment pattern. */ + find_alignment_pattern(q, qr_index); + + /* Find the point of the alignment pattern closest to the + * top-left of the QR grid. + */ + if (qr->align_region >= 0) { + struct polygon_score_data psd; + struct quirc_region *reg = + &q->regions[qr->align_region]; + + /* Start from some point inside the alignment pattern */ + memcpy(&qr->align, ®->seed, sizeof(qr->align)); + + memcpy(&psd.ref, &hd, sizeof(psd.ref)); + psd.corners = &qr->align; + psd.scores[0] = -hd.y * qr->align.x + + hd.x * qr->align.y; + + flood_fill_seed(q, reg->seed.x, reg->seed.y, + qr->align_region, QUIRC_PIXEL_BLACK, + NULL, NULL, 0); + flood_fill_seed(q, reg->seed.x, reg->seed.y, + QUIRC_PIXEL_BLACK, qr->align_region, + find_leftmost_to_line, &psd, 0); + } + } + + setup_qr_perspective(q, qr_index); + return; + +fail: + /* We've been unable to complete setup for this grid. Undo what we've + * recorded and pretend it never happened. + */ + for (i = 0; i < 3; i++) + q->capstones[qr->caps[i]].qr_grid = -1; + q->num_grids--; +} + +struct neighbour { + int index; + double distance; +}; + +struct neighbour_list { + struct neighbour n[QUIRC_MAX_CAPSTONES]; + int count; +}; + +static void test_neighbours(struct quirc *q, int i, + const struct neighbour_list *hlist, + const struct neighbour_list *vlist) +{ + int j, k; + double best_score = 0.0; + int best_h = -1, best_v = -1; + + /* Test each possible grouping */ + for (j = 0; j < hlist->count; j++) + for (k = 0; k < vlist->count; k++) { + const struct neighbour *hn = &hlist->n[j]; + const struct neighbour *vn = &vlist->n[k]; + double score = fabs(1.0 - hn->distance / vn->distance); + + if (score > 2.5) + continue; + + if (best_h < 0 || score < best_score) { + best_h = hn->index; + best_v = vn->index; + best_score = score; + } + } + + if (best_h < 0 || best_v < 0) + return; + + record_qr_grid(q, best_h, i, best_v); +} + +static void test_grouping(struct quirc *q, int i) +{ + struct quirc_capstone *c1 = &q->capstones[i]; + int j; + struct neighbour_list hlist; + struct neighbour_list vlist; + + if (c1->qr_grid >= 0) + return; + + hlist.count = 0; + vlist.count = 0; + + /* Look for potential neighbours by examining the relative gradients + * from this capstone to others. + */ + for (j = 0; j < q->num_capstones; j++) { + struct quirc_capstone *c2 = &q->capstones[j]; + double u, v; + + if (i == j || c2->qr_grid >= 0) + continue; + + perspective_unmap(c1->c, &c2->center, &u, &v); + + u = fabs(u - 3.5); + v = fabs(v - 3.5); + + if (u < 0.2 * v) { + struct neighbour *n = &hlist.n[hlist.count++]; + + n->index = j; + n->distance = v; + } + + if (v < 0.2 * u) { + struct neighbour *n = &vlist.n[vlist.count++]; + + n->index = j; + n->distance = u; + } + } + + if (!(hlist.count && vlist.count)) + return; + + test_neighbours(q, i, &hlist, &vlist); +} + +uint8_t *quirc_begin(struct quirc *q, int *w, int *h) +{ + q->num_regions = QUIRC_PIXEL_REGION; + q->num_capstones = 0; + q->num_grids = 0; + + if (w) + *w = q->w; + if (h) + *h = q->h; + + return q->image; +} + +void quirc_end(struct quirc *q) +{ + int i; + + threshold(q); + + for (i = 0; i < q->h; i++) + finder_scan(q, i); + + for (i = 0; i < q->num_capstones; i++) + test_grouping(q, i); +} + +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code) +{ + const struct quirc_grid *qr = &q->grids[index]; + int y; + int i = 0; + + if (index < 0 || index > q->num_grids) + return; + + memset(code, 0, sizeof(*code)); + + perspective_map(qr->c, 0.0, 0.0, &code->corners[0]); + perspective_map(qr->c, qr->grid_size, 0.0, &code->corners[1]); + perspective_map(qr->c, qr->grid_size, qr->grid_size, + &code->corners[2]); + perspective_map(qr->c, 0.0, qr->grid_size, &code->corners[3]); + + code->size = qr->grid_size; + + for (y = 0; y < qr->grid_size; y++) { + int x; + + for (x = 0; x < qr->grid_size; x++) { + if (read_cell(q, index, x, y) > 0) + code->cell_bitmap[i >> 3] |= (1 << (i & 7)); + + i++; + } + } +} diff --git a/source/quirc/quirc.c b/source/quirc/quirc.c new file mode 100644 index 0000000..3226ef0 --- /dev/null +++ b/source/quirc/quirc.c @@ -0,0 +1,81 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "quirc_internal.h" + +const char *quirc_version(void) +{ + return "1.0"; +} + +struct quirc *quirc_new(void) +{ + struct quirc *q = malloc(sizeof(*q)); + + if (!q) + return NULL; + + memset(q, 0, sizeof(*q)); + return q; +} + +void quirc_destroy(struct quirc *q) +{ + if (q->image) + free(q->image); + + free(q); +} + +int quirc_resize(struct quirc *q, int w, int h) +{ + uint8_t *new_image = realloc(q->image, w * h); + + if (!new_image) + return -1; + + q->image = new_image; + q->w = w; + q->h = h; + + return 0; +} + +int quirc_count(const struct quirc *q) +{ + return q->num_grids; +} + +static const char *const error_table[] = { + [QUIRC_SUCCESS] = "Success", + [QUIRC_ERROR_INVALID_GRID_SIZE] = "Invalid grid size", + [QUIRC_ERROR_INVALID_VERSION] = "Invalid version", + [QUIRC_ERROR_FORMAT_ECC] = "Format data ECC failure", + [QUIRC_ERROR_DATA_ECC] = "ECC failure", + [QUIRC_ERROR_UNKNOWN_DATA_TYPE] = "Unknown data type", + [QUIRC_ERROR_DATA_OVERFLOW] = "Data overflow", + [QUIRC_ERROR_DATA_UNDERFLOW] = "Data underflow" +}; + +const char *quirc_strerror(quirc_decode_error_t err) +{ + if (err >= 0 && err < sizeof(error_table) / sizeof(error_table[0])) + return error_table[err]; + + return "Unknown error"; +} diff --git a/source/quirc/quirc.h b/source/quirc/quirc.h new file mode 100644 index 0000000..c902410 --- /dev/null +++ b/source/quirc/quirc.h @@ -0,0 +1,145 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUIRC_H_ +#define QUIRC_H_ + +#include + +struct quirc; + +/* Obtain the library version string. */ +const char *quirc_version(void); + +/* Construct a new QR-code recognizer. This function will return NULL + * if sufficient memory could not be allocated. + */ +struct quirc *quirc_new(void); + +/* Destroy a QR-code recognizer. */ +void quirc_destroy(struct quirc *q); + +/* Resize the QR-code recognizer. The size of an image must be + * specified before codes can be analyzed. + * + * This function returns 0 on success, or -1 if sufficient memory could + * not be allocated. + */ +int quirc_resize(struct quirc *q, int w, int h); + +/* These functions are used to process images for QR-code recognition. + * quirc_begin() must first be called to obtain access to a buffer into + * which the input image should be placed. Optionally, the current + * width and height may be returned. + * + * After filling the buffer, quirc_end() should be called to process + * the image for QR-code recognition. The locations and content of each + * code may be obtained using accessor functions described below. + */ +uint8_t *quirc_begin(struct quirc *q, int *w, int *h); +void quirc_end(struct quirc *q); + +/* This structure describes a location in the input image buffer. */ +struct quirc_point { + int x; + int y; +}; + +/* This enum describes the various decoder errors which may occur. */ +typedef enum { + QUIRC_SUCCESS = 0, + QUIRC_ERROR_INVALID_GRID_SIZE, + QUIRC_ERROR_INVALID_VERSION, + QUIRC_ERROR_FORMAT_ECC, + QUIRC_ERROR_DATA_ECC, + QUIRC_ERROR_UNKNOWN_DATA_TYPE, + QUIRC_ERROR_DATA_OVERFLOW, + QUIRC_ERROR_DATA_UNDERFLOW +} quirc_decode_error_t; + +/* Return a string error message for an error code. */ +const char *quirc_strerror(quirc_decode_error_t err); + +/* Limits on the maximum size of QR-codes and their content. */ +#define QUIRC_MAX_BITMAP 3917 +#define QUIRC_MAX_PAYLOAD 8896 + +/* QR-code ECC types. */ +#define QUIRC_ECC_LEVEL_M 0 +#define QUIRC_ECC_LEVEL_L 1 +#define QUIRC_ECC_LEVEL_H 2 +#define QUIRC_ECC_LEVEL_Q 3 + +/* QR-code data types. */ +#define QUIRC_DATA_TYPE_NUMERIC 1 +#define QUIRC_DATA_TYPE_ALPHA 2 +#define QUIRC_DATA_TYPE_BYTE 4 +#define QUIRC_DATA_TYPE_KANJI 8 + +/* This structure is used to return information about detected QR codes + * in the input image. + */ +struct quirc_code { + /* The four corners of the QR-code, from top left, clockwise */ + struct quirc_point corners[4]; + + /* The number of cells across in the QR-code. The cell bitmap + * is a bitmask giving the actual values of cells. If the cell + * at (x, y) is black, then the following bit is set: + * + * cell_bitmap[i << 3] & (1 << (i & 7)) + * + * where i = (y * size) + x. + */ + int size; + uint8_t cell_bitmap[QUIRC_MAX_BITMAP]; +}; + +/* This structure holds the decoded QR-code data */ +struct quirc_data { + /* Various parameters of the QR-code. These can mostly be + * ignored if you only care about the data. + */ + int version; + int ecc_level; + int mask; + + /* This field is the highest-valued data type found in the QR + * code. + */ + int data_type; + + /* Data payload. For the Kanji datatype, payload is encoded as + * Shift-JIS. For all other datatypes, payload is ASCII text. + */ + uint8_t payload[QUIRC_MAX_PAYLOAD]; + int payload_len; +}; + +/* Return the number of QR-codes identified in the last processed + * image. + */ +int quirc_count(const struct quirc *q); + +/* Extract the QR-code specified by the given index. */ +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code); + +/* Decode a QR-code, returning the payload data. */ +quirc_decode_error_t quirc_decode(const struct quirc_code *code, + struct quirc_data *data); + +#endif diff --git a/source/quirc/quirc_internal.h b/source/quirc/quirc_internal.h new file mode 100644 index 0000000..b7aba4a --- /dev/null +++ b/source/quirc/quirc_internal.h @@ -0,0 +1,103 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUIRC_INTERNAL_H_ +#define QUIRC_INTERNAL_H_ + +#include "quirc.h" + +#define QUIRC_PIXEL_WHITE 0 +#define QUIRC_PIXEL_BLACK 1 +#define QUIRC_PIXEL_REGION 2 + +#define QUIRC_MAX_REGIONS 254 +#define QUIRC_MAX_CAPSTONES 32 +#define QUIRC_MAX_GRIDS 8 + +#define QUIRC_PERSPECTIVE_PARAMS 8 + +struct quirc_region { + struct quirc_point seed; + int count; + int capstone; +}; + +struct quirc_capstone { + int ring; + int stone; + + struct quirc_point corners[4]; + struct quirc_point center; + double c[QUIRC_PERSPECTIVE_PARAMS]; + + int qr_grid; +}; + +struct quirc_grid { + /* Capstone indices */ + int caps[3]; + + /* Alignment pattern region and corner */ + int align_region; + struct quirc_point align; + + /* Timing pattern endpoints */ + struct quirc_point tpep[3]; + int hscan; + int vscan; + + /* Grid size and perspective transform */ + int grid_size; + double c[QUIRC_PERSPECTIVE_PARAMS]; +}; + +struct quirc { + uint8_t *image; + int w; + int h; + + int num_regions; + struct quirc_region regions[QUIRC_MAX_REGIONS]; + + int num_capstones; + struct quirc_capstone capstones[QUIRC_MAX_CAPSTONES]; + + int num_grids; + struct quirc_grid grids[QUIRC_MAX_GRIDS]; +}; + +/************************************************************************ + * QR-code version information database + */ + +#define QUIRC_MAX_VERSION 40 +#define QUIRC_MAX_ALIGNMENT 7 + +struct quirc_rs_params { + int bs; /* Block size */ + int dw; /* Data words */ + int ce; /* Correctable errors */ +}; + +struct quirc_version_info { + int data_bytes; + int apat[QUIRC_MAX_ALIGNMENT]; + struct quirc_rs_params ecc[4]; +}; + +extern const struct quirc_version_info quirc_version_db[QUIRC_MAX_VERSION + 1]; + +#endif diff --git a/source/quirc/version_db.c b/source/quirc/version_db.c new file mode 100644 index 0000000..693d413 --- /dev/null +++ b/source/quirc/version_db.c @@ -0,0 +1,421 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "quirc_internal.h" + +const struct quirc_version_info quirc_version_db[QUIRC_MAX_VERSION + 1] = { + {0}, + { /* Version 1 */ + .data_bytes = 26, + .apat = {0}, + .ecc = { + {.bs = 26, .dw = 16, .ce = 4}, + {.bs = 26, .dw = 19, .ce = 2}, + {.bs = 26, .dw = 9, .ce = 8}, + {.bs = 26, .dw = 13, .ce = 6} + } + }, + { /* Version 2 */ + .data_bytes = 44, + .apat = {6, 18, 0}, + .ecc = { + {.bs = 44, .dw = 28, .ce = 8}, + {.bs = 44, .dw = 34, .ce = 4}, + {.bs = 44, .dw = 16, .ce = 14}, + {.bs = 44, .dw = 22, .ce = 11} + } + }, + { /* Version 3 */ + .data_bytes = 70, + .apat = {6, 22, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ce = 13}, + {.bs = 70, .dw = 55, .ce = 7}, + {.bs = 35, .dw = 13, .ce = 11}, + {.bs = 35, .dw = 17, .ce = 9} + } + }, + { /* Version 4 */ + .data_bytes = 100, + .apat = {6, 26, 0}, + .ecc = { + {.bs = 50, .dw = 32, .ce = 9}, + {.bs = 100, .dw = 80, .ce = 10}, + {.bs = 25, .dw = 9, .ce = 8}, + {.bs = 50, .dw = 24, .ce = 13} + } + }, + { /* Version 5 */ + .data_bytes = 134, + .apat = {6, 30, 0}, + .ecc = { + {.bs = 67, .dw = 43, .ce = 12}, + {.bs = 134, .dw = 108, .ce = 13}, + {.bs = 33, .dw = 11, .ce = 11}, + {.bs = 33, .dw = 15, .ce = 9} + } + }, + { /* Version 6 */ + .data_bytes = 172, + .apat = {6, 34, 0}, + .ecc = { + {.bs = 43, .dw = 27, .ce = 8}, + {.bs = 86, .dw = 68, .ce = 9}, + {.bs = 43, .dw = 15, .ce = 14}, + {.bs = 43, .dw = 19, .ce = 12} + } + }, + { /* Version 7 */ + .data_bytes = 196, + .apat = {6, 22, 38, 0}, + .ecc = { + {.bs = 49, .dw = 31, .ce = 9}, + {.bs = 98, .dw = 78, .ce = 10}, + {.bs = 39, .dw = 13, .ce = 13}, + {.bs = 32, .dw = 14, .ce = 9} + } + }, + { /* Version 8 */ + .data_bytes = 242, + .apat = {6, 24, 42, 0}, + .ecc = { + {.bs = 60, .dw = 38, .ce = 11}, + {.bs = 121, .dw = 97, .ce = 12}, + {.bs = 40, .dw = 14, .ce = 13}, + {.bs = 40, .dw = 18, .ce = 11} + } + }, + { /* Version 9 */ + .data_bytes = 292, + .apat = {6, 26, 46, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ce = 11}, + {.bs = 146, .dw = 116, .ce = 15}, + {.bs = 36, .dw = 12, .ce = 12}, + {.bs = 36, .dw = 16, .ce = 10} + } + }, + { /* Version 10 */ + .data_bytes = 346, + .apat = {6, 28, 50, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ce = 13}, + {.bs = 86, .dw = 68, .ce = 9}, + {.bs = 43, .dw = 15, .ce = 14}, + {.bs = 43, .dw = 19, .ce = 12} + } + }, + { /* Version 11 */ + .data_bytes = 404, + .apat = {6, 30, 54, 0}, + .ecc = { + {.bs = 80, .dw = 50, .ce = 15}, + {.bs = 101, .dw = 81, .ce = 10}, + {.bs = 36, .dw = 12, .ce = 12}, + {.bs = 50, .dw = 22, .ce = 14} + } + }, + { /* Version 12 */ + .data_bytes = 466, + .apat = {6, 32, 58, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ce = 11}, + {.bs = 116, .dw = 92, .ce = 12}, + {.bs = 42, .dw = 14, .ce = 14}, + {.bs = 46, .dw = 20, .ce = 14} + } + }, + { /* Version 13 */ + .data_bytes = 532, + .apat = {6, 34, 62, 0}, + .ecc = { + {.bs = 59, .dw = 37, .ce = 11}, + {.bs = 133, .dw = 107, .ce = 13}, + {.bs = 33, .dw = 11, .ce = 11}, + {.bs = 44, .dw = 20, .ce = 12} + } + }, + { /* Version 14 */ + .data_bytes = 581, + .apat = {6, 26, 46, 66, 0}, + .ecc = { + {.bs = 65, .dw = 41, .ce = 12}, + {.bs = 109, .dw = 87, .ce = 11}, + {.bs = 36, .dw = 12, .ce = 12}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 15 */ + .data_bytes = 655, + .apat = {6, 26, 48, 70, 0}, + .ecc = { + {.bs = 65, .dw = 41, .ce = 12}, + {.bs = 109, .dw = 87, .ce = 11}, + {.bs = 36, .dw = 12, .ce = 12}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 16 */ + .data_bytes = 733, + .apat = {6, 26, 50, 74, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ce = 14}, + {.bs = 122, .dw = 98, .ce = 12}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 43, .dw = 19, .ce = 12} + } + }, + { /* Version 17 */ + .data_bytes = 815, + .apat = {6, 30, 54, 78, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 135, .dw = 107, .ce = 14}, + {.bs = 42, .dw = 14, .ce = 14}, + {.bs = 50, .dw = 22, .ce = 14} + } + }, + { /* Version 18 */ + .data_bytes = 901, + .apat = {6, 30, 56, 82, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ce = 13}, + {.bs = 150, .dw = 120, .ce = 15}, + {.bs = 42, .dw = 14, .ce = 14}, + {.bs = 50, .dw = 22, .ce = 14} + } + }, + { /* Version 19 */ + .data_bytes = 991, + .apat = {6, 30, 58, 86, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ce = 13}, + {.bs = 141, .dw = 113, .ce = 14}, + {.bs = 39, .dw = 13, .ce = 13}, + {.bs = 47, .dw = 21, .ce = 13} + } + }, + { /* Version 20 */ + .data_bytes = 1085, + .apat = {6, 34, 62, 90, 0}, + .ecc = { + {.bs = 67, .dw = 41, .ce = 13}, + {.bs = 135, .dw = 107, .ce = 14}, + {.bs = 43, .dw = 15, .ce = 14}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 21 */ + .data_bytes = 1156, + .apat = {6, 28, 50, 72, 92, 0}, + .ecc = { + {.bs = 68, .dw = 42, .ce = 13}, + {.bs = 144, .dw = 116, .ce = 14}, + {.bs = 46, .dw = 16, .ce = 15}, + {.bs = 50, .dw = 22, .ce = 14} + } + }, + { /* Version 22 */ + .data_bytes = 1258, + .apat = {6, 26, 50, 74, 98, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 139, .dw = 111, .ce = 14}, + {.bs = 37, .dw = 13, .ce = 12}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 23 */ + .data_bytes = 1364, + .apat = {6, 30, 54, 78, 102, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 151, .dw = 121, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 24 */ + .data_bytes = 1474, + .apat = {6, 28, 54, 80, 106, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ce = 14}, + {.bs = 147, .dw = 117, .ce = 15}, + {.bs = 46, .dw = 16, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 25 */ + .data_bytes = 1588, + .apat = {6, 32, 58, 84, 110, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 132, .dw = 106, .ce = 13}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 26 */ + .data_bytes = 1706, + .apat = {6, 30, 58, 86, 114, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 142, .dw = 114, .ce = 14}, + {.bs = 46, .dw = 16, .ce = 15}, + {.bs = 50, .dw = 22, .ce = 14} + } + }, + { /* Version 27 */ + .data_bytes = 1828, + .apat = {6, 34, 62, 90, 118, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ce = 14}, + {.bs = 152, .dw = 122, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 53, .dw = 23, .ce = 15} + } + }, + { /* Version 28 */ + .data_bytes = 1921, + .apat = {6, 26, 50, 74, 98, 122, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ce = 14}, + {.bs = 147, .dw = 117, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 29 */ + .data_bytes = 2051, + .apat = {6, 30, 54, 78, 102, 126, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ce = 14}, + {.bs = 146, .dw = 116, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 73, .dw = 45, .ce = 14} + } + }, + { /* Version 30 */ + .data_bytes = 2185, + .apat = {6, 26, 52, 78, 104, 130, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 145, .dw = 115, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 31 */ + .data_bytes = 2323, + .apat = {6, 30, 56, 82, 108, 134, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 145, .dw = 115, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 32 */ + .data_bytes = 2465, + .apat = {6, 34, 60, 86, 112, 138, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 145, .dw = 115, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 33 */ + .data_bytes = 2611, + .apat = {6, 30, 58, 96, 114, 142, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 145, .dw = 115, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 34 */ + .data_bytes = 2761, + .apat = {6, 34, 62, 90, 118, 146, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 145, .dw = 115, .ce = 15}, + {.bs = 46, .dw = 16, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 35 */ + .data_bytes = 2876, + .apat = {6, 30, 54, 78, 102, 126, 150}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 151, .dw = 121, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 36 */ + .data_bytes = 3034, + .apat = {6, 24, 50, 76, 102, 128, 154}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 151, .dw = 121, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 37 */ + .data_bytes = 3196, + .apat = {6, 28, 54, 80, 106, 132, 158}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 152, .dw = 122, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 38 */ + .data_bytes = 3362, + .apat = {6, 32, 58, 84, 110, 136, 162}, + .ecc = { + {.bs = 74, .dw = 46, .ce = 14}, + {.bs = 152, .dw = 122, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 39 */ + .data_bytes = 3532, + .apat = {6, 26, 54, 82, 110, 138, 166}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 147, .dw = 117, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + }, + { /* Version 40 */ + .data_bytes = 3706, + .apat = {6, 30, 58, 86, 114, 142, 170}, + .ecc = { + {.bs = 75, .dw = 47, .ce = 14}, + {.bs = 148, .dw = 118, .ce = 15}, + {.bs = 45, .dw = 15, .ce = 15}, + {.bs = 54, .dw = 24, .ce = 15} + } + } +}; diff --git a/source/screen.c b/source/screen.c index 0c343ab..ea7c40c 100644 --- a/source/screen.c +++ b/source/screen.c @@ -202,8 +202,8 @@ void screen_init() { screen_load_texture_file(TEXTURE_BUTTON_LARGE, "button_large.png", true); screen_load_texture_file(TEXTURE_PROGRESS_BAR_BG, "progress_bar_bg.png", true); screen_load_texture_file(TEXTURE_PROGRESS_BAR_CONTENT, "progress_bar_content.png", true); - screen_load_texture_file(TEXTURE_SMDH_INFO_BOX, "smdh_info_box.png", true); - screen_load_texture_file(TEXTURE_SMDH_INFO_BOX_SHADOW, "smdh_info_box_shadow.png", true); + screen_load_texture_file(TEXTURE_META_INFO_BOX, "meta_info_box.png", true); + screen_load_texture_file(TEXTURE_META_INFO_BOX_SHADOW, "meta_info_box_shadow.png", true); screen_load_texture_file(TEXTURE_BATTERY_CHARGING, "battery_charging.png", true); screen_load_texture_file(TEXTURE_BATTERY_0, "battery0.png", true); screen_load_texture_file(TEXTURE_BATTERY_1, "battery1.png", true); @@ -274,7 +274,7 @@ void screen_load_texture(u32 id, void* data, u32 size, u32 width, u32 height, GP u8* pow2Tex = linearAlloc(pow2Width * pow2Height * pixelSize); if(pow2Tex == NULL) { - util_panic("Failed to allocate temporary texture buffer for tiled data."); + util_panic("Failed to allocate temporary texture buffer."); return; } @@ -298,7 +298,7 @@ void screen_load_texture(u32 id, void* data, u32 size, u32 width, u32 height, GP textures[id].pow2Height = pow2Height; if(!C3D_TexInit(&textures[id].tex, (int) pow2Width, (int) pow2Height, format)) { - util_panic("Failed to initialize texture for tiled data."); + util_panic("Failed to initialize texture."); return; } @@ -306,16 +306,11 @@ void screen_load_texture(u32 id, void* data, u32 size, u32 width, u32 height, GP Result flushRes = GSPGPU_FlushDataCache(pow2Tex, pow2Width * pow2Height * 4); if(R_FAILED(flushRes)) { - util_panic("Failed to flush texture buffer for tiled data: 0x%08lX", flushRes); - return; - } - - Result transferRes = GX_DisplayTransfer((u32*) pow2Tex, GX_BUFFER_DIM(pow2Width, pow2Height), (u32*) textures[id].tex.data, GX_BUFFER_DIM(pow2Width, pow2Height), GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT((u32) gpuToGxFormat[format]) | GX_TRANSFER_OUT_FORMAT((u32) gpuToGxFormat[format]) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)); - if(R_FAILED(transferRes)) { - util_panic("Failed to tile texture data for tiled data: 0x%08lX", transferRes); + util_panic("Failed to flush texture buffer: 0x%08lX", flushRes); return; } + C3D_SafeDisplayTransfer((u32*) pow2Tex, GX_BUFFER_DIM(pow2Width, pow2Height), (u32*) textures[id].tex.data, GX_BUFFER_DIM(pow2Width, pow2Height), GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT((u32) gpuToGxFormat[format]) | GX_TRANSFER_OUT_FORMAT((u32) gpuToGxFormat[format]) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)); gspWaitForPPF(); linearFree(pow2Tex); @@ -347,7 +342,7 @@ void screen_load_texture_file(u32 id, const char* path, bool linearFilter) { FILE* fd = util_open_resource(path); if(fd == NULL) { - util_panic("Failed to load PNG file \"%s\": %s", strerror(errno)); + util_panic("Failed to load PNG file \"%s\": %s", path, strerror(errno)); return; } diff --git a/source/screen.h b/source/screen.h index 3446659..e90f597 100644 --- a/source/screen.h +++ b/source/screen.h @@ -24,8 +24,8 @@ #define TEXTURE_BUTTON_LARGE 13 #define TEXTURE_PROGRESS_BAR_BG 14 #define TEXTURE_PROGRESS_BAR_CONTENT 15 -#define TEXTURE_SMDH_INFO_BOX 16 -#define TEXTURE_SMDH_INFO_BOX_SHADOW 17 +#define TEXTURE_META_INFO_BOX 16 +#define TEXTURE_META_INFO_BOX_SHADOW 17 #define TEXTURE_BATTERY_CHARGING 18 #define TEXTURE_BATTERY_0 19 #define TEXTURE_BATTERY_1 20 diff --git a/source/ui/info.c b/source/ui/info.c index 8a4a037..d1a2339 100644 --- a/source/ui/info.c +++ b/source/ui/info.c @@ -1,5 +1,7 @@ -#include <3ds.h> #include +#include + +#include <3ds.h> #include "info.h" #include "../screen.h" @@ -69,6 +71,7 @@ void info_display(const char* name, const char* info, bool bar, void* data, void infoData->bar = bar; infoData->data = data; infoData->progress = 0; + snprintf(infoData->text, PROGRESS_TEXT_MAX, "Please wait..."); infoData->update = update; infoData->drawTop = drawTop; diff --git a/source/ui/mainmenu.c b/source/ui/mainmenu.c index 5e4b662..7922e87 100644 --- a/source/ui/mainmenu.c +++ b/source/ui/mainmenu.c @@ -8,7 +8,7 @@ #include "section/section.h" #include "../screen.h" -#define MAINMENU_ITEM_COUNT 12 +#define MAINMENU_ITEM_COUNT 13 static u32 mainmenu_item_count = MAINMENU_ITEM_COUNT; static list_item mainmenu_items[MAINMENU_ITEM_COUNT] = { @@ -24,6 +24,7 @@ static list_item mainmenu_items[MAINMENU_ITEM_COUNT] = { {"Ext Save Data", COLOR_TEXT, extsavedata_open}, {"System Save Data", COLOR_TEXT, systemsavedata_open}, {"Network Install", COLOR_TEXT, networkinstall_open}, + {"QR Code Install", COLOR_TEXT, qrinstall_open}, }; static void mainmenu_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) { diff --git a/source/ui/section/extsavedata.c b/source/ui/section/extsavedata.c index ffa1272..466ff11 100644 --- a/source/ui/section/extsavedata.c +++ b/source/ui/section/extsavedata.c @@ -96,6 +96,7 @@ static void extsavedata_update(ui_view* view, void* data, list_item** items, u32 ui_pop(); list_destroy(view); + task_clear_ext_save_data(listData->items, &listData->count); free(listData); return; } diff --git a/source/ui/section/files.c b/source/ui/section/files.c index 88a3681..655aa22 100644 --- a/source/ui/section/files.c +++ b/source/ui/section/files.c @@ -260,6 +260,7 @@ static void files_update(ui_view* view, void* data, list_item** items, u32** ite ui_pop(); list_destroy(view); + task_clear_files(listData->items, &listData->count); free(listData); return; } else { diff --git a/source/ui/section/networkinstall.c b/source/ui/section/networkinstall.c index bab73d4..7b91d5c 100644 --- a/source/ui/section/networkinstall.c +++ b/source/ui/section/networkinstall.c @@ -7,12 +7,12 @@ #include <3ds.h> -#include "action/action.h" +#include "task/task.h" +#include "section.h" #include "../error.h" #include "../info.h" #include "../prompt.h" #include "../../screen.h" -#include "section.h" typedef struct { int serverSocket; diff --git a/source/ui/section/pendingtitles.c b/source/ui/section/pendingtitles.c index 33a8598..43b8995 100644 --- a/source/ui/section/pendingtitles.c +++ b/source/ui/section/pendingtitles.c @@ -95,6 +95,7 @@ static void pendingtitles_update(ui_view* view, void* data, list_item** items, u ui_pop(); list_destroy(view); + task_clear_pending_titles(listData->items, &listData->count); free(listData); return; } diff --git a/source/ui/section/qrinstall.c b/source/ui/section/qrinstall.c new file mode 100644 index 0000000..8973d34 --- /dev/null +++ b/source/ui/section/qrinstall.c @@ -0,0 +1,385 @@ +#include +#include +#include + +#include <3ds.h> + +#include "task/task.h" +#include "../error.h" +#include "../info.h" +#include "../prompt.h" +#include "../../screen.h" +#include "../../quirc/quirc_internal.h" + +#define IMAGE_WIDTH 400 +#define IMAGE_HEIGHT 240 + +#define URL_MAX 1024 +#define URLS_MAX 128 + +typedef struct { + struct quirc* qrContext; + char urls[URLS_MAX][URL_MAX]; + + Handle mutex; + u16* buffer; + u32 tex; + Handle camCancelEvent; + + u32 responseCode; + u64 currTitleId; + + data_op_info installInfo; + Handle installCancelEvent; +} qr_install_data; + +static Result qrinstall_is_src_directory(void* data, u32 index, bool* isDirectory) { + *isDirectory = false; + return 0; +} + +static Result qrinstall_make_dst_directory(void* data, u32 index) { + return 0; +} + +static Result qrinstall_open_src(void* data, u32 index, u32* handle) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + httpcContext* context = (httpcContext*) calloc(1, sizeof(httpcContext)); + + Result res = 0; + + if(R_SUCCEEDED(res = httpcOpenContext(context, HTTPC_METHOD_GET, qrInstallData->urls[index], 1))) { + httpcSetSSLOpt(context, SSLCOPT_DisableVerify); + if(R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(context, &qrInstallData->responseCode, 0))) { + if(qrInstallData->responseCode == 200) { + *handle = (u32) context; + } else { + res = R_FBI_HTTP_RESPONSE_CODE; + } + } + + if(R_FAILED(res)) { + httpcCloseContext(context); + } + } + + if(R_FAILED(res)) { + free(context); + } + + return res; +} + +static Result qrinstall_close_src(void* data, u32 index, bool succeeded, u32 handle) { + return httpcCloseContext((httpcContext*) handle); +} + +static Result qrinstall_get_src_size(void* data, u32 handle, u64* size) { + u32 downloadSize = 0; + Result res = httpcGetDownloadSizeState((httpcContext*) handle, NULL, &downloadSize); + + *size = downloadSize; + return res; +} + +static Result qrinstall_read_src(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size) { + Result res = httpcDownloadData((httpcContext*) handle, buffer, size, bytesRead); + return res != HTTPC_RESULTCODE_DOWNLOADPENDING ? res : 0; +} + +static Result qrinstall_open_dst(void* data, u32 index, void* initialReadBlock, u32* handle) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + u8* buffer = (u8*) initialReadBlock; + + u32 headerSize = *(u32*) &buffer[0x00]; + u32 certSize = *(u32*) &buffer[0x08]; + u64 titleId = __builtin_bswap64(*(u64*) &buffer[((headerSize + 0x3F) & ~0x3F) + ((certSize + 0x3F) & ~0x3F) + 0x1DC]); + + FS_MediaType dest = ((titleId >> 32) & 0x8010) != 0 ? MEDIATYPE_NAND : MEDIATYPE_SD; + + u8 n3ds = false; + if(R_SUCCEEDED(APT_CheckNew3DS(&n3ds)) && !n3ds && ((titleId >> 28) & 0xF) == 2) { + return R_FBI_WRONG_SYSTEM; + } + + // Deleting FBI before it reinstalls itself causes issues. + if(((titleId >> 8) & 0xFFFFF) != 0xF8001) { + AM_DeleteTitle(dest, titleId); + AM_DeleteTicket(titleId); + + if(dest == 1) { + AM_QueryAvailableExternalTitleDatabase(NULL); + } + } + + Result res = AM_StartCiaInstall(dest, handle); + if(R_SUCCEEDED(res)) { + qrInstallData->currTitleId = titleId; + } + + return res; +} + +static Result qrinstall_close_dst(void* data, u32 index, bool succeeded, u32 handle) { + if(succeeded) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + Result res = 0; + if(R_SUCCEEDED(res = AM_FinishCiaInstall(handle))) { + if(qrInstallData->currTitleId == 0x0004013800000002 || qrInstallData->currTitleId == 0x0004013820000002) { + res = AM_InstallFirm(qrInstallData->currTitleId); + } + } + + return res; + } else { + return AM_CancelCIAInstall(handle); + } +} + +static Result qrinstall_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) { + return FSFILE_Write(handle, bytesWritten, offset, buffer, size, 0); +} + +static bool qrinstall_error(void* data, u32 index, Result res) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + if(res == R_FBI_CANCELLED) { + prompt_display("Failure", "Install cancelled.", COLOR_TEXT, false, NULL, NULL, NULL, NULL); + return false; + } else { + char* url = qrInstallData->urls[index]; + + volatile bool dismissed = false; + if(res == R_FBI_WRONG_SYSTEM) { + if(strlen(url) > 48) { + error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\n%.45s...\nAttempted to install N3DS title to O3DS.", url); + } else { + error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\n%.48s\nAttempted to install N3DS title to O3DS.", url); + } + } else if(res == R_FBI_HTTP_RESPONSE_CODE) { + error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\nHTTP server returned response code %d", ((qr_install_data*) data)->responseCode); + } else { + if(strlen(url) > 48) { + error_display_res(&dismissed, NULL, NULL, res, "Failed to install CIA file.\n%.45s...", url); + } else { + error_display_res(&dismissed, NULL, NULL, res, "Failed to install CIA file.\n%.48s", url); + } + } + + while(!dismissed) { + svcSleepThread(1000000); + } + } + + return index < qrInstallData->installInfo.total - 1; +} + +static void qrinstall_install_update(ui_view* view, void* data, float* progress, char* text) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + if(qrInstallData->installInfo.finished) { + ui_pop(); + info_destroy(view); + + if(!qrInstallData->installInfo.premature) { + prompt_display("Success", "Install finished.", COLOR_TEXT, false, data, NULL, NULL, NULL); + } + + return; + } + + if(hidKeysDown() & KEY_B) { + svcSignalEvent(qrInstallData->installCancelEvent); + } + + *progress = qrInstallData->installInfo.currTotal != 0 ? (float) ((double) qrInstallData->installInfo.currProcessed / (double) qrInstallData->installInfo.currTotal) : 0; + snprintf(text, PROGRESS_TEXT_MAX, "%lu / %lu\n%.2f MB / %.2f MB", qrInstallData->installInfo.processed, qrInstallData->installInfo.total, qrInstallData->installInfo.currProcessed / 1024.0 / 1024.0, qrInstallData->installInfo.currTotal / 1024.0 / 1024.0); +} + +static void qrinstall_confirm_onresponse(ui_view* view, void* data, bool response) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + if(response) { + qrInstallData->installCancelEvent = task_data_op(&qrInstallData->installInfo); + if(qrInstallData->installCancelEvent != 0) { + info_display("Installing CIA(s)", "Press B to cancel.", true, data, qrinstall_install_update, NULL); + } else { + error_display(NULL, NULL, NULL, "Failed to initiate CIA installation."); + } + } +} + +static void qrinstall_free_data(qr_install_data* data) { + if(data->camCancelEvent != 0) { + svcSignalEvent(data->camCancelEvent); + while(svcWaitSynchronization(data->camCancelEvent, 0) == 0) { + svcSleepThread(1000000); + } + + data->camCancelEvent = 0; + } + + if(data->qrContext != NULL) { + quirc_destroy(data->qrContext); + data->qrContext = NULL; + } + + if(data->buffer != NULL) { + free(data->buffer); + data->buffer = NULL; + } + + if(data->tex != 0) { + screen_unload_texture(data->tex); + data->tex = 0; + } + + free(data); +} + +static void qrinstall_wait_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + if(qrInstallData->tex != 0) { + screen_draw_texture(qrInstallData->tex, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); + } +} + +static void qrinstall_wait_update(ui_view* view, void* data, float* progress, char* text) { + qr_install_data* qrInstallData = (qr_install_data*) data; + + if(hidKeysDown() & KEY_B) { + ui_pop(); + info_destroy(view); + + qrinstall_free_data(qrInstallData); + + return; + } + + if(qrInstallData->tex != 0) { + screen_unload_texture(qrInstallData->tex); + qrInstallData->tex = 0; + } + + int w = 0; + int h = 0; + uint8_t* qrBuf = quirc_begin(qrInstallData->qrContext, &w, &h); + + svcWaitSynchronization(qrInstallData->mutex, U64_MAX); + + qrInstallData->tex = screen_load_texture_auto(qrInstallData->buffer, IMAGE_WIDTH * IMAGE_HEIGHT * sizeof(u16), IMAGE_WIDTH, IMAGE_HEIGHT, GPU_RGB565, false); + + for(int x = 0; x < w; x++) { + for(int y = 0; y < h; y++) { + u16 px = qrInstallData->buffer[y * IMAGE_WIDTH + x]; + qrBuf[y * w + x] = (u8) (((((px >> 11) & 0x1F) << 3) + (((px >> 5) & 0x3F) << 2) + ((px & 0x1F) << 3)) / 3); + } + } + + svcReleaseMutex(qrInstallData->mutex); + + quirc_end(qrInstallData->qrContext); + + int qrCount = quirc_count(qrInstallData->qrContext); + for(int i = 0; i < qrCount; i++) { + struct quirc_code qrCode; + quirc_extract(qrInstallData->qrContext, i, &qrCode); + + struct quirc_data qrData; + quirc_decode_error_t err = quirc_decode(&qrCode, &qrData); + + if(err == 0) { + qrInstallData->installInfo.total = 0; + + char* currStart = (char*) qrData.payload; + char* currEnd = NULL; + while((currEnd = strchr(currStart, '\n')) != NULL) { + u32 len = currEnd - currStart; + if(len > URL_MAX) { + len = URL_MAX; + } + + strncpy(qrInstallData->urls[qrInstallData->installInfo.total++], currStart, len); + + currStart = currEnd + 1; + } + + if(*currStart != '\0') { + strncpy(qrInstallData->urls[qrInstallData->installInfo.total++], currStart, URL_MAX); + } + + prompt_display("Confirmation", "Install from the scanned URL(s)?", COLOR_TEXT, true, data, NULL, NULL, qrinstall_confirm_onresponse); + } + } + + snprintf(text, PROGRESS_TEXT_MAX, "Waiting for QR code..."); +} + +void qrinstall_open() { + qr_install_data* data = (qr_install_data*) calloc(1, sizeof(qr_install_data)); + + data->qrContext = quirc_new(); + if(data->qrContext == NULL) { + error_display(NULL, NULL, NULL, "Failed to create QR context."); + + qrinstall_free_data(data); + return; + } + + if(quirc_resize(data->qrContext, IMAGE_WIDTH, IMAGE_HEIGHT) != 0) { + error_display(NULL, NULL, NULL, "Failed to resize QR context."); + + qrinstall_free_data(data); + return; + } + + data->buffer = (u16*) calloc(1, IMAGE_WIDTH * IMAGE_HEIGHT * sizeof(u16)); + if(data->buffer == NULL) { + error_display(NULL, NULL, NULL, "Failed to create image buffer."); + + qrinstall_free_data(data); + return; + } + + data->camCancelEvent = task_capture_cam(&data->mutex, data->buffer, IMAGE_WIDTH, IMAGE_HEIGHT); + if(data->camCancelEvent == 0) { + error_display(NULL, NULL, NULL, "Failed to start camera capture."); + + qrinstall_free_data(data); + return; + } + + data->tex = 0; + + data->currTitleId = 0; + + data->installInfo.data = data; + + data->installInfo.op = DATAOP_COPY; + + data->installInfo.copyEmpty = false; + + data->installInfo.total = 0; + + data->installInfo.isSrcDirectory = qrinstall_is_src_directory; + data->installInfo.makeDstDirectory = qrinstall_make_dst_directory; + + data->installInfo.openSrc = qrinstall_open_src; + data->installInfo.closeSrc = qrinstall_close_src; + data->installInfo.getSrcSize = qrinstall_get_src_size; + data->installInfo.readSrc = qrinstall_read_src; + + data->installInfo.openDst = qrinstall_open_dst; + data->installInfo.closeDst = qrinstall_close_dst; + data->installInfo.writeDst = qrinstall_write_dst; + + data->installInfo.error = qrinstall_error; + + data->installCancelEvent = 0; + + info_display("QR Code Install", "B: Return", false, data, qrinstall_wait_update, qrinstall_wait_draw_top); +} diff --git a/source/ui/section/section.h b/source/ui/section/section.h index bb9ae86..f374225 100644 --- a/source/ui/section/section.h +++ b/source/ui/section/section.h @@ -12,6 +12,7 @@ void files_open_twl_photo(); void files_open_twl_sound(); void networkinstall_open(); void pendingtitles_open(); +void qrinstall_open(); void systemsavedata_open(); void tickets_open(); void titles_open(); \ No newline at end of file diff --git a/source/ui/section/systemsavedata.c b/source/ui/section/systemsavedata.c index a2cd14b..927f6bd 100644 --- a/source/ui/section/systemsavedata.c +++ b/source/ui/section/systemsavedata.c @@ -95,6 +95,7 @@ static void systemsavedata_update(ui_view* view, void* data, list_item** items, ui_pop(); list_destroy(view); + task_clear_system_save_data(listData->items, &listData->count); free(listData); return; } diff --git a/source/ui/section/task/capturecam.c b/source/ui/section/task/capturecam.c new file mode 100644 index 0000000..4f83ca5 --- /dev/null +++ b/source/ui/section/task/capturecam.c @@ -0,0 +1,163 @@ +#include +#include + +#include <3ds.h> + +#include "../../list.h" +#include "../../error.h" +#include "task.h" + +#define EVENT_CANCEL 0 +#define EVENT_RECV 1 +#define EVENT_BUFFER_ERROR 2 + +#define EVENT_COUNT 3 + +typedef struct { + u16* buffer; + s16 width; + s16 height; + + Handle mutex; + + Handle cancelEvent; +} capture_cam_data; + +static void task_capture_cam_thread(void* arg) { + capture_cam_data* data = (capture_cam_data*) arg; + + Handle events[EVENT_COUNT] = {0}; + events[EVENT_CANCEL] = data->cancelEvent; + + Result res = 0; + + u32 bufferSize = data->width * data->height * sizeof(u16); + u16* buffer = (u16*) calloc(1, bufferSize); + if(buffer != NULL) { + if(R_SUCCEEDED(res = camInit())) { + if(R_SUCCEEDED(res = CAMU_SetSize(SELECT_OUT1, SIZE_VGA, CONTEXT_A)) + && R_SUCCEEDED(res = CAMU_SetOutputFormat(SELECT_OUT1, OUTPUT_RGB_565, CONTEXT_A)) + && R_SUCCEEDED(res = CAMU_SetFrameRate(SELECT_OUT1, FRAME_RATE_30)) + && R_SUCCEEDED(res = CAMU_SetNoiseFilter(SELECT_OUT1, true)) + && R_SUCCEEDED(res = CAMU_SetAutoExposure(SELECT_OUT1, true)) + && R_SUCCEEDED(res = CAMU_SetAutoWhiteBalance(SELECT_OUT1, true)) + && R_SUCCEEDED(res = CAMU_Activate(SELECT_OUT1))) { + u32 transferUnit = 0; + + if(R_SUCCEEDED(res = CAMU_GetBufferErrorInterruptEvent(&events[EVENT_BUFFER_ERROR], PORT_CAM1)) + && R_SUCCEEDED(res = CAMU_SetTrimming(PORT_CAM1, true)) + && R_SUCCEEDED(res = CAMU_SetTrimmingParamsCenter(PORT_CAM1, data->width, data->height, 640, 480)) + && R_SUCCEEDED(res = CAMU_GetMaxBytes(&transferUnit, data->width, data->height)) + && R_SUCCEEDED(res = CAMU_SetTransferBytes(PORT_CAM1, transferUnit, data->width, data->height)) + && R_SUCCEEDED(res = CAMU_ClearBuffer(PORT_CAM1)) + && R_SUCCEEDED(res = CAMU_SetReceiving(&events[EVENT_RECV], buffer, PORT_CAM1, bufferSize, (s16) transferUnit)) + && R_SUCCEEDED(res = CAMU_StartCapture(PORT_CAM1))) { + bool cancelRequested = false; + while(!task_is_quit_all() && !cancelRequested && R_SUCCEEDED(res)) { + s32 index = 0; + if(R_SUCCEEDED(res = svcWaitSynchronizationN(&index, events, EVENT_COUNT, false, U64_MAX))) { + switch(index) { + case EVENT_CANCEL: + cancelRequested = true; + break; + case EVENT_RECV: + svcCloseHandle(events[EVENT_RECV]); + events[EVENT_RECV] = 0; + + svcWaitSynchronization(data->mutex, U64_MAX); + memcpy(data->buffer, buffer, bufferSize); + svcReleaseMutex(data->mutex); + + res = CAMU_SetReceiving(&events[EVENT_RECV], buffer, PORT_CAM1, bufferSize, (s16) transferUnit); + break; + case EVENT_BUFFER_ERROR: + svcCloseHandle(events[EVENT_RECV]); + events[EVENT_RECV] = 0; + + if(R_SUCCEEDED(res = CAMU_ClearBuffer(PORT_CAM1)) + && R_SUCCEEDED(res = CAMU_SetReceiving(&events[EVENT_RECV], buffer, PORT_CAM1, bufferSize, (s16) transferUnit))) { + res = CAMU_StartCapture(PORT_CAM1); + } + + break; + default: + break; + } + } + } + + CAMU_StopCapture(PORT_CAM1); + + bool busy = false; + while(R_SUCCEEDED(CAMU_IsBusy(&busy, PORT_CAM1)) && busy) { + svcSleepThread(1000000); + } + + CAMU_ClearBuffer(PORT_CAM1); + } + + CAMU_Activate(SELECT_NONE); + } + + camExit(); + } + + free(buffer); + } else { + res = R_FBI_OUT_OF_MEMORY; + } + + if(R_FAILED(res)) { + error_display_res(NULL, NULL, NULL, res, "Error capturing camera image."); + } + + for(int i = 0; i < EVENT_COUNT; i++) { + if(events[i] != 0) { + svcCloseHandle(events[i]); + events[i] = 0; + } + } + + svcCloseHandle(data->mutex); + free(data); +} + +Handle task_capture_cam(Handle* mutex, u16* buffer, s16 width, s16 height) { + if(buffer == NULL || width <= 0 || width > 640 || height <= 0 || height > 480 || mutex == 0) { + return 0; + } + + capture_cam_data* data = (capture_cam_data*) calloc(1, sizeof(capture_cam_data)); + data->buffer = buffer; + data->width = width; + data->height = height; + + Result eventRes = svcCreateEvent(&data->cancelEvent, 1); + if(R_FAILED(eventRes)) { + error_display_res(NULL, NULL, NULL, eventRes, "Failed to create camera capture cancel event."); + + free(data); + return 0; + } + + Result mutexRes = svcCreateMutex(&data->mutex, false); + if(R_FAILED(mutexRes)) { + error_display_res(NULL, NULL, NULL, mutexRes, "Failed to create camera capture buffer mutex."); + + svcCloseHandle(data->cancelEvent); + free(data); + return 0; + } + + if(threadCreate(task_capture_cam_thread, data, 0x4000, 0x19, 1, true) == NULL) { + error_display(NULL, NULL, NULL, "Failed to create camera capture thread."); + + svcCloseHandle(data->mutex); + svcCloseHandle(data->cancelEvent); + free(data); + return 0; + } + + *mutex = data->mutex; + return data->cancelEvent; +} \ No newline at end of file diff --git a/source/ui/section/task/listextsavedata.c b/source/ui/section/task/listextsavedata.c index 8883527..23c7796 100644 --- a/source/ui/section/task/listextsavedata.c +++ b/source/ui/section/task/listextsavedata.c @@ -30,7 +30,7 @@ static Result task_populate_ext_save_data_from(populate_ext_save_data_data* data qsort(extSaveDataIds, extSaveDataCount, sizeof(u64), util_compare_u64); SMDH smdh; - for(u32 i = 0; i < extSaveDataCount && i < data->max; i++) { + for(u32 i = 0; i < extSaveDataCount && i < data->max && R_SUCCEEDED(res); i++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { break; } @@ -40,6 +40,7 @@ static Result task_populate_ext_save_data_from(populate_ext_save_data_data* data extSaveDataInfo->mediaType = mediaType; extSaveDataInfo->extSaveDataId = extSaveDataIds[i]; extSaveDataInfo->shared = mediaType == MEDIATYPE_NAND; + extSaveDataInfo->hasMeta = false; list_item* item = &data->items[*data->count]; @@ -51,13 +52,11 @@ static Result task_populate_ext_save_data_from(populate_ext_save_data_data* data utf16_to_utf8((uint8_t*) item->name, smdh.titles[systemLanguage].shortDescription, NAME_MAX); - extSaveDataInfo->hasSmdh = true; - utf16_to_utf8((uint8_t*) extSaveDataInfo->smdhInfo.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(extSaveDataInfo->smdhInfo.shortDescription)); - utf16_to_utf8((uint8_t*) extSaveDataInfo->smdhInfo.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(extSaveDataInfo->smdhInfo.longDescription)); - utf16_to_utf8((uint8_t*) extSaveDataInfo->smdhInfo.publisher, smdh.titles[systemLanguage].publisher, sizeof(extSaveDataInfo->smdhInfo.publisher)); - extSaveDataInfo->smdhInfo.texture = screen_load_texture_tiled_auto(smdh.largeIcon, sizeof(smdh.largeIcon), 48, 48, GPU_RGB565, false); - } else { - extSaveDataInfo->hasSmdh = false; + extSaveDataInfo->hasMeta = true; + utf16_to_utf8((uint8_t*) extSaveDataInfo->meta.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(extSaveDataInfo->meta.shortDescription)); + utf16_to_utf8((uint8_t*) extSaveDataInfo->meta.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(extSaveDataInfo->meta.longDescription)); + utf16_to_utf8((uint8_t*) extSaveDataInfo->meta.publisher, smdh.titles[systemLanguage].publisher, sizeof(extSaveDataInfo->meta.publisher)); + extSaveDataInfo->meta.texture = screen_load_texture_tiled_auto(smdh.largeIcon, sizeof(smdh.largeIcon), 48, 48, GPU_RGB565, false); } bool empty = strlen(item->name) == 0; @@ -88,6 +87,8 @@ static Result task_populate_ext_save_data_from(populate_ext_save_data_data* data item->data = extSaveDataInfo; (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; } } } @@ -112,7 +113,7 @@ static void task_populate_ext_save_data_thread(void* arg) { free(data); } -static void task_clear_ext_save_data(list_item* items, u32* count) { +void task_clear_ext_save_data(list_item* items, u32* count) { if(items == NULL || count == NULL || *count == 0) { return; } @@ -123,8 +124,8 @@ static void task_clear_ext_save_data(list_item* items, u32* count) { for(u32 i = 0; i < prevCount; i++) { if(items[i].data != NULL) { ext_save_data_info* extSaveDataInfo = (ext_save_data_info*) items[i].data; - if(extSaveDataInfo->hasSmdh) { - screen_unload_texture(extSaveDataInfo->smdhInfo.texture); + if(extSaveDataInfo->hasMeta) { + screen_unload_texture(extSaveDataInfo->meta.texture); } free(items[i].data); diff --git a/source/ui/section/task/listfiles.c b/source/ui/section/task/listfiles.c index d188eb2..73d9d3a 100644 --- a/source/ui/section/task/listfiles.c +++ b/source/ui/section/task/listfiles.c @@ -29,9 +29,8 @@ static void task_populate_files_thread(void* arg) { Result res = 0; - if(data->max > *data->count) { - FS_Path* fsPath = util_make_path_utf8(data->dir->path); - + FS_Path* fsPath = util_make_path_utf8(data->dir->path); + if(fsPath != NULL) { Handle dirHandle = 0; if(R_SUCCEEDED(res = FSUSER_OpenDirectory(&dirHandle, *data->dir->archive, *fsPath))) { u32 entryCount = 0; @@ -41,7 +40,7 @@ static void task_populate_files_thread(void* arg) { qsort(entries, entryCount, sizeof(FS_DirectoryEntry), util_compare_directory_entries); SMDH smdh; - for(u32 i = 0; i < entryCount && i < data->max; i++) { + for(u32 i = 0; i < entryCount && i < data->max && R_SUCCEEDED(res); i++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { break; } @@ -78,68 +77,71 @@ static void task_populate_files_thread(void* arg) { fileInfo->isCia = false; FS_Path* fileFsPath = util_make_path_utf8(fileInfo->path); + if(fileFsPath != NULL) { + Handle fileHandle; + if(R_SUCCEEDED(FSUSER_OpenFile(&fileHandle, *data->dir->archive, *fileFsPath, FS_OPEN_READ, 0))) { + FSFILE_GetSize(fileHandle, &fileInfo->size); - Handle fileHandle; - if(R_SUCCEEDED(FSUSER_OpenFile(&fileHandle, *data->dir->archive, *fileFsPath, FS_OPEN_READ, 0))) { - FSFILE_GetSize(fileHandle, &fileInfo->size); + size_t len = strlen(fileInfo->path); + if(len > 4) { + if(strcasecmp(&fileInfo->path[len - 4], ".cia") == 0) { + AM_TitleEntry titleEntry; + if(R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_SD, &titleEntry, fileHandle))) { + data->dir->containsCias = true; - size_t len = strlen(fileInfo->path); - if(len > 4) { - if(strcasecmp(&fileInfo->path[len - 4], ".cia") == 0) { - AM_TitleEntry titleEntry; - if(R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_SD, &titleEntry, fileHandle))) { - data->dir->containsCias = true; - - fileInfo->isCia = true; - fileInfo->ciaInfo.titleId = titleEntry.titleID; - fileInfo->ciaInfo.version = titleEntry.version; - fileInfo->ciaInfo.installedSize = titleEntry.size; - fileInfo->ciaInfo.hasSmdh = false; - - if(((titleEntry.titleID >> 32) & 0x8010) != 0 && R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_NAND, &titleEntry, fileHandle))) { + fileInfo->isCia = true; + fileInfo->ciaInfo.titleId = titleEntry.titleID; + fileInfo->ciaInfo.version = titleEntry.version; fileInfo->ciaInfo.installedSize = titleEntry.size; + fileInfo->ciaInfo.hasMeta = false; + + if(((titleEntry.titleID >> 32) & 0x8010) != 0 && R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_NAND, &titleEntry, fileHandle))) { + fileInfo->ciaInfo.installedSize = titleEntry.size; + } + + if(R_SUCCEEDED(AM_GetCiaIcon(&smdh, fileHandle))) { + u8 systemLanguage = CFG_LANGUAGE_EN; + CFGU_GetSystemLanguage(&systemLanguage); + + fileInfo->ciaInfo.hasMeta = true; + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.meta.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(fileInfo->ciaInfo.meta.shortDescription)); + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.meta.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(fileInfo->ciaInfo.meta.longDescription)); + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.meta.publisher, smdh.titles[systemLanguage].publisher, sizeof(fileInfo->ciaInfo.meta.publisher)); + fileInfo->ciaInfo.meta.texture = screen_load_texture_tiled_auto(smdh.largeIcon, sizeof(smdh.largeIcon), 48, 48, GPU_RGB565, false); + } } + } else if(strcasecmp(&fileInfo->path[len - 4], ".tik") == 0) { + u32 bytesRead = 0; - if(R_SUCCEEDED(AM_GetCiaIcon(&smdh, fileHandle))) { - u8 systemLanguage = CFG_LANGUAGE_EN; - CFGU_GetSystemLanguage(&systemLanguage); + u8 sigType = 0; + if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 3, &sigType, sizeof(sigType))) && bytesRead == sizeof(sigType) && sigType <= 5) { + static u32 dataOffsets[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80}; + static u32 titleIdOffset = 0x9C; - fileInfo->ciaInfo.hasSmdh = true; - utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(fileInfo->ciaInfo.smdhInfo.shortDescription)); - utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(fileInfo->ciaInfo.smdhInfo.longDescription)); - utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.publisher, smdh.titles[systemLanguage].publisher, sizeof(fileInfo->ciaInfo.smdhInfo.publisher)); - fileInfo->ciaInfo.smdhInfo.texture = screen_load_texture_tiled_auto(smdh.largeIcon, sizeof(smdh.largeIcon), 48, 48, GPU_RGB565, false); - } - } - } else if(strcasecmp(&fileInfo->path[len - 4], ".tik") == 0) { - u32 bytesRead = 0; + u64 titleId = 0; + if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, dataOffsets[sigType] + titleIdOffset, &titleId, sizeof(titleId))) && bytesRead == sizeof(titleId)) { + data->dir->containsTickets = true; - u8 sigType = 0; - if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 3, &sigType, sizeof(sigType))) && bytesRead == sizeof(sigType) && sigType <= 5) { - static u32 dataOffsets[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80}; - static u32 titleIdOffset = 0x9C; - - u64 titleId = 0; - if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, dataOffsets[sigType] + titleIdOffset, &titleId, sizeof(titleId))) && bytesRead == sizeof(titleId)) { - data->dir->containsTickets = true; - - fileInfo->isTicket = true; - fileInfo->ticketInfo.ticketId = __builtin_bswap64(titleId); + fileInfo->isTicket = true; + fileInfo->ticketInfo.ticketId = __builtin_bswap64(titleId); + } } } } + + FSFILE_Close(fileHandle); } - FSFILE_Close(fileHandle); + util_free_path_utf8(fileFsPath); } - - util_free_path_utf8(fileFsPath); } strncpy(item->name, entryName, NAME_MAX); item->data = fileInfo; (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; } } } @@ -153,6 +155,8 @@ static void task_populate_files_thread(void* arg) { } util_free_path_utf8(fsPath); + } else { + res = R_FBI_OUT_OF_MEMORY; } if(R_FAILED(res)) { @@ -163,7 +167,7 @@ static void task_populate_files_thread(void* arg) { free(data); } -static void task_clear_files(list_item* items, u32* count) { +void task_clear_files(list_item* items, u32* count) { if(items == NULL || count == NULL) { return; } @@ -174,8 +178,8 @@ static void task_clear_files(list_item* items, u32* count) { for(u32 i = 0; i < prevCount; i++) { if(items[i].data != NULL) { file_info* fileInfo = (file_info*) items[i].data; - if(fileInfo->isCia && fileInfo->ciaInfo.hasSmdh) { - screen_unload_texture(fileInfo->ciaInfo.smdhInfo.texture); + if(fileInfo->isCia && fileInfo->ciaInfo.hasMeta) { + screen_unload_texture(fileInfo->ciaInfo.meta.texture); } free(items[i].data); diff --git a/source/ui/section/task/listpendingtitles.c b/source/ui/section/task/listpendingtitles.c index 418a895..aa29974 100644 --- a/source/ui/section/task/listpendingtitles.c +++ b/source/ui/section/task/listpendingtitles.c @@ -33,7 +33,7 @@ static Result task_populate_pending_titles_from(populate_pending_titles_data* da AM_PendingTitleEntry* pendingTitleInfos = (AM_PendingTitleEntry*) calloc(pendingTitleCount, sizeof(AM_PendingTitleEntry)); if(pendingTitleInfos != NULL) { if(R_SUCCEEDED(res = AM_GetPendingTitleInfo(pendingTitleCount, mediaType, pendingTitleIds, pendingTitleInfos))) { - for(u32 i = 0; i < pendingTitleCount && i < data->max; i++) { + for(u32 i = 0; i < pendingTitleCount && i < data->max && R_SUCCEEDED(res); i++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { break; } @@ -55,6 +55,8 @@ static Result task_populate_pending_titles_from(populate_pending_titles_data* da item->data = pendingTitleInfo; (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; } } } @@ -86,7 +88,7 @@ static void task_populate_pending_titles_thread(void* arg) { free(data); } -static void task_clear_pending_titles(list_item* items, u32* count) { +void task_clear_pending_titles(list_item* items, u32* count) { if(items == NULL || count == NULL) { return; } diff --git a/source/ui/section/task/listsystemsavedata.c b/source/ui/section/task/listsystemsavedata.c index 6341f31..52022ad 100644 --- a/source/ui/section/task/listsystemsavedata.c +++ b/source/ui/section/task/listsystemsavedata.c @@ -31,7 +31,7 @@ static void task_populate_system_save_data_thread(void* arg) { if(R_SUCCEEDED(res = FSUSER_EnumerateSystemSaveData(&systemSaveDataCount, data->max * sizeof(u32), systemSaveDataIds))) { qsort(systemSaveDataIds, systemSaveDataCount, sizeof(u32), util_compare_u32); - for(u32 i = 0; i < systemSaveDataCount && i < data->max; i++) { + for(u32 i = 0; i < systemSaveDataCount && i < data->max && R_SUCCEEDED(res); i++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { break; } @@ -46,6 +46,8 @@ static void task_populate_system_save_data_thread(void* arg) { item->data = systemSaveDataInfo; (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; } } } @@ -63,7 +65,7 @@ static void task_populate_system_save_data_thread(void* arg) { free(data); } -static void task_clear_system_save_data(list_item* items, u32* count) { +void task_clear_system_save_data(list_item* items, u32* count) { if(items == NULL || count == NULL) { return; } diff --git a/source/ui/section/task/listtickets.c b/source/ui/section/task/listtickets.c index 6f4540e..b1a9583 100644 --- a/source/ui/section/task/listtickets.c +++ b/source/ui/section/task/listtickets.c @@ -32,7 +32,7 @@ static void task_populate_tickets_thread(void* arg) { if(R_SUCCEEDED(res = AM_GetTicketList(&ticketCount, ticketCount, 0, ticketIds))) { qsort(ticketIds, ticketCount, sizeof(u64), util_compare_u64); - for(u32 i = 0; i < ticketCount && i < data->max; i++) { + for(u32 i = 0; i < ticketCount && i < data->max && R_SUCCEEDED(res); i++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { break; } @@ -47,6 +47,8 @@ static void task_populate_tickets_thread(void* arg) { item->data = ticketInfo; (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; } } } @@ -65,7 +67,7 @@ static void task_populate_tickets_thread(void* arg) { free(data); } -static void task_clear_tickets(list_item* items, u32* count) { +void task_clear_tickets(list_item* items, u32* count) { if(items == NULL || count == NULL) { return; } diff --git a/source/ui/section/task/listtitles.c b/source/ui/section/task/listtitles.c index 521e1df..873a64a 100644 --- a/source/ui/section/task/listtitles.c +++ b/source/ui/section/task/listtitles.c @@ -5,6 +5,7 @@ #include #include <3ds.h> +#include <3ds/services/am.h> #include "../../list.h" #include "../../error.h" @@ -20,6 +21,229 @@ typedef struct { Handle cancelEvent; } populate_titles_data; +static Result task_populate_titles_add_ctr(populate_titles_data* data, FS_MediaType mediaType, u64 titleId) { + Result res = 0; + + AM_TitleEntry entry; + if(R_SUCCEEDED(res = AM_GetTitleInfo(mediaType, 1, &titleId, &entry))) { + title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info)); + if(titleInfo != NULL) { + titleInfo->mediaType = mediaType; + titleInfo->titleId = titleId; + AM_GetTitleProductCode(mediaType, titleId, titleInfo->productCode); + titleInfo->version = entry.version; + titleInfo->installedSize = entry.size; + titleInfo->twl = false; + titleInfo->hasMeta = false; + + list_item* item = &data->items[*data->count]; + + static const u32 filePathData[] = {0x00000000, 0x00000000, 0x00000002, 0x6E6F6369, 0x00000000}; + static const FS_Path filePath = (FS_Path) {PATH_BINARY, 0x14, (u8*) filePathData}; + u32 archivePath[] = {(u32) (titleId & 0xFFFFFFFF), (u32) ((titleId >> 32) & 0xFFFFFFFF), mediaType, 0x00000000}; + FS_Archive archive = {ARCHIVE_SAVEDATA_AND_CONTENT, (FS_Path) {PATH_BINARY, 0x10, (u8*) archivePath}}; + + Handle fileHandle; + if(R_SUCCEEDED(FSUSER_OpenFileDirectly(&fileHandle, archive, filePath, FS_OPEN_READ, 0))) { + SMDH* smdh = (SMDH*) calloc(1, sizeof(SMDH)); + if(smdh != NULL) { + u32 bytesRead; + if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 0, smdh, sizeof(SMDH))) && bytesRead == sizeof(SMDH)) { + if(smdh->magic[0] == 'S' && smdh->magic[1] == 'M' && smdh->magic[2] == 'D' && smdh->magic[3] == 'H') { + titleInfo->hasMeta = true; + + u8 systemLanguage = CFG_LANGUAGE_EN; + CFGU_GetSystemLanguage(&systemLanguage); + + utf16_to_utf8((uint8_t*) item->name, smdh->titles[systemLanguage].shortDescription, NAME_MAX); + + utf16_to_utf8((uint8_t*) titleInfo->meta.shortDescription, smdh->titles[systemLanguage].shortDescription, sizeof(titleInfo->meta.shortDescription)); + utf16_to_utf8((uint8_t*) titleInfo->meta.longDescription, smdh->titles[systemLanguage].longDescription, sizeof(titleInfo->meta.longDescription)); + utf16_to_utf8((uint8_t*) titleInfo->meta.publisher, smdh->titles[systemLanguage].publisher, sizeof(titleInfo->meta.publisher)); + titleInfo->meta.texture = screen_load_texture_tiled_auto(smdh->largeIcon, sizeof(smdh->largeIcon), 48, 48, GPU_RGB565, false); + } + } + + free(smdh); + } + + FSFILE_Close(fileHandle); + } + + bool empty = strlen(item->name) == 0; + if(!empty) { + empty = true; + + char* curr = item->name; + while(*curr) { + if(*curr != ' ') { + empty = false; + break; + } + + curr++; + } + } + + if(empty) { + snprintf(item->name, NAME_MAX, "%016llX", titleId); + } + + if(mediaType == MEDIATYPE_NAND) { + item->rgba = COLOR_NAND; + } else if(mediaType == MEDIATYPE_SD) { + item->rgba = COLOR_SD; + } else if(mediaType == MEDIATYPE_GAME_CARD) { + item->rgba = COLOR_GAME_CARD; + } + + item->data = titleInfo; + + (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; + } + } + + return res; +} + +static Result task_populate_titles_add_twl(populate_titles_data* data, FS_MediaType mediaType, u64 titleId) { + Result res = 0; + + u64 realTitleId = 0; + char productCode[12] = {'\0'}; + u16 version = 0; + u64 installedSize = 0; + + AM_TitleEntry entry; + if(R_SUCCEEDED(res = AM_GetTitleInfo(mediaType, 1, &titleId, &entry))) { + realTitleId = titleId; + AM_GetTitleProductCode(mediaType, titleId, productCode); + version = entry.version; + installedSize = entry.size; + } else { + u8* header = (u8*) calloc(1, 0x3B4); + if(header != NULL) { + if(R_SUCCEEDED(res = FSUSER_GetLegacyRomHeader(mediaType, titleId, header))) { + realTitleId = titleId != 0 ? titleId : *(u64*) &header[0x230]; + memcpy(productCode, header, 0x00C); + version = header[0x01E]; + installedSize = (header[0x012] & 0x2) != 0 ? *(u32*) &header[0x210] : *(u32*) &header[0x080]; + } + + free(header); + } else { + res = R_FBI_OUT_OF_MEMORY; + } + } + + if(R_SUCCEEDED(res)) { + title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info)); + if(titleInfo != NULL) { + titleInfo->mediaType = mediaType; + titleInfo->titleId = realTitleId; + strncpy(titleInfo->productCode, productCode, 12); + titleInfo->version = version; + titleInfo->installedSize = installedSize; + titleInfo->twl = true; + titleInfo->hasMeta = false; + + list_item* item = &data->items[*data->count]; + + BNR* bnr = (BNR*) calloc(1, sizeof(BNR)); + if(bnr != NULL) { + if(R_SUCCEEDED(FSUSER_GetLegacyBannerData(mediaType, titleId, (u8*) bnr))) { + titleInfo->hasMeta = true; + + u8 systemLanguage = CFG_LANGUAGE_EN; + CFGU_GetSystemLanguage(&systemLanguage); + + char title[0x100] = {'\0'}; + utf16_to_utf8((uint8_t*) title, bnr->titles[systemLanguage], 0x100); + + if(strchr(title, '\n') == NULL) { + size_t len = strlen(title); + strncpy(item->name, title, len); + strncpy(titleInfo->meta.shortDescription, title, len); + } else { + char* destinations[] = {titleInfo->meta.shortDescription, titleInfo->meta.longDescription, titleInfo->meta.publisher}; + int currDest = 0; + + char* last = title; + char* curr = NULL; + + while(currDest < 3 && (curr = strchr(last, '\n')) != NULL) { + if(currDest == 0) { + strncpy(item->name, last, curr - last); + } + + strncpy(destinations[currDest++], last, curr - last); + last = curr + 1; + } + + strncpy(item->name, title, last - title); + if(currDest < 3) { + strncpy(destinations[currDest], last, strlen(title) - (last - title)); + } + } + + u8 icon[32 * 32 * 2]; + for(u32 x = 0; x < 32; x++) { + for(u32 y = 0; y < 32; y++) { + u32 srcPos = (((y >> 3) * 4 + (x >> 3)) * 8 + (y & 7)) * 4 + ((x & 7) >> 1); + u32 srcShift = (x & 1) * 4; + u16 srcPx = bnr->mainIconPalette[(bnr->mainIconBitmap[srcPos] >> srcShift) & 0xF]; + + u8 r = (u8) (srcPx & 0x1F); + u8 g = (u8) ((srcPx >> 5) & 0x1F); + u8 b = (u8) ((srcPx >> 10) & 0x1F); + + u16 reversedPx = (u16) ((r << 11) | (g << 6) | (b << 1) | 1); + + u32 dstPos = (y * 32 + x) * 2; + icon[dstPos + 0] = (u8) (reversedPx & 0xFF); + icon[dstPos + 1] = (u8) ((reversedPx >> 8) & 0xFF); + } + } + + titleInfo->meta.texture = screen_load_texture_auto(icon, sizeof(icon), 32, 32, GPU_RGBA5551, false); + } + + free(bnr); + } + + bool empty = strlen(item->name) == 0; + if(!empty) { + empty = true; + + char* curr = item->name; + while(*curr) { + if(*curr != ' ') { + empty = false; + break; + } + + curr++; + } + } + + if(empty) { + snprintf(item->name, NAME_MAX, "%016llX", realTitleId); + } + + item->rgba = COLOR_DS_TITLE; + item->data = titleInfo; + + (*data->count)++; + } else { + res = R_FBI_OUT_OF_MEMORY; + } + } + + return res; +} + static Result task_populate_titles_from(populate_titles_data* data, FS_MediaType mediaType, bool useDSiWare) { bool inserted; FS_CardType type; @@ -37,168 +261,17 @@ static Result task_populate_titles_from(populate_titles_data* data, FS_MediaType if(R_SUCCEEDED(res = AM_GetTitleList(&titleCount, mediaType, titleCount, titleIds))) { qsort(titleIds, titleCount, sizeof(u64), util_compare_u64); - AM_TitleEntry* titleInfos = (AM_TitleEntry*) calloc(titleCount, sizeof(AM_TitleEntry)); - if(titleInfos != NULL) { - if(R_SUCCEEDED(res = AM_GetTitleInfo(mediaType, titleCount, titleIds, titleInfos))) { - SMDH* smdh = (SMDH*) calloc(1, sizeof(SMDH)); - BNR* bnr = (BNR*) calloc(1, sizeof(BNR)); - for(u32 i = 0; i < titleCount && i < data->max; i++) { - if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { - break; - } - - bool dsiWare = ((titleIds[i] >> 32) & 0x8000) != 0; - if(dsiWare != useDSiWare) { - continue; - } - - title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info)); - if(titleInfo != NULL) { - titleInfo->mediaType = mediaType; - titleInfo->titleId = titleIds[i]; - AM_GetTitleProductCode(mediaType, titleIds[i], titleInfo->productCode); - titleInfo->version = titleInfos[i].version; - titleInfo->installedSize = titleInfos[i].size; - titleInfo->twl = dsiWare; - titleInfo->hasSmdh = false; - - list_item* item = &data->items[*data->count]; - - if(dsiWare) { - if(R_SUCCEEDED(FSUSER_GetLegacyBannerData(mediaType, titleIds[i], (u8*) bnr))) { - titleInfo->hasSmdh = true; - - u8 systemLanguage = CFG_LANGUAGE_EN; - CFGU_GetSystemLanguage(&systemLanguage); - - char title[0x100] = {'\0'}; - utf16_to_utf8((uint8_t*) title, bnr->titles[systemLanguage], 0x100); - - if(strchr(title, '\n') == NULL) { - size_t len = strlen(title); - strncpy(item->name, title, len); - strncpy(titleInfo->smdhInfo.shortDescription, title, len); - } else { - char* destinations[] = {titleInfo->smdhInfo.shortDescription, titleInfo->smdhInfo.longDescription, titleInfo->smdhInfo.publisher}; - int currDest = 0; - - char* last = title; - char* curr = NULL; - - while(currDest < 3 && (curr = strchr(last, '\n')) != NULL) { - if(currDest == 0) { - strncpy(item->name, last, curr - last); - } - - strncpy(destinations[currDest++], last, curr - last); - last = curr + 1; - } - - if(currDest < 3) { - strncpy(destinations[currDest], last, strlen(title) - (last - title)); - } - - strncpy(item->name, title, last - title); - int len = strlen(item->name); - for(int pos = 0; pos < len; pos++) { - if(item->name[pos] == '\n') { - item->name[pos] = ' '; - } - } - } - - u8 icon[32 * 32 * 2]; - for(u32 x = 0; x < 32; x++) { - for(u32 y = 0; y < 32; y++) { - u32 srcPos = (((y >> 3) * 4 + (x >> 3)) * 8 + (y & 7)) * 4 + ((x & 7) >> 1); - u32 srcShift = (x & 1) * 4; - u16 srcPx = bnr->mainIconPalette[(bnr->mainIconBitmap[srcPos] >> srcShift) & 0xF]; - - u8 r = (u8) (srcPx & 0x1F); - u8 g = (u8) ((srcPx >> 5) & 0x1F); - u8 b = (u8) ((srcPx >> 10) & 0x1F); - - u16 reversedPx = (u16) ((r << 11) | (g << 6) | (b << 1) | 1); - - u32 dstPos = (y * 32 + x) * 2; - icon[dstPos + 0] = (u8) (reversedPx & 0xFF); - icon[dstPos + 1] = (u8) ((reversedPx >> 8) & 0xFF); - } - } - - titleInfo->smdhInfo.texture = screen_load_texture_auto(icon, sizeof(icon), 32, 32, GPU_RGBA5551, false); - } - } else { - static const u32 filePathData[] = {0x00000000, 0x00000000, 0x00000002, 0x6E6F6369, 0x00000000}; - static const FS_Path filePath = (FS_Path) {PATH_BINARY, 0x14, (u8*) filePathData}; - u32 archivePath[] = {(u32) (titleIds[i] & 0xFFFFFFFF), (u32) ((titleIds[i] >> 32) & 0xFFFFFFFF), mediaType, 0x00000000}; - FS_Archive archive = {ARCHIVE_SAVEDATA_AND_CONTENT, (FS_Path) {PATH_BINARY, 0x10, (u8*) archivePath}}; - Handle fileHandle; - if(R_SUCCEEDED(FSUSER_OpenFileDirectly(&fileHandle, archive, filePath, FS_OPEN_READ, 0))) { - u32 bytesRead; - if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 0, smdh, sizeof(SMDH))) && bytesRead == sizeof(SMDH)) { - if(smdh->magic[0] == 'S' && smdh->magic[1] == 'M' && smdh->magic[2] == 'D' && smdh->magic[3] == 'H') { - u8 systemLanguage = CFG_LANGUAGE_EN; - CFGU_GetSystemLanguage(&systemLanguage); - - utf16_to_utf8((uint8_t*) item->name, smdh->titles[systemLanguage].shortDescription, NAME_MAX); - - titleInfo->hasSmdh = true; - utf16_to_utf8((uint8_t*) titleInfo->smdhInfo.shortDescription, smdh->titles[systemLanguage].shortDescription, sizeof(titleInfo->smdhInfo.shortDescription)); - utf16_to_utf8((uint8_t*) titleInfo->smdhInfo.longDescription, smdh->titles[systemLanguage].longDescription, sizeof(titleInfo->smdhInfo.longDescription)); - utf16_to_utf8((uint8_t*) titleInfo->smdhInfo.publisher, smdh->titles[systemLanguage].publisher, sizeof(titleInfo->smdhInfo.publisher)); - titleInfo->smdhInfo.texture = screen_load_texture_tiled_auto(smdh->largeIcon, sizeof(smdh->largeIcon), 48, 48, GPU_RGB565, false); - } - } - - FSFILE_Close(fileHandle); - } - } - - bool empty = strlen(item->name) == 0; - if(!empty) { - empty = true; - - char* curr = item->name; - while(*curr) { - if(*curr != ' ') { - empty = false; - break; - } - - curr++; - } - } - - if(empty) { - snprintf(item->name, NAME_MAX, "%016llX", titleIds[i]); - } - - if(mediaType == MEDIATYPE_NAND) { - if(dsiWare) { - item->rgba = COLOR_DS_TITLE; - } else { - item->rgba = COLOR_NAND; - } - } else if(mediaType == MEDIATYPE_SD) { - item->rgba = COLOR_SD; - } else if(mediaType == MEDIATYPE_GAME_CARD) { - item->rgba = COLOR_GAME_CARD; - } - - item->data = titleInfo; - - (*data->count)++; - } - } - - free(smdh); - free(bnr); + for(u32 i = 0; i < titleCount && i < data->max && R_SUCCEEDED(res); i++) { + if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { + break; } - free(titleInfos); - } else { - res = R_FBI_OUT_OF_MEMORY; + bool dsiWare = ((titleIds[i] >> 32) & 0x8000) != 0; + if(dsiWare != useDSiWare) { + continue; + } + + res = dsiWare ? task_populate_titles_add_twl(data, mediaType, titleIds[i]) : task_populate_titles_add_ctr(data, mediaType, titleIds[i]); } } @@ -208,84 +281,7 @@ static Result task_populate_titles_from(populate_titles_data* data, FS_MediaType } } } else { - u8* header = (u8*) calloc(1, 0x3B4); - BNR* bnr = (BNR*) calloc(1, sizeof(BNR)); - - if(R_SUCCEEDED(res = FSUSER_GetLegacyRomHeader(MEDIATYPE_GAME_CARD, 0, header)) && R_SUCCEEDED(res = FSUSER_GetLegacyBannerData(MEDIATYPE_GAME_CARD, 0, (u8*) bnr))) { - title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info)); - if(titleInfo != NULL) { - titleInfo->mediaType = MEDIATYPE_GAME_CARD; - titleInfo->titleId = *(u64*) &header[0x230]; - memcpy(titleInfo->productCode, header, 0x00C); - titleInfo->version = header[0x01E]; - titleInfo->installedSize = titleInfo->titleId != 0 ? *(u32*) &header[0x210] : *(u32*) &header[0x080]; - titleInfo->twl = true; - titleInfo->hasSmdh = true; - - list_item* item = &data->items[*data->count]; - - u8 systemLanguage = CFG_LANGUAGE_EN; - CFGU_GetSystemLanguage(&systemLanguage); - - char title[0x100] = {'\0'}; - utf16_to_utf8((uint8_t*) title, bnr->titles[systemLanguage], 0x100); - - if(strchr(title, '\n') == NULL) { - size_t len = strlen(title); - strncpy(item->name, title, len); - strncpy(titleInfo->smdhInfo.shortDescription, title, len); - } else { - char* destinations[] = {titleInfo->smdhInfo.shortDescription, titleInfo->smdhInfo.longDescription, titleInfo->smdhInfo.publisher}; - int currDest = 0; - - char* last = title; - char* curr = NULL; - - while(currDest < 3 && (curr = strchr(last, '\n')) != NULL) { - if(currDest == 0) { - strncpy(item->name, last, curr - last); - } - - strncpy(destinations[currDest++], last, curr - last); - last = curr + 1; - } - - strncpy(item->name, title, last - title); - if(currDest < 3) { - strncpy(destinations[currDest], last, strlen(title) - (last - title)); - } - } - - u8 icon[32 * 32 * 2]; - for(u32 x = 0; x < 32; x++) { - for(u32 y = 0; y < 32; y++) { - u32 srcPos = (((y >> 3) * 4 + (x >> 3)) * 8 + (y & 7)) * 4 + ((x & 7) >> 1); - u32 srcShift = (x & 1) * 4; - u16 srcPx = bnr->mainIconPalette[(bnr->mainIconBitmap[srcPos] >> srcShift) & 0xF]; - - u8 r = (u8) (srcPx & 0x1F); - u8 g = (u8) ((srcPx >> 5) & 0x1F); - u8 b = (u8) ((srcPx >> 10) & 0x1F); - - u16 reversedPx = (u16) ((r << 11) | (g << 6) | (b << 1) | 1); - - u32 dstPos = (y * 32 + x) * 2; - icon[dstPos + 0] = (u8) (reversedPx & 0xFF); - icon[dstPos + 1] = (u8) ((reversedPx >> 8) & 0xFF); - } - } - - titleInfo->smdhInfo.texture = screen_load_texture_auto(icon, sizeof(icon), 32, 32, GPU_RGBA5551, false); - - item->rgba = COLOR_DS_TITLE; - item->data = titleInfo; - - (*data->count)++; - } - } - - free(header); - free(bnr); + res = task_populate_titles_add_twl(data, mediaType, 0); } return res; @@ -303,7 +299,7 @@ static void task_populate_titles_thread(void* arg) { free(data); } -static void task_clear_titles(list_item* items, u32* count) { +void task_clear_titles(list_item* items, u32* count) { if(items == NULL || count == NULL) { return; } @@ -314,8 +310,8 @@ static void task_clear_titles(list_item* items, u32* count) { for(u32 i = 0; i < prevCount; i++) { if(items[i].data != NULL) { title_info* titleInfo = (title_info*) items[i].data; - if(titleInfo->hasSmdh) { - screen_unload_texture(titleInfo->smdhInfo.texture); + if(titleInfo->hasMeta) { + screen_unload_texture(titleInfo->meta.texture); } free(items[i].data); diff --git a/source/ui/section/task/task.h b/source/ui/section/task/task.h index aa1c6bc..2b61f5a 100644 --- a/source/ui/section/task/task.h +++ b/source/ui/section/task/task.h @@ -9,7 +9,7 @@ typedef struct { char longDescription[0x200]; char publisher[0x100]; u32 texture; -} smdh_info; +} meta_info; typedef struct { FS_MediaType mediaType; @@ -18,8 +18,8 @@ typedef struct { u16 version; u64 installedSize; bool twl; - bool hasSmdh; - smdh_info smdhInfo; + bool hasMeta; + meta_info meta; } title_info; typedef struct { @@ -36,8 +36,8 @@ typedef struct { FS_MediaType mediaType; u64 extSaveDataId; bool shared; - bool hasSmdh; - smdh_info smdhInfo; + bool hasMeta; + meta_info meta; } ext_save_data_info; typedef struct { @@ -48,8 +48,8 @@ typedef struct { u64 titleId; u16 version; u64 installedSize; - bool hasSmdh; - smdh_info smdhInfo; + bool hasMeta; + meta_info meta; } cia_info; typedef struct { @@ -70,7 +70,8 @@ typedef struct { #define R_FBI_CANCELLED MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, 1) #define R_FBI_ERRNO MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 2) -#define R_FBI_WRONG_SYSTEM MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_APPLICATION, 3) +#define R_FBI_HTTP_RESPONSE_CODE MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 3) +#define R_FBI_WRONG_SYSTEM MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_APPLICATION, 4) #define R_FBI_OUT_OF_MEMORY MAKERESULT(RL_FATAL, RS_OUTOFRESOURCE, RM_APPLICATION, RD_OUT_OF_MEMORY) @@ -120,10 +121,24 @@ typedef struct { bool task_is_quit_all(); void task_quit_all(); +Handle task_capture_cam(Handle* mutex, u16* buffer, s16 width, s16 height); + +Handle task_data_op(data_op_info* info); + +void task_clear_ext_save_data(list_item* items, u32* count); Handle task_populate_ext_save_data(list_item* items, u32* count, u32 max); + +void task_clear_files(list_item* items, u32* count); Handle task_populate_files(list_item* items, u32* count, u32 max, file_info* dir); + +void task_clear_pending_titles(list_item* items, u32* count); Handle task_populate_pending_titles(list_item* items, u32* count, u32 max); + +void task_clear_system_save_data(list_item* items, u32* count); Handle task_populate_system_save_data(list_item* items, u32* count, u32 max); + +void task_clear_tickets(list_item* items, u32* count); Handle task_populate_tickets(list_item* items, u32* count, u32 max); -Handle task_populate_titles(list_item* items, u32* count, u32 max); -Handle task_data_op(data_op_info* info); \ No newline at end of file + +void task_clear_titles(list_item* items, u32* count); +Handle task_populate_titles(list_item* items, u32* count, u32 max); \ No newline at end of file diff --git a/source/ui/section/tickets.c b/source/ui/section/tickets.c index e6114b8..a08b9f1 100644 --- a/source/ui/section/tickets.c +++ b/source/ui/section/tickets.c @@ -93,6 +93,7 @@ static void tickets_update(ui_view* view, void* data, list_item** items, u32** i ui_pop(); list_destroy(view); + task_clear_tickets(listData->items, &listData->count); free(listData); return; } diff --git a/source/ui/section/titles.c b/source/ui/section/titles.c index 08378e9..f087623 100644 --- a/source/ui/section/titles.c +++ b/source/ui/section/titles.c @@ -139,6 +139,7 @@ static void titles_update(ui_view* view, void* data, list_item** items, u32** it ui_pop(); list_destroy(view); + task_clear_titles(listData->items, &listData->count); free(listData); return; } diff --git a/source/ui/ui.c b/source/ui/ui.c index 9354ff8..1245ac3 100644 --- a/source/ui/ui.c +++ b/source/ui/ui.c @@ -64,25 +64,10 @@ void ui_pop() { if(ui_stack_top >= 0) { ui_stack[ui_stack_top--] = NULL; } - + svcReleaseMutex(ui_stack_mutex); } -void ui_update() { - hidScanInput(); - - ui_view* ui = ui_top(); - if(ui != NULL && ui->update != NULL) { - u32 bottomScreenTopBarHeight = 0; - screen_get_texture_size(NULL, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); - - u32 bottomScreenBottomBarHeight = 0; - screen_get_texture_size(NULL, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); - - ui->update(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); - } -} - static void ui_draw_top(ui_view* ui) { u32 topScreenBgWidth = 0; u32 topScreenBgHeight = 0; @@ -232,14 +217,31 @@ static void ui_draw_bottom(ui_view* ui) { } } -void ui_draw() { - ui_view* ui = ui_top(); +bool ui_update() { + ui_view* ui = NULL; + + hidScanInput(); + + ui = ui_top(); + if(ui != NULL && ui->update != NULL) { + u32 bottomScreenTopBarHeight = 0; + screen_get_texture_size(NULL, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); + + u32 bottomScreenBottomBarHeight = 0; + screen_get_texture_size(NULL, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); + + ui->update(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); + } + + ui = ui_top(); if(ui != NULL) { screen_begin_frame(); ui_draw_top(ui); ui_draw_bottom(ui); screen_end_frame(); } + + return ui != NULL; } void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { @@ -247,50 +249,50 @@ void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, f char buf[64]; - if(info->hasSmdh) { - u32 smdhInfoBoxShadowWidth; - u32 smdhInfoBoxShadowHeight; - screen_get_texture_size(&smdhInfoBoxShadowWidth, &smdhInfoBoxShadowHeight, TEXTURE_SMDH_INFO_BOX_SHADOW); + if(info->hasMeta) { + u32 metaInfoBoxShadowWidth; + u32 metaInfoBoxShadowHeight; + screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW); - float smdhInfoBoxShadowX = x1 + (x2 - x1 - smdhInfoBoxShadowWidth) / 2; - float smdhInfoBoxShadowY = y1 + (y2 - y1) / 4 - smdhInfoBoxShadowHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX_SHADOW, smdhInfoBoxShadowX, smdhInfoBoxShadowY, smdhInfoBoxShadowWidth, smdhInfoBoxShadowHeight); + float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2; + float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight); - u32 smdhInfoBoxWidth; - u32 smdhInfoBoxHeight; - screen_get_texture_size(&smdhInfoBoxWidth, &smdhInfoBoxHeight, TEXTURE_SMDH_INFO_BOX); + u32 metaInfoBoxWidth; + u32 metaInfoBoxHeight; + screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX); - float smdhInfoBoxX = x1 + (x2 - x1 - smdhInfoBoxWidth) / 2; - float smdhInfoBoxY = y1 + (y2 - y1) / 4 - smdhInfoBoxHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX, smdhInfoBoxX, smdhInfoBoxY, smdhInfoBoxWidth, smdhInfoBoxHeight); + float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2; + float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight); - u32 smdhIconWidth; - u32 smdhIconHeight; - screen_get_texture_size(&smdhIconWidth, &smdhIconHeight, info->smdhInfo.texture); + u32 iconWidth; + u32 iconHeight; + screen_get_texture_size(&iconWidth, &iconHeight, info->meta.texture); - float smdhIconX = smdhInfoBoxX + (64 - smdhIconWidth) / 2; - float smdhIconY = smdhInfoBoxY + (smdhInfoBoxHeight - smdhIconHeight) / 2; - screen_draw_texture(info->smdhInfo.texture, smdhIconX, smdhIconY, smdhIconWidth, smdhIconHeight); + float iconX = metaInfoBoxX + (64 - iconWidth) / 2; + float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2; + screen_draw_texture(info->meta.texture, iconX, iconY, iconWidth, iconHeight); float shortDescriptionHeight; - screen_get_string_size(NULL, &shortDescriptionHeight, info->smdhInfo.shortDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &shortDescriptionHeight, info->meta.shortDescription, 0.5f, 0.5f); float longDescriptionHeight; - screen_get_string_size(NULL, &longDescriptionHeight, info->smdhInfo.longDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &longDescriptionHeight, info->meta.longDescription, 0.5f, 0.5f); float publisherHeight; - screen_get_string_size(NULL, &publisherHeight, info->smdhInfo.publisher, 0.5f, 0.5f); + screen_get_string_size(NULL, &publisherHeight, info->meta.publisher, 0.5f, 0.5f); - float smdhTextX = smdhInfoBoxX + 64; + float metaTextX = metaInfoBoxX + 64; - float smdhShortDescriptionY = smdhInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; - screen_draw_string(info->smdhInfo.shortDescription, smdhTextX, smdhShortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; + screen_draw_string(info->meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhLongDescriptionY = smdhShortDescriptionY + shortDescriptionHeight + 2; - screen_draw_string(info->smdhInfo.longDescription, smdhTextX, smdhLongDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2; + screen_draw_string(info->meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhPublisherY = smdhLongDescriptionY + longDescriptionHeight + 2; - screen_draw_string(info->smdhInfo.publisher, smdhTextX, smdhPublisherY, 0.5f, 0.5f, COLOR_TEXT, false); + float publisherY = longDescriptionY + longDescriptionHeight + 2; + screen_draw_string(info->meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false); } snprintf(buf, 64, "Ext Save Data ID: %016llX", info->extSaveDataId); @@ -345,50 +347,50 @@ void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, screen_draw_string(buf, sizeX, sizeY, 0.5f, 0.5f, COLOR_TEXT, false); if(info->isCia) { - if(info->ciaInfo.hasSmdh) { - u32 smdhInfoBoxShadowWidth; - u32 smdhInfoBoxShadowHeight; - screen_get_texture_size(&smdhInfoBoxShadowWidth, &smdhInfoBoxShadowHeight, TEXTURE_SMDH_INFO_BOX_SHADOW); + if(info->ciaInfo.hasMeta) { + u32 metaInfoBoxShadowWidth; + u32 metaInfoBoxShadowHeight; + screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW); - float smdhInfoBoxShadowX = x1 + (x2 - x1 - smdhInfoBoxShadowWidth) / 2; - float smdhInfoBoxShadowY = y1 + (y2 - y1) / 4 - smdhInfoBoxShadowHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX_SHADOW, smdhInfoBoxShadowX, smdhInfoBoxShadowY, smdhInfoBoxShadowWidth, smdhInfoBoxShadowHeight); + float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2; + float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight); - u32 smdhInfoBoxWidth; - u32 smdhInfoBoxHeight; - screen_get_texture_size(&smdhInfoBoxWidth, &smdhInfoBoxHeight, TEXTURE_SMDH_INFO_BOX); + u32 metaInfoBoxWidth; + u32 metaInfoBoxHeight; + screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX); - float smdhInfoBoxX = x1 + (x2 - x1 - smdhInfoBoxWidth) / 2; - float smdhInfoBoxY = y1 + (y2 - y1) / 4 - smdhInfoBoxHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX, smdhInfoBoxX, smdhInfoBoxY, smdhInfoBoxWidth, smdhInfoBoxHeight); + float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2; + float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight); - u32 smdhIconWidth; - u32 smdhIconHeight; - screen_get_texture_size(&smdhIconWidth, &smdhIconHeight, info->ciaInfo.smdhInfo.texture); + u32 iconWidth; + u32 iconHeight; + screen_get_texture_size(&iconWidth, &iconHeight, info->ciaInfo.meta.texture); - float smdhIconX = smdhInfoBoxX + (64 - smdhIconWidth) / 2; - float smdhIconY = smdhInfoBoxY + (smdhInfoBoxHeight - smdhIconHeight) / 2; - screen_draw_texture(info->ciaInfo.smdhInfo.texture, smdhIconX, smdhIconY, smdhIconWidth, smdhIconHeight); + float iconX = metaInfoBoxX + (64 - iconWidth) / 2; + float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2; + screen_draw_texture(info->ciaInfo.meta.texture, iconX, iconY, iconWidth, iconHeight); float shortDescriptionHeight; - screen_get_string_size(NULL, &shortDescriptionHeight, info->ciaInfo.smdhInfo.shortDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &shortDescriptionHeight, info->ciaInfo.meta.shortDescription, 0.5f, 0.5f); float longDescriptionHeight; - screen_get_string_size(NULL, &longDescriptionHeight, info->ciaInfo.smdhInfo.longDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &longDescriptionHeight, info->ciaInfo.meta.longDescription, 0.5f, 0.5f); float publisherHeight; - screen_get_string_size(NULL, &publisherHeight, info->ciaInfo.smdhInfo.publisher, 0.5f, 0.5f); + screen_get_string_size(NULL, &publisherHeight, info->ciaInfo.meta.publisher, 0.5f, 0.5f); - float smdhTextX = smdhInfoBoxX + 64; + float metaTextX = metaInfoBoxX + 64; - float smdhShortDescriptionY = smdhInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; - screen_draw_string(info->ciaInfo.smdhInfo.shortDescription, smdhTextX, smdhShortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; + screen_draw_string(info->ciaInfo.meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhLongDescriptionY = smdhShortDescriptionY + shortDescriptionHeight + 2; - screen_draw_string(info->ciaInfo.smdhInfo.longDescription, smdhTextX, smdhLongDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2; + screen_draw_string(info->ciaInfo.meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhPublisherY = smdhLongDescriptionY + longDescriptionHeight + 2; - screen_draw_string(info->ciaInfo.smdhInfo.publisher, smdhTextX, smdhPublisherY, 0.5f, 0.5f, COLOR_TEXT, false); + float publisherY = longDescriptionY + longDescriptionHeight + 2; + screen_draw_string(info->ciaInfo.meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false); } snprintf(buf, 64, "Title ID: %016llX", info->ciaInfo.titleId); @@ -517,50 +519,50 @@ void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2, char buf[64]; - if(info->hasSmdh) { - u32 smdhInfoBoxShadowWidth; - u32 smdhInfoBoxShadowHeight; - screen_get_texture_size(&smdhInfoBoxShadowWidth, &smdhInfoBoxShadowHeight, TEXTURE_SMDH_INFO_BOX_SHADOW); + if(info->hasMeta) { + u32 metaInfoBoxShadowWidth; + u32 metaInfoBoxShadowHeight; + screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW); - float smdhInfoBoxShadowX = x1 + (x2 - x1 - smdhInfoBoxShadowWidth) / 2; - float smdhInfoBoxShadowY = y1 + (y2 - y1) / 4 - smdhInfoBoxShadowHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX_SHADOW, smdhInfoBoxShadowX, smdhInfoBoxShadowY, smdhInfoBoxShadowWidth, smdhInfoBoxShadowHeight); + float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2; + float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight); - u32 smdhInfoBoxWidth; - u32 smdhInfoBoxHeight; - screen_get_texture_size(&smdhInfoBoxWidth, &smdhInfoBoxHeight, TEXTURE_SMDH_INFO_BOX); + u32 metaInfoBoxWidth; + u32 metaInfoBoxHeight; + screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX); - float smdhInfoBoxX = x1 + (x2 - x1 - smdhInfoBoxWidth) / 2; - float smdhInfoBoxY = y1 + (y2 - y1) / 4 - smdhInfoBoxHeight / 2; - screen_draw_texture(TEXTURE_SMDH_INFO_BOX, smdhInfoBoxX, smdhInfoBoxY, smdhInfoBoxWidth, smdhInfoBoxHeight); + float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2; + float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight); - u32 smdhIconWidth; - u32 smdhIconHeight; - screen_get_texture_size(&smdhIconWidth, &smdhIconHeight, info->smdhInfo.texture); + u32 iconWidth; + u32 iconHeight; + screen_get_texture_size(&iconWidth, &iconHeight, info->meta.texture); - float smdhIconX = smdhInfoBoxX + (64 - smdhIconWidth) / 2; - float smdhIconY = smdhInfoBoxY + (smdhInfoBoxHeight - smdhIconHeight) / 2; - screen_draw_texture(info->smdhInfo.texture, smdhIconX, smdhIconY, smdhIconWidth, smdhIconHeight); + float iconX = metaInfoBoxX + (64 - iconWidth) / 2; + float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2; + screen_draw_texture(info->meta.texture, iconX, iconY, iconWidth, iconHeight); float shortDescriptionHeight; - screen_get_string_size(NULL, &shortDescriptionHeight, info->smdhInfo.shortDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &shortDescriptionHeight, info->meta.shortDescription, 0.5f, 0.5f); float longDescriptionHeight; - screen_get_string_size(NULL, &longDescriptionHeight, info->smdhInfo.longDescription, 0.5f, 0.5f); + screen_get_string_size(NULL, &longDescriptionHeight, info->meta.longDescription, 0.5f, 0.5f); float publisherHeight; - screen_get_string_size(NULL, &publisherHeight, info->smdhInfo.publisher, 0.5f, 0.5f); + screen_get_string_size(NULL, &publisherHeight, info->meta.publisher, 0.5f, 0.5f); - float smdhTextX = smdhInfoBoxX + 64; + float metaTextX = metaInfoBoxX + 64; - float smdhShortDescriptionY = smdhInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; - screen_draw_string(info->smdhInfo.shortDescription, smdhTextX, smdhShortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; + screen_draw_string(info->meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhLongDescriptionY = smdhShortDescriptionY + shortDescriptionHeight + 2; - screen_draw_string(info->smdhInfo.longDescription, smdhTextX, smdhLongDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); + float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2; + screen_draw_string(info->meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false); - float smdhPublisherY = smdhLongDescriptionY + longDescriptionHeight + 2; - screen_draw_string(info->smdhInfo.publisher, smdhTextX, smdhPublisherY, 0.5f, 0.5f, COLOR_TEXT, false); + float publisherY = longDescriptionY + longDescriptionHeight + 2; + screen_draw_string(info->meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false); } snprintf(buf, 64, "Title ID: %016llX", info->titleId); diff --git a/source/ui/ui.h b/source/ui/ui.h index d0ee4ba..ca22f97 100644 --- a/source/ui/ui.h +++ b/source/ui/ui.h @@ -17,8 +17,7 @@ void ui_exit(); ui_view* ui_top(); bool ui_push(ui_view* view); void ui_pop(); -void ui_update(); -void ui_draw(); +bool ui_update(); void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2); void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);