Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.4.2

- One place on the home screen always stays unpinned. With every place pinned, the last game opened (the
jailbreak's entry, say) had nowhere to go and ended up in the game library. Pin the whole row now leaves the
first place free, and Lineup will not save a home screen pinned in full.
- A row pinned in full by an older version opens with an offer to free its first place.

## 1.4.1

1.4.0 was not released; its changes ship here.
Expand Down
2 changes: 1 addition & 1 deletion app/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {Archives, Downloads, FileSystem, Http, Payloads} from '@ps5-react/core';
import {T} from './i18n.js';

export const VERSION = '1.4.1';
export const VERSION = '1.4.2';
const BASE = 'http://127.0.0.1:9077';

/** An error the app shows as is: a short title and what to do about it. */
Expand Down
2 changes: 1 addition & 1 deletion app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"titleId": "PPSA99077",
"contentId": "UP9000-PPSA99077_00-LINEUP0000000000",
"name": "Lineup",
"version": "01.004.001",
"version": "01.004.002",
"render": {
"width": 1920,
"height": 1080
Expand Down
10 changes: 10 additions & 0 deletions app/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ const en = {
missingBody: 'The home screen database was rebuilt or updated, so the games no longer hold their places. '
+ 'Pin them again?',
reapply: 'Pin again', later: 'Not now',
keepFree: 'Leave one place on the home screen unpinned, for the last game opened.',
fullTitle: 'Every place on the home screen is pinned',
fullBody: 'The last game opened has no place left there and ends up in the game library. '
+ 'Free the first place for it?',
freeFirst: 'Free the first place',
},
saved: ['Order saved', 'Closing the app. The home screen comes back with the new order.'],
closeNow: 'Close now',
Expand Down Expand Up @@ -158,6 +163,11 @@ const pt = {
missingBody: 'O banco de dados da tela inicial foi reconstruído ou atualizado, e os jogos não seguram mais o '
+ 'lugar. Fixar de novo?',
reapply: 'Fixar de novo', later: 'Agora não',
keepFree: 'Deixe um lugar da tela inicial sem fixar, para o último jogo aberto.',
fullTitle: 'Todos os lugares da tela inicial estão fixos',
fullBody: 'O último jogo aberto fica sem lugar ali e vai parar na biblioteca de jogos. '
+ 'Liberar o primeiro lugar para ele?',
freeFirst: 'Liberar o primeiro lugar',
},
saved: ['Ordem salva', 'Fechando o app. A tela inicial já volta com a nova ordem.'],
closeNow: 'Fechar agora',
Expand Down
49 changes: 35 additions & 14 deletions app/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ const sortKey = name => name.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCas
// Games moved or pinned/unpinned since the row was read. `pinned` maps the games read pinned to their slots.
const changed = s => s.order.reduce((count, id, i) => count + (s.home[id] !== i || !s.pins[id] !== !(id in s.pinned)), 0);
const allPinned = pinned => Object.fromEntries(Object.keys(pinned).map(id => [id, true]));
// Every home place pinned: the last game opened (the jailbreak's entry, say) would have no place there and end up in
// the game library, so one stays free. lineupd refuses such a save too.
const homeFull = (order, pins) => order.slice(0, HOME_LIMIT).every(id => pins[id]);
const without = (pins, id) => {
const rest = {...pins};
delete rest[id];
return rest;
};

// The order the pins ask for, as lineupd's triggers work it out: pinned games at their slots (packed at the end of
// a row that got shorter), the others in their order in the free places.
Expand Down Expand Up @@ -295,10 +303,11 @@ function App() {
if (game.pinned !== null && game.pinned !== undefined) pinned[game.titleId] = game.pinned;
});
const base = list.map(game => game.titleId);
const missing = pins === 'missing';
// A row pinned in full by 1.4.1 or older.
const layer = pins === 'missing' ? 'pinsMissing' : pins === 'on' && homeFull(base, allPinned(pinned)) ? 'pinsFull' : null;
light(false);
patch({phase: 'row', profiles, user, games, home, base, order: base, pins: allPinned(pinned), pinned, focus: 0,
dir: 1, lifted: false, layer: missing ? 'pinsMissing' : null, target: null, pending: missing ? null : {index: 0}});
dir: 1, lifted: false, layer, target: null, pending: layer ? null : {index: 0}});
};

const boot = async () => {
Expand Down Expand Up @@ -418,6 +427,11 @@ function App() {
toast(T.alreadySaved);
return;
}
if (homeFull(now.order, now.pins)) {
close();
toast(T.pins.keepFree);
return;
}
light(false);
patch({lifted: false, layer: 'busy'});
const result = await api.save(now.user, now.order, now.games, now.pins);
Expand Down Expand Up @@ -480,13 +494,11 @@ function App() {
return true;
}
if (action === 'square') {
patch(p => {
const id = p.order[p.focus];
const pins = {...p.pins};
if (pins[id]) delete pins[id];
else pins[id] = true;
return {pins};
});
const p = ref.current;
const id = p.order[p.focus];
const pins = p.pins[id] ? without(p.pins, id) : {...p.pins, [id]: true};
if (!p.pins[id] && homeFull(p.order, pins)) toast(T.pins.keepFree);
else patch({pins});
return true;
}
return false;
Expand All @@ -496,14 +508,17 @@ function App() {
patch(p => ({order: p.base, pins: allPinned(p.pinned), layer: null, pending: {index: 0}}));
toast(T.discarded);
};
const pinAll = on => patch(p => ({pins: on ? Object.fromEntries(p.order.map(id => [id, true])) : {}, layer: null,
pending: {index: p.focus}}));
// Puts the games back at their slots and saves, which creates the triggers again.
// The whole row but its first place, which stays free.
const pinAll = on => patch(p => ({pins: on ? Object.fromEntries(p.order.slice(1).map(id => [id, true])) : {},
layer: null, pending: {index: p.focus}}));
// Puts the games back at their slots and saves, which creates the triggers again. A row pinned in full gets its
// first place freed.
const reapply = () => {
const now = ref.current;
const order = pinnedOrder(now.order, now.pinned);
patch({order});
save(null, {...now, order}, true);
const pins = homeFull(order, now.pins) ? without(now.pins, order[0]) : now.pins;
patch({order, pins});
save(null, {...now, order, pins}, true);
};
const unpinEverywhere = async () => {
patch({layer: 'busy'});
Expand Down Expand Up @@ -664,6 +679,12 @@ function App() {
<Button label={T.pins.later} onPress={close} />
</Dialog>
) : null}
{s.layer === 'pinsFull' ? (
<Dialog title={T.pins.fullTitle} body={T.pins.fullBody} onBack={close}>
<Button label={T.pins.freeFirst} onPress={reapply} />
<Button label={T.pins.later} onPress={close} />
</Dialog>
) : null}
{s.layer === 'busy' ? (
<FocusScope trap autoFocus onBack={() => true} onAction={() => true}>
<View className="absolute inset-0 bg-black/70 items-center justify-center">
Expand Down
23 changes: 22 additions & 1 deletion app/payloads/lineupd.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
#define LOG(fmt, ...) klog_printf("[lineupd] " fmt "\n", ##__VA_ARGS__)
#endif

#define LINEUPD_VERSION "1.4.1"
#define LINEUPD_VERSION "1.4.2"
#define TITLE_ID "PPSA99077"
#ifdef LINEUPD_DIAG
// Phase 0 diagnostic build (docs/pt-BR/PLANO-1.2.md): its own port, open to the LAN, so it runs beside the
Expand All @@ -73,6 +73,8 @@
#define MAX_HEADER (16 * 1024)
#define MAX_BODY (64 * 1024)
#define MAX_GAMES 512
// The home screen shows this many games of the row, as HOME_LIMIT in app/index.jsx.
#define HOME_LIMIT 12
#define KEEP_BACKUPS 5

static const char* db_path = "/system_data/priv/mms/app.db";
Expand Down Expand Up @@ -495,6 +497,14 @@ static int unpin(const char* user, char* backup, size_t backup_size, char* messa
// Saves `posted` as the new order. Returns the HTTP status; `message` explains a failure.
static int save_order(const char* user, const Row* posted, int posted_count, char* backup, size_t backup_size,
char* integrity, size_t integrity_size, char* message, size_t message_size) {
// With every home place pinned, the last game opened (the jailbreak's entry, say) gets no place there and ends up
// in the game library: one stays free.
int home = posted_count < HOME_LIMIT ? posted_count : HOME_LIMIT, free_places = 0;
for (int i = 0; i < home; ++i) free_places += !posted[i].pinned;
if (home && !free_places) {
snprintf(message, message_size, "Leave one of the first %d places unpinned. Nothing was saved.", home);
return 400;
}
sqlite3* db = open_db(1);
if (!db) { snprintf(message, message_size, "Could not open the home screen database."); return 500; }
int status = 500;
Expand Down Expand Up @@ -1732,6 +1742,17 @@ static void test_pins(void) {
const char* one = "0000000001";
int slots[MAX_GAMES];
given = scalar("SELECT MAX(lastAccessIndex) FROM tbl_iconinfo_0000000001");
// A row pinned in full is refused: one home place stays free.
{
static Row full[MAX_GAMES];
sqlite3* db = open_db(0);
int count = read_row(db, one, full, NULL, MAX_GAMES);
sqlite3_close(db);
for (int i = 0; i < count; ++i) full[i].pinned = 1;
char backup[256], integrity[256], message[300];
assert(save_order(one, full, count, backup, sizeof backup, integrity, sizeof integrity, message, sizeof message) == 400);
assert(!strcmp(pins_of(one, slots), "off"));
}
save_digits(one, "31254", "35");
expect_row(one, "31254");
assert(!strcmp(pins_of(one, slots), "on") && slots[0] == 0 && slots[1] < 0 && slots[3] == 3);
Expand Down
7 changes: 6 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ carry `X-Lineup: 1`, so a web page cannot trigger one with a plain form.

Errors are `{"error": "<English detail>"}`:

- 400: malformed request.
- 400: malformed request, or every home place pinned (see Pinned games).
- 403: the `X-Lineup` header is missing.
- 404: no such profile or icon.
- 409: the row changed since it was read.
Expand All @@ -104,6 +104,11 @@ Saving drops the profile's triggers, writes the order and the pins, and creates
are pins, all in one transaction. When the last pin of every profile is gone, the tables go too. Rebuilding the
database can drop the triggers; `/row` then says `"missing"` and the app offers to pin the games again.

One of the first `HOME_LIMIT` (12) places always stays unpinned: with all of them pinned, the last game opened,
such as the jailbreak's entry, has no place on the home screen and ends up in the game library. The app keeps it
free (Pin the whole row leaves the first place) and offers to free the first place of a row pinned in full by
1.4.1 or older; `save_order` refuses such a row with 400.

## Updates

The app reads `https://github.com/bps2414/lineup/releases/latest/download/update.json` (written by
Expand Down
7 changes: 6 additions & 1 deletion docs/pt-BR/ARQUITETURA.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ formulário simples.

Os erros vêm como `{"error": "<detalhe em inglês>"}`:

- 400: pedido malformado.
- 400: pedido malformado, ou todos os lugares da tela inicial fixos (veja Jogos fixos).
- 403: falta o cabeçalho `X-Lineup`.
- 404: o perfil ou o ícone não existe.
- 409: a fileira mudou desde a leitura.
Expand All @@ -104,6 +104,11 @@ Ao salvar, o lineupd apaga os triggers do perfil, grava a ordem e os fixos e rec
tudo numa transação só. Quando nenhum perfil tem mais jogos fixos, as tabelas também saem. Reconstruir o banco de
dados pode apagar os triggers; aí o `/row` responde `"missing"` e o app oferece fixar de novo.

Um dos primeiros `HOME_LIMIT` (12) lugares sempre fica sem fixar: com todos fixos, o último jogo aberto, como a
entrada do jailbreak, fica sem lugar na tela inicial e vai parar na biblioteca de jogos. O app mantém esse lugar
livre ("Fixar toda a fileira" deixa o primeiro) e oferece liberar o primeiro lugar de uma fileira toda fixa pela
1.4.1 ou anterior; o `save_order` recusa essa fileira com 400.

## Atualização

O app lê `https://github.com/bps2414/lineup/releases/latest/download/update.json` (gerado pelo
Expand Down
7 changes: 7 additions & 0 deletions docs/pt-BR/NOVIDADES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

As notas que o app mostra ao atualizar. Cada versão aqui acompanha a mesma seção do `CHANGELOG.md`.

## 1.4.2

- Um lugar da tela inicial sempre fica sem fixar. Com todos fixos, o último jogo aberto (a entrada do
jailbreak, por exemplo) ficava sem lugar e ia parar na biblioteca de jogos. "Fixar toda a fileira" agora deixa
o primeiro lugar livre, e o Lineup não salva uma tela inicial toda fixa.
- Uma fileira toda fixa por uma versão anterior abre oferecendo liberar o primeiro lugar.

## 1.4.1

- O jogo levantado fica por cima: ao levar ele para a esquerda, o jogo que ele ultrapassa não passa mais por
Expand Down
4 changes: 2 additions & 2 deletions ps5-react/dependencies.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
]
},
"platform": {
"url": "https://github.com/blackbearreloaded/ps5-homebrew-ui.git",
"url": "https://github.com/Phononenom/ps5-homebrew-ui.git",
"revision": "4bd942579dd981b3df9c740438489ca6a614ddc0",
"patches": [
"patches/platform-heap-size.patch",
Expand Down Expand Up @@ -85,7 +85,7 @@
"sha256": "ce2669c51272b70bc0aafe9a4e6223e5c7720edaebb40201db191634aa503f5e"
},
"filesystemHelperClient": {
"url": "https://github.com/blackbearreloaded/ProsperoStore.git",
"url": "https://github.com/JVK3R/ProsperoStore.git",
"revision": "335d8c43f916020205f445b76b66106ee50d2eeb"
},
"filesystemHelper": {
Expand Down
3 changes: 2 additions & 1 deletion ps5-react/tools/build_ps5.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def main():
PS5_CLANG=str(llvm / "clang"), PS5_PAYLOAD_SDK=str(sdk), USE_CCACHE="0")

archive = args.opengl_archive or DEPS / "ps5-opengl-sdk-1.0.0.tar.gz"
fetch("https://github.com/blackbearreloaded/ps5-opengl/releases/download/v1.0.0/ps5-opengl-sdk-1.0.0.tar.gz",
# A copy of the upstream release (blackbearreloaded left GitHub); GL_HASH checks it is the same file.
fetch("https://github.com/MyLuxy/overdrive-deps/releases/download/ps5-opengl-v1.0.0/ps5-opengl-sdk-1.0.0.tar.gz",
archive, GL_HASH)
gl = DEPS / "ps5-opengl-sdk-1.0.0/sdk"
if not gl.exists():
Expand Down
Loading