73d084a703
v1.0.0 đ â Production release. i18n Espagnol + Allemand (launcher + emails).
...
PremiÚre release MAJEURE aprÚs ~30 itérations 0.x. Le systÚme est complet
et stable pour la production : distribution PROSERVE multi-channels, install
launcher cross-fleet, monitoring santé VR, sécurité (signatures Ed25519,
HMAC URLs présignées, settings lock per-license, RFC1918 filter cache LAN).
i18n nouvelles langues (Espagnol + Allemand) :
- Strings.cs : signature T() étendue avec 2 params optionnels (es?/de?)
en plus des 5 obligatoires. Fallback automatique sur l'anglais quand
non traduit â infrastructure prĂȘte immĂ©diatement, traduction progressive
sur les ~317 strings du launcher selon les besoins terrain.
- ~30 strings critiques traduites maintenant (top bar, body, status badges,
actions principales Launch/Install/Cancel/Yes/No, MessageBox titles).
- Available[] inclut es/Español + de/Deutsch ; auto-détection Windows
pour es-* / de-* funcionne au premier launch.
- Init() whitelist mise Ă jour.
Emails release announce localisés par license :
- Migration 005 : ajout `language VARCHAR(8) NULL` sur licenses (whitelist
fr/en/es/de/zh/th/ar, NULL = fallback English).
- getEmailStrings() PHP étendu : dictionnaire 7 langues à 14 strings.
Traduction complÚte es + de (gérable : 28 nouvelles strings, vs 634
pour traduire le launcher en intégralité).
- notify_release boucle par license : récupÚre language + locale fallback
'en' + render avec la bonne locale â chaque destinataire reçoit son
email dans la langue de SA license, indépendamment des autres clients.
- Subject localisé aussi (« PROSERVE v1.5.4 ya estå disponible » /
« PROSERVE v1.5.4 ist jetzt verfĂŒgbar »).
- Release notes body reste TOUJOURS en anglais (évite d'avoir à maintenir
N traductions du Markdown des notes).
- Direction RTL préservée pour l'arabe (dir="rtl" sur <html> + <table>).
Admin UI : dropdown langue dans la modal Contacts d'une license + whitelist
backend (set_contact_emails accepte fr/en/es/de/zh/th/ar).
Bumps : 0.29.10 â 1.0.0 â milestone V1, premier release majeur en prod.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 18:01:50 +02:00
500f7d12e6
v0.29.10 â Admin UI refonte (modals + onglets), emails de release, 4-digit versions
...
Admin backoffice â UI redesign :
- Pages versions.php + licenses.php : remplace les <details> inline qui
dĂ©bordaient horizontalement par UN bouton « â Modifier » par row qui
ouvre une modal <dialog> avec onglets. 5 tabs versions (Méta, Notes,
BĂTA, Channels, AvancĂ©) + 6 tabs licenses (Prolonger, Slots, Channel,
BĂTA, Lock, Contacts, Machines). DĂ©lĂ©gation JS unique pour les onglets.
- Bouton đ Copier la clĂ© license dans chaque row (Clipboard API + feedback
visuel â vert 1.5s). Ăvite le dĂ©tour par phpMyAdmin pour transmettre la
clé aux clients.
- Overlay « hashing en cours » plein écran sur tous les boutons de hash
(3-5 min sur OVH pour 13 Go ZIP). Spinner CSS + message contextualisé
par scope (bulk vs single).
- Date de release passée de datetime-local à date (l'heure n'a pas de
sens UX), avec défaut = aujourd'hui pour release_at et aujourd'hui-1an
pour min_license_date (= license standard couvre les releases sur 1 an).
Emails de notification release :
- Migration 004 : colonne contact_emails TEXT NULL sur licenses (CSV)
- Onglet « Contacts » sur la modal licenses pour saisir les emails par
license (parsing tolérant : CSV, ligne par ligne, point-virgule)
- Bouton « â Notifier » par version : POST notify_release filtre les
licenses éligibles (channel match + min_license_date + can_see_betas
pour BĂTA) et envoie un email HTML Ă chaque contact (dĂ©dup global)
- Template email table-based + bgcolor (compat Outlook/Word engine),
navy foncé #0F172A, logo Asterion en CID embed (= affichage direct
sans demande de permission Outlook), bouton download installer
centré (align="center" + margin auto), release notes en <pre>
- Mailer.php helper : parse emails, multipart/related avec attachments
inline, fallback execCommand pour clipboard
4-digit version support (X.Y.Z.B) :
- SemVer Parse/CompareTo/ToString gĂšrent 3 ou 4 digits ; Build absent =
0 implicite (1.5.4 == 1.5.4.0 < 1.5.4.13). HasExplicitBuild préserve
le format d'origine au round-trip.
- Regex InstallationRegistry Ă©tendue avec (?:\.\d+)? â reconnaĂźt
« PROSERVE v1.5.4.13 » cÎte-à -cÎte avec « PROSERVE v1.5.4 » sur disque
- Server-side : versions.php, launcher.php, DownloadUrl.php, api/index.php,
Releasenotes.php â toutes les regex de validation acceptent le 4á” digit
- Use case : dev/test iterations cohabitant avec leur release stable
Bugs fixes :
- migrate.php : strip ligne par ligne les commentaires SQL avant le
check is-empty. Sans ça, le PREMIER chunk d'un fichier migration
(= header + premier ALTER) commençait par `--` et était silencieusement
skip â ALTER jamais appliquĂ©. Affectait migrations 003, 004.
- SignManifest::getOrComputeSha256 ignorait son cache interne quand force
demandĂ© par le caller, retournant l'ancien hash en 0 ms mĂȘme aprĂšs
re-upload SFTP (avec mtime préservé). Propage maintenant le flag $force.
Bouton « đ Hash » per-row force maintenant un re-calcul systĂ©matique.
- DownloadManager : 416 (Range Not Satisfiable) ajouté aux URL-refresh
triggers, avec HEAD probe pour comparer taille serveur vs manifest â
message d'erreur explicite si ZIP tronqué. Bps display lissé sur une
fenĂȘtre glissante de 12 samples (3 s) â plus de clignotement quand un
segment finit / Polly retry. SHA mismatch popup enrichi avec les
deux SHAs (attendu vs calculé) extraits via regex de l'exception.
- DownloadUrl.php : signature de l'URL utilisait un template hardcodé
/builds/proserve-{version}.zip, ignorant tout rename serveur. Lit
maintenant download.url du manifest et signe le filename réel.
Strings i18n (5 langues) :
- ~15 nouveaux : SHA mismatch enrichi avec sources, 416 size mismatch,
stale manifest, manifest refreshed auto-retry, force fresh menu
Bumps : 0.29.7 â 0.29.10 (4-digit support + accumulated UI fixes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-28 17:24:50 +02:00
50da755a92
v0.29.7 â SteamVR merge + rĂ©silience DL (404 retry, force fresh, SHA purge)
...
SteamVR + Vive Business Streaming settings merge :
- Nouveau ISteamVrSettingsDeployer : si _steamvr/steamvr.vrsettings est
dans le ZIP, deep-merge récursif des blocs racine dans le fichier user
(clĂ© absente â ajout, deux objets â recurse, sinon â replace ; les
sous-clĂ©s cible non touchĂ©es sont prĂ©servĂ©es â crucial pour "trackers")
- Auto-localisation via registre Steam (HKCU/HKLM) + fallback Program Files
- Phase pré-check (CheckMergeNeededAsync) : skip silencieux si la cible
est dĂ©jĂ Ă jour (deep equality) â pas de kill SteamVR/VBS inutile, pas
de popup Ă l'opĂ©rateur. Si changements nĂ©cessaires â popup OK/Annuler
avec nombre de blocs qui changeront + liste des process Ă fermer.
- Liste de kill construite depuis les health checks de type "Process"
(l'opérateur connaßt déjà sa stack VR) + guardians (Vive Business
Streaming en tĂȘte car il relance SteamVR auto)
- Settings UI : section dédiée avec opt-in, override path, liste process
éditable + affichage du path canonique attendu
Résilience téléchargements :
- 404 mid-DL traité comme 403/410 (URL refresh trigger) au niveau segment :
on extrait le nouveau filename via /download-url server-side, retry transparent
- Auto-retry install une fois aprÚs refresh manifest sur 404 : l'opérateur
ne voit rien si la cause était un manifest local stale
- Si retry Ă©choue aussi en 404 â message ciblĂ© "manifest serveur stale"
(problÚme cÎté serveur, pas client)
- Sur SHA-256 mismatch : auto-purge du cache LAN local (.zip + .sha256)
+ message d'erreur dédié avec source du DL (peer ou OVH) + nouveau menu
"⻠Forcer re-téléchargement" pour purger état + cache manuellement
- Détection client-side de l'incohérence "signed URL filename != manifest
URL filename" avant DL : abort immédiat plutÎt que 14 Go pour rien
- IZipCacheStore.InvalidateAsync : nouvelle API pour purger un cache par
version (utilisée par SHA mismatch handler + Force Fresh menu)
Bug serveur (DownloadUrl.php) :
- L'endpoint construisait l'URL signée avec un template hardcodé
`/builds/proserve-{version}.zip`, ignorant complĂštement
download.url du manifest. Conséquence : un opérateur qui rename
son ZIP pour buster le cache CDN OVH (ex. proserve-full-1.5.4.zip)
voyait toutes ses releases retournent du 404 silencieux cÎté client.
- Fix : on lit basename(parse_url(entry.download.url).path), whitelist
sur le filename, vérif is_file() avant de signer, erreur 500 explicite
si manifest et filesystem désynchros.
Strings (5 langues) :
- ~20 nouvelles : SteamVR popups + status, SHA mismatch dialog + source
labels, force fresh confirm + menu, manifest stale + auto-retry status
Bumps : 0.29.6 (dĂ©ployĂ© hors-commit pendant la session) â 0.29.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-27 14:52:00 +02:00
01793eb32d
v0.28.12 â Fix DL >100% : onBytes au checkpoint, pas par WriteAsync
...
Régression introduite par v0.28.11 (durability checkpoint) : le footer
affichait des pourcentages > 100% en fin de DL, mĂȘme sans pause manuelle.
Cause : seg.DownloadedBytes était devenu durable (incrémenté au checkpoint
tous les 64 MiB), MAIS onBytes(seg.Index, n) continuait Ă fire par
WriteAsync. Quand Polly retry un segment mid-stream (fréquent sur OVH
mutualisĂ© qui coupe les requĂȘtes longues via PHP-FPM
request_terminate_timeout) :
Attempt 1 :
- onBytes fire pour bytes 0..30 MiB (live)
- HttpResumableException (PHP-FPM kill)
- Dispose flush, mais seg.DownloadedBytes encore Ă 0 (dernier checkpoint)
Polly retry attempt 2 :
- segStart = seg.Start + 0
- Re-download bytes 0..30 MiB (overwrite same content sur disque, OK)
- onBytes fire Ă NOUVEAU pour ces 30 MiB â DOUBLE COMPTAGE
MultipliĂ© par 16 segments Ă N retries â aggregate dĂ©passe total.
Fix : onBytes fire UNIQUEMENT au checkpoint, avec la valeur inFlight
juste avant la reset. Comme ça les bytes d'une attempt qui a failé ne
sont jamais reportés (la failure se produit AVANT que le checkpoint
soit atteint), et la retry re-télécharge + reporte une seule fois.
Trade-off : UI updates tous les 64 MiB par segment au lieu de chaque
4 MiB. Avec 16 segments en parallÚle, ça fait ~10-20 reports/s en pic,
le reporter task échantillonne à 4 Hz de toute façon, invisible cÎté UX.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-14 19:06:45 +02:00
eae0514058
v0.28.11 â Multi-segment DL : durability checkpoint pour fix SHA fail post-pause
...
Bug rapporté : ~80% des reprises aprÚs pause failaient la vérification
SHA-256 finale alors que la DL semblait s'ĂȘtre passĂ©e correctement et
que le fichier était de la bonne taille. Root cause : seg.DownloadedBytes
était incrémenté juste aprÚs le retour de WriteAsync, mais cet appel met
seulement les bytes dans le buffer interne du FileStream (4 MiB) â pas
forcément sur disque ni dans le cache OS.
à la pause, le ct est cancelé, le segment throw une OperationCanceledException
au prochain await, et `await using var dst` dispose le FileStream â qui
SENSE flush le buffer vers le disque mais avec useAsync=true + cancellation
en cours, ce flush peut ĂȘtre partiel ou Ă©chouer silencieusement. RĂ©sultat :
le compteur dit "X bytes écrits" mais le disque en a moins. Au resume,
segStart = seg.Start + X évite donc les bytes perdus, ces régions
restent en zéros sparse (pré-alloc), et le SHA final fail.
Fix : pattern checkpoint. Le compteur seg.DownloadedBytes n'est mis Ă
jour qu'APRĂS un FlushAsync(CancellationToken.None) rĂ©ussi. Le flush
est forcé non-cancelable pour éviter qu'une pause l'interrompe en
plein milieu. Granularité : tous les 64 MiB de download par segment.
Invariant garanti aprĂšs le fix :
seg.DownloadedBytes <= bytes_réellement_sur_disque
Au pire, en cas de pause, seg.DownloadedBytes est UN PEU en arriĂšre de
ce qui est sur disque (jusqu'Ă 64 MiB par segment). Au resume, on
re-tĂ©lĂ©charge ces bytes â ils sont Ă©crasĂ©s avec le mĂȘme contenu, le
SHA final passe. Le cas dangereux ("compteur en avance, trou de zéros
sur disque") est définitivement impossible.
Bonus : initialisation de aggregateBytes depuis sum(seg.DownloadedBytes)
au lieu de state.DownloadedBytes pour éviter un drift cosmétique du
footer aprĂšs resume (state.DownloadedBytes capturait la live aggregate
incluant l'in-flight, donc pouvait dépasser le total durable).
Trade-off : un peu plus de re-download au resume (jusqu'Ă 1 GB pour
16 segments à 64 MiB) en échange d'une fiabilité totale. Le user ne
verra pas la différence sur un 14 GB de DL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-14 17:25:51 +02:00
efb53f079e
v0.28.10 â Mode auto + settings lock par license + fix post-install state
...
Mode auto (auto-launch d'une version désignée au démarrage et aprÚs exit) :
- Master toggle + version désignée via bouton AUTO (orange) sur chaque row
- Countdown 5 s configurable avec popup d'annulation (StartAutoLaunchCountdown)
- Args CLI passés à PROSERVE : opérateur tape juste le nom (autoconnect, login),
le launcher ajoute -/=/quotes. Format produit identique Ă un .bat.
Fix : passage de ArgumentList à Arguments string pour éviter le quotage auto
.NET sur les args contenant '=' (cassait FParse::Value).
- Garde-fou anti double-lancement : refuse de lancer si _runningProserve vivant
OU IProcessLauncher.IsRunning() détecte une instance externe. Silent pour
les triggers auto, popup pour les clics manuels.
- Option « Attendre santé systÚme » : countdown différé tant que les health
checks ne sont pas tous OK (phase 1 du dialog avec liste des pending).
Settings lock par license (remplace l'ancien lock global manifest) :
- Colonne settings_lock_password_hash sur table licenses (migration 003)
- Backoffice : bouton đ par license pour set/clear hash SHA-256
- Payload /license/validate inclut settingsLockPasswordHash (v3 canonique)
- 3-tier fallback signature : v3 â v2 â legacy pour compat cache offline
- SettingsLockService : in-memory unlock state, gate l'expander Avancés
- SettingsLockDialog : prompt mdp, persiste déverrouillé jusqu'au restart
Fix post-install : la row restait en visuel « Installing » jusqu'au prochain
Check Updates. Reset explicite row.State=InstalledIdle + _activeRow=null
AVANT le RebuildList post-install pour purger l'instance orpheline.
Migrations :
- 002_channel_betas.sql réécrit en ALTER simples (DELIMITER cassait
migrate.php qui split sur ';\n')
- migrate.php tolÚre « Duplicate column/key name » comme idempotent
Strings (5 langues, ~25 nouvelles) :
- Renomme « Relance automatique » â « Lancement automatique » (dialog
utilisé pour les 3 entry points : clic, startup, post-exit)
- Tooltips auto-mode, settings lock prompts, health wait phase, etc.
Bumps : 0.27.4 â 0.28.10 (csproj + .iss).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-12 16:56:55 +02:00
9b17dfa84c
v0.27.4 â Exe per-version configurable + auto-install redists Unreal
...
== Exe par-version, configurable via le backoffice ==
Pour éviter de devoir bumper le launcher à chaque changement d'Unreal Engine
(PROSERVE_UE_5_5.exe â PROSERVE_UE_5_7.exe), l'exe Ă lancer est maintenant
déclaré par version dans le manifest serveur, configurable depuis le form
admin.
- server/admin/versions.php : nouveau champ "Nom de l'exécutable" dans le
form d'ajout de version (regex strict anti-path-traversal). Par défaut
PROSERVE_UE_5_7.exe.
- InstallationRegistry : nouveau .proserve-meta.json écrit dans chaque dossier
d'install fraßchement extrait (contient l'exe declaré par le manifest).
Scan() lit cette meta pour résoudre l'exe sans avoir besoin du manifest
en mémoire (offline / pré-check).
- Fallback glob PROSERVE_UE_*.exe pour les vieux installs sans metadata â
garantit la backward compat de l'UE 5.5 actuel sans intervention.
== Auto-install des redists Unreal depuis _redist/ ==
Quand une version change d'Unreal (typique : UE 5.5 â 5.7), les redists
Microsoft VC + UEPrereqSetup doivent ĂȘtre installĂ©s sur le PC. Au lieu de
demander à l'opérateur de les installer à la main, le launcher détecte
maintenant un dossier _redist/ dans la racine du ZIP de release et lance
automatiquement tous les .exe dedans.
- MainViewModel.InstallRedistsAsync : aprĂšs le ZipInstaller, scan _redist/
pour .exe, lance chacun avec /install /quiet /norestart + Verb=runas
(UAC popup par installer, inévitable car les redists écrivent dans
Program Files). Tri alphabétique (préfixe 01_, 02_, ⊠pour forcer un
ordre si besoin).
- InstallationRegistry.MarkRedistInstalledAsync : trace redistInstalledAt
dans .proserve-meta.json aprĂšs succĂšs. Reinstall de la mĂȘme version :
skip silencieux, pas de UAC popup chain.
- Best-effort sur les exit codes : les "already installed" retournent
souvent un code non-zero, on log mais on continue (l'install PROSERVE
ne doit pas ĂȘtre bloquĂ©e par un redist mineur).
CÎté ZIP de release : crée _redist/ à la racine avec les .exe à installer
(typiquement VC_redist.x64.exe + UEPrereqSetup_x64.exe).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-11 15:11:57 +02:00
d9c3f09e9a
v0.27.2 â Persiste channel + canSeeBetas dans le cache license
...
Bug : le LicenseValidationResponse réhydraté par GetCached() ne contenait
plus les champs Channel et CanSeeBetas (introduits en v0.26.0). Cause :
LocalConfig.LicenseConfig ne persistait pas ces deux champs, donc Ă chaque
relance du launcher (ou simple GetCached), Channel retombait Ă null et
CanSeeBetas Ă false.
Conséquence visible : un client avec license channel='firefighter'
cÎté serveur fetchait toujours /api/manifest (sans ?channel=), recevait
le manifest default-only, et ne voyait jamais le build firefighter mĂȘme
aprĂšs re-validation de license.
Fix :
- Ajout des champs CachedChannel + CachedCanSeeBetas sur LicenseConfig
(persistés dans %LocalAppData%\PSLauncher\config.json)
- SaveCached() écrit ces champs depuis la réponse signée du serveur
- GetCached() les restaure dans la LicenseValidationResponse renvoyée
Migration silencieuse : un launcher pre-v0.27.2 a CachedChannel=null
et CachedCanSeeBetas=false dans son config.json. Au prochain Vérifier
les MAJ, RefreshLicenseFromServerAsync re-valide la license, le serveur
renvoie les valeurs courantes, SaveCached les persiste. La fois d'aprĂšs
le launcher fetch correctement avec ?channel=X.
Ă tester chez l'utilisateur :
1. Recompiler + réinstaller le launcher en v0.27.2
2. Settings â VĂ©rifier les MAJ (force la revalidation license)
3. Vérifier %LocalAppData%\PSLauncher\config.json :
"CachedChannel": "firefighter" doit apparaĂźtre
4. Le manifest-cache.json devrait alors contenir le bon ZIP
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 09:37:19 +02:00
72f99e0c56
v0.27.1 â Versions : permettre numĂ©ro identique sur channels diffĂ©rents
...
Cas d'usage rapporté : « je peux avoir une version 1.5.3 pour les
pompiers et une version 1.5.3 pour la police ». Le check anti-doublon
sur le numĂ©ro de version bloquait la crĂ©ation â c'Ă©tait un faux check
puisque la duplication SUR DES CHANNELS DIFFĂRENTS est lĂ©gitime.
Changements :
1. ID stable interne : chaque entry de version reçoit un `id` immutable
généré à la création (`v` + 8 hex aléatoires). Ce id devient l'identifiant
primaire dans les forms et URLs admin. Le numéro de version reste un
libellé human-readable, possiblement dupliqué.
2. Migration douce : loadManifest() ajoute un id aux entrées qui n'en
ont pas, et auto-save pour persister. Stable aux reloads suivants.
Les manifests legacy fonctionnent sans aucune action manuelle.
3. Check anti-doublon déplacé sur le NOM DE ZIP au lieu du numéro de
version. Deux entries v1.5.3 sont autorisées si elles pointent vers
des ZIPs différents (proserve-1.5.3-police.zip + proserve-1.5.3-pompier.zip).
Une vraie ambiguĂŻtĂ© sur disque (deux entries â mĂȘme ZIP) est rejetĂ©e.
4. Toutes les actions row-level (edit_meta, edit_notes, set_beta,
set_channels, toggle_available, delete, set_skip_hash, sync_one)
identifient l'entry par `id` au lieu de `version`. Forms HTML mis
à jour en conséquence.
5. Release notes : addressables par id stable. Stockées en
releasenotes/{id}.md. Compat ascendante : si un fichier {id}.md
n'existe pas, l'admin lit le legacy {version}.md (pour les notes
créées avant v0.27.1). L'endpoint API releasenotes/{key} accepte
les deux formats.
6. Suppression d'une entry : nettoie le fichier {id}.md correspondant.
7. SignManifest::run filtre toujours par version string : si plusieurs
entries partagent un numéro de version, toutes sont re-hashées via
« đ Hash » sur une row. Comportement correct (chaque entry a sa
propre URL/ZIP, donc son hash spécifique), juste moins efficient.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 09:02:00 +02:00
49a3b855af
v0.27.0 â Channels comme TAGS sur versions (refactor du modĂšle)
...
Changement majeur du modÚle de channels en réponse au feedback :
« c'est trop compliqué. Il faudrait juste pouvoir tout laisser dans
builds, et taguer une version dans un canal ou un autre voir plusieurs
canaux. ». Le précédent design (1 fichier manifest par channel +
sous-dossier builds/{channel}/) imposait une duplication de structure
et empĂȘchait une version d'ĂȘtre visible sur plusieurs channels Ă la fois.
NOUVEAU MODĂLE :
- 1 seul fichier manifest/versions.json
- 1 seul dossier builds/ flat (les ZIPs ont des noms libres pour éviter
les collisions homonymes â proserve-1.5.3.zip, proserve-1.5.3-police.zipâŠ)
- Chaque entry de version a un champ `channels: ["default", "police"]`
qui dit qui peut la voir
- Sémantique additive : un client sur channel "police" voit les versions
taggées "default" + celles taggées "police". "default" = public.
- Une version peut ĂȘtre taggĂ©e sur plusieurs channels en une fois.
NOUVEAU REGISTRE channels.json :
Le registre des channels (avec name + label + description) vit dans
manifest/channels.json. Le channel "default" est implicite et ne peut
pas ĂȘtre supprimĂ©. Nouvelle page admin Channels (CRUD) pour gĂ©rer la
liste, avec garde-fou anti-suppression : compte les versions taggées
et les licenses attribuées avant d'autoriser la suppression.
SERVEUR :
- api/routes/Manifest.php : reçoit ?channel=X depuis la license,
filtre versions oĂč channels[] contient X ou "default", re-signe avec
la clĂ© privĂ©e Ed25519 Ă la volĂ©e. CoĂ»t ~1ms par requĂȘte.
- admin/versions.php : refactor â plus de session "channel actif", plus
de switcher en haut, plus de prefix builds/{channel}/. Add form a
des checkboxes channels[]. Nouveau bouton "Channels" par-row pour
retager.
- admin/licenses.php : dropdown channel alimenté depuis channels.json
au lieu de scanner les fichiers manifest.
- tools/SignManifest.php : revert du constructeur channel-aware,
toujours sur versions.json + builds/ flat.
CLIENT :
- VersionManifest.Channels (List<string>) ajouté pour debug, mais le
filtrage est server-side donc le client n'a rien Ă faire de ce champ
cÎté UX.
- L'envoi du ?channel=X depuis la license signĂ©e fonctionne dĂ©jĂ
depuis v0.26.0, pas de changement nécessaire.
MIGRATION :
Les fichiers manifest/versions-{X}.json créés en v0.26.0 deviennent
inutiles. Si tu en avais (test "police" par exemple), copie les
versions concernées dans versions.json et tague-les avec les bons
channels via la nouvelle UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 08:51:10 +02:00
c371a79f93
v0.26.0 â Channels per-license + tag BĂTA sur versions
...
DEUX features liées :
1. CHANNELS : chaque license peut ĂȘtre attribuĂ©e Ă un manifest distinct
(« channel »). Permet de servir des versions différentes selon le
client. Le serveur lit ?channel=X et sert manifest/versions-{X}.json
avec fallback transparent sur versions.json. NULL = default.
2. BĂTA : nouveau flag isBeta + betaNotes par version dans le manifest.
Visible uniquement par les licenses avec can_see_betas=1. Affichage
d'une pill orange « BĂTA » sur la row + tooltip avec les notes pour
les testeurs. Les installations locales déjà présentes restent
visibles mĂȘme si l'accĂšs BĂTA est retirĂ© ensuite (on n'efface pas
le disque du client).
DB :
002_channel_betas.sql ajoute channel + can_see_betas sur licenses.
Idempotent (ALTER TABLE IF NOT EXISTS), zero data migration.
Serveur PHP :
- ValidateLicense.php signe channel + canSeeBetas dans la réponse
(ordre des clés CRITIQUE pour matcher le canonical client).
- Manifest.php : whitelist regex anti-traversal sur ?channel=, fallback
silencieux sur versions.json si channel inconnu (évite leak de la
liste de channels par probing).
- SignManifest.php prend un channel optionnel â l'admin peut signer
chaque manifest indépendamment.
- admin/licenses.php : dropdown channel + checkbox bĂȘta sur create,
bouton détails repliable par-row pour edit.
- admin/versions.php : channel switcher en tĂȘte, badge BĂTA sur chaque
row, dialog repliable « BĂȘta » avec checkbox + notes des testeurs.
Client C# :
- License.Channel + License.CanSeeBetas (dans le canonical signé).
- VersionManifest.IsBeta + BetaNotes.
- ManifestService prend un channelProvider via DI, lu depuis license
cachée à chaque fetch (lazy, pas de circular dep).
- MainViewModel.RebuildList filtre les versions IsBeta si !CanSeeBetas
(mais conserve les installĂ©es locales â on ne retire pas l'accĂšs
rétroactivement à ce qui est déjà sur disque).
- VersionRowViewModel : props IsBeta / BetaNotes / BetaTooltip.
- MainWindow.xaml : pill orange à cÎté du n° version pour le featured
et les rows compactes, tooltip dynamique avec les notes testeurs.
Backward compat signature :
Anciennes licenses cachées (signées sans channel/canSeeBetas) sont
toujours validées via un fallback canonical legacy dans VerifySignature.
Sans ce fallback, le passage Ă v0.26 invaliderait toutes les caches
hors-ligne et bloquerait les users en mobilité.
Migration cÎté admin : jouer 002_channel_betas.sql sur la base, déployer
les fichiers PHP, créer manifest/versions-{channel}.json pour les
nouveaux channels (l'admin versions.php propose un input « Créer/utiliser
un nouveau channel »). Les licenses existantes restent en channel=NULL
= default = comportement actuel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 07:38:00 +02:00
3d691c3e38
v0.25.10 â LAN P2P offline-friendly : manifest fallback + perf
...
Fonctionne entiÚrement offline si un PC du LAN a fetché OVH récemment :
les autres PCs récupÚrent le manifest + les ZIPs depuis lui sans Internet.
Discovery des peers quasi-instantanée au cold start, fail-fast partout sur
les fetch OVH pour ne pas bloquer la UI.
== Manifest fallback offline ==
- IManifestCache + ManifestCache (Core/Manifests/) : cache disque du dernier
manifest réussi (JSON brut byte-for-byte pour préserver la signature Ed25519).
Path : %LocalAppData%\PSLauncher\manifest-cache.json
- IPeerManifestFetcher + PeerManifestFetcher (Core/Lan/) : récupÚre le
manifest brut depuis un peer LAN via GET /v1/manifest. Probe parallĂšle
avec timeout court par peer.
- LanCacheServer : nouvelle route GET /v1/manifest qui sert le cache disque
byte-for-byte aux clients sans Internet.
- ManifestService.FetchAsync : online check d'abord (ping ICMP 8.8.8.8 +
fallback TCP connect au serveur OVH si firewall bloque ICMP, timeout
800ms Ă 2). Si online â OVH only (canonique, voit toutes les versions).
Si offline â peer LAN puis cache disque local.
- ManifestSource enum exposĂ© via IManifestService.LastSource â call sites
peuvent prendre des décisions éclairées (skip release notes, badge UI).
== Discovery active des peers (cold start instantané) ==
- LanCacheServer : nouvelle route GET /v1/info qui retourne {host, port,
versions} (mĂȘme payload que les beacons UDP).
- LanDiscoveryService : bootstrap au démarrage qui probe en parallÚle tous
les peers connus (cache disque) + manuels (config) via /v1/info, timeout
1s par peer. Les peers qui répondent sont injectés dans _peers avec
lastSeen=now â apparaissent immĂ©diatement dans ListDiscovered() et donc
dans la sidebar + PeerSourceResolver. Plus besoin d'attendre les 15s du
prochain beacon UDP.
- ILanDiscoveryService.ListKnown() exposé pour que les fetchers puissent
utiliser le cache disque comme seed.
- LocalConfig.LanCache.ManualPeerUrls : default ["http://10.0.4.100:47623 "]
(IP fixe du serveur ASTERION dans toutes les salles VR).
== Timeouts courts sur tous les fetch OVH (fail-fast offline) ==
- ManifestService : 3s pour le manifest fetch (vs 100s par défaut)
- LicenseService.ValidateAsync : 3s (résolvait le délai de ~15s pendant
CheckForUpdates en offline â HttpClient global a Timeout=Infinite,
sans CTS court ça hangait jusqu'à l'abandon SYN TCP par l'OS)
- LicenseService.GetSignedDownloadUrlAsync : 5s
- ManifestService.FetchReleaseNotesAsync : 5s
== UI ==
- StatusMessage post-Check Updates suffixé avec la source du manifest :
"đ OVH (en ligne)" / "đĄ Peer LAN (hors ligne)" / "đŸ Cache local (hors ligne)"
- InstallVersionAsync : skip le fetch release notes si manifest source
!= OVH â dialog s'ouvre instantanĂ©ment avec "indisponible" au lieu
d'attendre 5s de timeout.
== Latences attendues ==
| Scénario | Avant | AprÚs |
|---|---|---|
| Vérifier MAJ online | ~5s | ~500ms |
| Vérifier MAJ offline | ~15-20s | ~5s |
| Cold start discovery (1 peer connu) | jusqu'Ă 15s | ~200ms |
| Click Install offline | ~5s release notes timeout | instantané |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 17:51:50 +02:00
65ff8541b7
v0.25.4 â Cache LAN P2P : un PC sert les ZIPs aux autres du LAN
...
Feature : sur un site multi-PCs (salle VR), un PC peut servir les ZIPs PROSERVE
qu'il a déjà téléchargés aux autres PCs du LAN au lieu de les forcer à re-DL
14 Go depuis OVH. Auto-discovery par UDP broadcast, fallback transparent OVH
si aucun peer ne répond, intégrité préservée par SHA-256 du manifest signé Ed25519.
Architecture (src/PSLauncher.Core/Lan/) :
- ZipCacheStore : cache local des ZIPs téléchargés (sidecar SHA-256 pour servir
sans re-hash). Pruning auto au top-N par mtime, protĂšge les versions encore
installées. Remplace le `File.Delete(zipPath)` post-install historique.
- LanCacheServer : serveur HTTP local sur TcpListener (PAS HttpListener â il
exigeait une URL ACL admin qui aurait obligé à coder un netsh dans l'installer).
Routes GET /v1/has/{ver} (probe) + GET /v1/zip/{ver} (Range support manuel,
~80 LOC de parsing HTTP). Filtre RFC1918/link-local sur RemoteEndPoint avant
toute IO, regex stricte sur version (anti-path-traversal), SemaphoreSlim(4)
pour rate-limiter Ă 4 DL concurrents.
- LanDiscoveryService : émet beacon UDP (255.255.255.255:47624, JSON avec host+
port+versions) toutes les 15 s en mode serveur ; écoute en mode client/serveur
avec TTL 60 s sur les peers. Pure .NET, zero NuGet (pas de mDNS deps).
- PeerSourceResolver : avant chaque DL, probe les peers (auto-découverts +
manuels) avec timeout 1 s à max 5 peers. Vérifie size + SHA-256 contre le
manifest avant de retourner une URL peer. Si null â fallback OVH transparent.
Modifs flow d'install (MainViewModel) :
- Avant GetSignedDownloadUrlAsync, tente PeerSourceResolver. Si peer trouvé,
utilise son URL directement (le DownloadManager multi-segment marche tel quel).
- RefreshUrlAsync handler : pour les peers, retourne la mĂȘme URL (pas d'expiration
HMAC). Pour OVH, refresh du HMAC inchangé.
- Post-install : remplace File.Delete(zipPath) par ZipCacheStore.Promote + Prune.
Sidebar info (visible en permanence dans le coin bas-gauche) :
- Lignes "Serveur ON/KO/â", "Client ON/â", "Peers N" â refresh auto toutes les
10 s via DispatcherTimer. Diagnostic instantané sans naviguer dans Settings.
Footer pendant le DL : prĂ©fixe diffĂ©renciĂ© pour rendre la source Ă©vidente â
"đĄ LAN 10.0.0.5 âą v⊠: ⊠Mo/s" vs "⏠v⊠: ⊠Mo/s" (OVH).
Settings â Cache LAN P2P : checkboxes ServerEnabled/ClientEnabled, ports HTTP/UDP,
MaxCachedVersions, statut serveur live, liste peers découverts, champ multi-line
peers manuels (override/fallback de l'auto-discovery). Toggle des flags â
prompt "Restart launcher requis" car les hosted services lisent leur config au
boot uniquement.
Fix critique au passage : App.OnStartup appelait .Build() mais JAMAIS .StartAsync()
sur l'IHost â du coup AUCUN HostedService n'a jamais tournĂ© depuis l'introduction
du pattern. Ajout de StartAsync (fire-and-forget avec log d'erreur) et StopAsync
propre dans OnExit (timeout 5 s pour libérer les sockets sans TIME_WAIT bloquant).
Installer (PSLauncher.iss) : rÚgles firewall TCP 47623 + UDP 47624 préposées
via netsh advfirewall, profile=private,domain (PAS public â safety net mĂȘme
si l'utilisateur connecte le PC Ă un Wi-Fi public). Inutile si firewall OFF
(cas du déploiement actuel) mais pose les rails pour les déploiements futurs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 16:00:10 +02:00
7de0c01b97
v0.24.9 â Deploy _api/, fix WebView2 Program Files, DL robustness
...
Deploy auto de l'API PHP de stats (parallĂšle Report/Doc) :
- ApiToolDeployer : copy _api/ â C:\xampp\htdocs\proserve\ avec atomic
rename + backups horodatés + revert + pruning. Strict clone du pattern
DocToolDeployer.
- ApiToolConfig dans LocalConfig (default folder "proserve" en minuscule
pour matcher l'install standard PROSERVE cÎté client).
- Settings â AvancĂ©s : section dĂ©diĂ©e (HtdocsRoot, FolderName, AutoDeploy,
MaxBackups, Re-déployer, liste backups + revert) + ApiBackupViewModel.
- DI registration dans App.xaml.cs, injection MainViewModel, appel
post-install aprĂšs le bloc Doc.
- 8 nouvelles strings i18n (5 langues).
Fix WebView2 vide en Program Files :
- Set WEBVIEW2_USER_DATA_FOLDER vers %LocalAppData%\PSLauncher\WebView2\
dans App.OnStartup. Sans ça, WebView2 essaie d'écrire son cache à cÎté
du .exe (read-only en Program Files) â init silencieusement KO â
onglets Report/Doc vides.
Refresh auto WebView2 sur navigation d'onglet :
- Hook IsVisibleChanged sur ReportWebView et DocsWebView. Ă chaque
rĂ©-affichage, CoreWebView2.Reload() pour voir les donnĂ©es serveur Ă
jour. Le binding Source garde la nav initiale au cold start.
Fix UI : Cancel décalant le footer pendant Check updates :
- Nouveau flag IsDownloadActive distinct de IsBusy. Visibility du bouton
Cancel passe de IsBusy â IsDownloadActive : couvre uniquement les DL,
plus aucune apparition pendant Check updates / Install / Uninstall /
Self-update qui décalait tout le layout 1 s.
DL robustness (multi-PC) :
- SafeInstallAsync : error boundary sur l'install handler. Plus aucune
exception silencieuse â log + popup d'erreur visible (rĂ©sout les cas
"le 2e PC ne démarre pas, sans message").
- DownloadManager : vérif explicite que CHAQUE segment a Completed=true
&& DownloadedBytes >= Length AVANT le SHA-256. Le check actualSize
était inutile (fichier sparse-préalloué).
- MaxRetryAttempts Polly 6 â 10 pour absorber les outages PHP-FPM
request_terminate_timeout sous charge multi-PC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 13:53:13 +02:00
9de8e95910
v0.24.5 â Install Program Files, multi-PC DL reliability, polish UI
...
Installeur :
- Bascule en Program Files (autopf) avec PrivilegesRequired=admin.
L'auto-update reste fonctionnel : LauncherSelfUpdater détecte le dossier
protégé et lance PS_Launcher.Updater.exe via Verb=runas (UAC à chaque MAJ).
- build-installer.ps1 : détection ISCC.exe robuste (Program Files + LocalAppData
pour install winget user-mode + PATH), messages d'erreur explicites avec URL
et commande winget, suppression accents (compat PS 5.1 sans BOM).
Téléchargements multi-PC :
- DownloadManager : détection des connexions fermées prématurément (PHP-FPM
request_terminate_timeout). Sans ça, segment marqué Completed=false sans
exception â fichier sparse final avec trous â SHA-256 KO. Lance maintenant
HttpResumableException(transient) pour que Polly retry au bon offset.
- ParallelDownloadSegments : 16 â 6 par dĂ©faut pour permettre plusieurs PCs
simultanés sans saturer le pool PHP-FPM OVH (~30 workers).
UI :
- FenĂȘtre maximisĂ©e par dĂ©faut au dĂ©marrage (WindowState=Maximized).
- Fix maximize qui cachait le footer / barre de DL (WM_GETMINMAXINFO clamp
sur work area, remplace le margin hack 7px imprécis).
- Sidebar : bloc info en bas avec PS_Launcher vX.Y.Z + IPv4 locale alignés.
- Copyright remontĂ© plus prĂšs du footer (margin 28 â 8).
- Settings â Health checks : boutons âČ/⌠pour rĂ©ordonner, CanExecute auto.
- HealthCheck refresh dĂ©faut : 5000 â 2000 ms.
Bug fix UI :
- MainViewModel.RebuildList preserve l'état du row actif pendant un DL.
Sinon ouvrir Settings/License pendant un DL recréait les rows from scratch
â UI affichait "Reprendre" + "Annuler" alors que le DL tournait. Les
progress callbacks pointent maintenant sur _activeRow (résolution dynamique)
au lieu de capturer le row local.
Defaults config :
- ServerBaseUrl : example.com â asterionvr.com (out-of-the-box).
- Vive Business Streaming check : HtcConnectionUtility â rrserver.
Backoffice (server/admin/licenses.php) :
- Action set_max_machines : bouton "Slots" pour ajuster max_machines Ă chaud
sur une licence existante. Refus de descendre sous le nombre de machines
déjà actives.
Build :
- AllowUnsafeBlocks=true sur PSLauncher.App.csproj (compat WinRT generator
récent qui émet du code unsafe dans WinRTGenericInstantiation.g.cs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 12:04:36 +02:00
be7ccfced5
v0.23.6 â Fix : footer (barre de DL) invisible en plein Ă©cran
...
Bug WPF classique avec WindowStyle=None + WindowChrome : quand la fenĂȘtre
passe en Maximized, elle déborde physiquement de la taille de
ResizeBorderThickness sur chaque bord (~7 px ici). Conséquence : la Row 3
(footer) passe sous la barre des tĂąches Windows, et la barre de
téléchargement devient invisible alors que tout le reste continue de
fonctionner.
Fix : on applique un Margin=7 sur le Grid root quand WindowState=Maximized
via un DataTrigger sur le RelativeSource Window. Le trigger se déclenche
automatiquement Ă chaque maximize/restore â pas de code-behind nĂ©cessaire.
Test : maximize + minimize + restore plusieurs fois â le footer reste
toujours visible et la fenĂȘtre revient Ă sa taille normale sans glitch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 21:06:54 +02:00
bebb701d70
v0.23.5 â Retire le check VR du default config
...
Vive Streaming ment à SteamVR : son driver prétend qu'un HMD est présent
mĂȘme quand la session Wi-Fi avec le casque physique est tombĂ©e. Donc
tous les signaux qui passent par SteamVR (VR_IsHmdPresent, vtable
IVRSystem, logs vrserver) sont contaminés et ne reflÚtent pas l'état
réel du casque. Le check qu'on a ajouté en v0.23.4 induisait l'utilisateur
en erreur ("vert" alors que le casque est éteint).
Pour l'instant on retire le check du default. Bookmark dans le code des
pistes Phase 2 qui ne passent PAS par SteamVR : connexions TCP/UDP de
HtcConnectionUtility via GetExtendedTcpTable, endpoint local de Vive
Console, compteurs IO, ARP avec OUIs HTC, logs Vive directs.
Le Kind « VrDevice » reste dispo dans l'éditeur pour les setups SteamVR
purs (Index/Vive Wand sur Lighthouse) oĂč le signal est honnĂȘte, mais
n'est plus poussé par défaut.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 20:45:55 +02:00
37a4cf424e
v0.23.4 â Default config : check VR par dĂ©faut en VrDevice (out-of-the-box)
...
Avant : check « Casque VR » par dĂ©faut en Ping avec Target vide (Ă
remplir par l'utilisateur avec l'IP du casque). Pour Vive Streaming
ça nécessite de connaßtre l'IP du casque cÎté Wi-Fi, info pas toujours
exposĂ©e par Vive Business Streaming â check dĂ©sactivĂ© chez 90% des users.
AprÚs : check VrDevice avec Target « hmd ». Marche sans aucune config :
détecte SteamVR + HMD via les flat C OpenVR (VR_IsRuntimeInstalled +
VR_IsHmdPresent + vrserver process check). Pas de SHM/IPC donc pas de
risque AccessViolation comme avec la vtable.
Avantage en plus : répond beaucoup mieux à la question qui compte
(« PROSERVE peut-il se lancer ? ») qu'un ping sur l'IP du casque, qui
indiquait juste la connectivité réseau.
Les utilisateurs déjà installés en v0.23.x gardent leur config existante
(le launcher ne réécrase pas un config.json déjà présent) ; ils peuvent
remplacer leur Ping par un VrDevice via Settings â Bandeau de santĂ©.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 20:32:06 +02:00
7cb2b01403
v0.23.3 â Mode safe : OpenVR sans vtable (Phase 1 stripped down)
...
Init OpenVR + binding de la vtable IVRSystem_022 fonctionne (confirmé par
les logs : "OpenVR state: Ready, vtable IVRSystem_022 bindée"), mais le
PREMIER appel vtable crashe en AccessViolation natif. Pas une race au
boot â ça plante mĂȘme 8s aprĂšs que vrserver est apparu, et probablement
Ă n'importe quel moment de la session SteamVR.
Diagnostic : les fonctions de la vtable (IsTrackedDeviceConnected,
GetBoolTrackedDeviceProperty, etc.) accĂšdent Ă des shared memories avec
vrserver qui peuvent ĂȘtre (re)mappĂ©es dynamiquement quand un device se
connecte/déconnecte. L'AccessViolation natif passe au travers du CLR
via Marshal.GetDelegateForFunctionPointer â non rattrapable par
catch (Exception) ni catch (AccessViolationException).
Fix : on retire TOUS les appels vtable. OpenVrService.Query utilise
maintenant uniquement l'API flat C qui ne fait pas de SHM/IPC :
- VR_IsRuntimeInstalled() â lookup statique dans la DLL
- VR_IsHmdPresent() â consulte un mutex/event nommĂ©, pas de SHM
- GetProcessesByName("vrserver") â process check Windows
Résultat : check VR robuste mais réduit à "SteamVR actif + HMD présent
oui/non". Plus de batterie, plus d'info per-device, le device picker du
dialog devient cosmĂ©tique (tous les aliases retournent la mĂȘme rĂ©ponse).
La batterie + l'info per-device reviendra en Phase 1.5 via une approche
sub-process : un PSLauncher.VrProbe.exe séparé qui fait les vtable calls
et communique par stdout JSON. Si VrProbe crashe en AccessViolation, le
launcher voit juste "exit code != 0" et report "VR query failed" sans
ĂȘtre affectĂ© lui-mĂȘme.
Le code de la vtable (delegates, BindVTableDelegates, Init/Shutdown) reste
en place dans OpenVrService.cs comme rĂ©fĂ©rence pour Phase 1.5 â n'est
juste plus appelé depuis Query.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 20:16:16 +02:00
07ead6011f
v0.23.2 â Fix AccessViolation pendant le dĂ©marrage de SteamVR
...
Cause : race condition. Quand l'utilisateur lance SteamVR alors que le
launcher tourne déjà , vrserver.exe apparaßt dans la liste des process
avant que ses drivers et ses shared memories soient prĂȘts Ă servir des
requĂȘtes vtable. La sĂ©quence problĂ©matique :
1. tick health loop â IsVrServerRunning() = true (vrserver vient juste
d'apparaĂźtre dans la process list)
2. VR_InitInternal2 â succeed (init est tolĂ©rant, ne lit pas la SHM)
3. VR_GetGenericInterface â vtable pointer valide
4. premier appel vtable (IsTrackedDeviceConnected, slot 21) â lit dans
une shared memory pas encore allouĂ©e par vrserver â AccessViolation
5. SEH passe au travers du CLR via Marshal.GetDelegateForFunctionPointer
â process killed sans que catch (Exception) ne se dĂ©clenche.
Stack confirmée par l'Event Viewer Windows :
System.AccessViolationException
at OpenVrService.Query(System.String)
at SystemHealthService.CheckVrDevice(...)
at health loop background task
Fix : cooldown de 8 s aprÚs la premiÚre détection de vrserver. On note
le timestamp UTC de la premiĂšre apparition, et on refuse l'init tant que
moins de 8 s n'ont passé. Pendant le warmup, la pill reste sur "SteamVR
non lancé" (HmdAbsent), puis bascule sur Ready au tick suivant. Si
vrserver disparaĂźt, on reset le timestamp + on tear-down la session
proprement, prĂȘts pour un prochain cycle de dĂ©marrage.
8 s = compromis : sur SSD le ramp-up des drivers SteamVR prend ~3-5 s,
sur HDD ça peut monter à 7 s. La marge couvre les deux.
Bonus diag : flush Serilog avant que le process meure dans le hook
AppDomain.UnhandledException. Sans ça, la trace de la fatale n'arrivait
jamais sur disque (exactement ce qu'on a vu dans les logs : silence
total avant le redémarrage suivant).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 20:08:32 +02:00
c997859750
v0.23.1 â Fix crash OpenVR quand SteamVR non lancĂ©
...
Deux bugs corrigés :
1. Marshalling bool incorrect sur VR_IsHmdPresent / VR_IsRuntimeInstalled.
OpenVR renvoie un bool C++ (1 octet) ; sans MarshalAs(I1) le marshaller
.NET lit 4 octets (Win32 BOOL) et obtient des valeurs random. Conséquence :
VR_IsHmdPresent passait parfois Ă "true" mĂȘme si aucun casque n'Ă©tait
dĂ©tectĂ©, et on tentait alors une init OpenVR sans backend â crash.
2. Garde process avant init. MĂȘme avec le bool marshalling correct,
VR_IsHmdPresent peut renvoyer true si un HMD est branché en USB sans
que SteamVR soit lancé. VR_InitInternal2 en mode Background tente alors
de charger les drivers (lighthouse, oculus_linkâŠ) qui peuvent crash en
SEH si vrserver n'a jamais été démarré sur cette session. On ajoute
une vérification de la présence de vrserver.exe avant de tenter l'init,
avec cache 2 s pour ne pas faire un GetProcessesByName Ă chaque tick.
RĂ©sultat : si SteamVR est installĂ© mais pas lancĂ© â pill "SteamVR pas lancĂ©"
proprement, sans tenter d'init et donc sans risque de crash.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 19:38:35 +02:00
4d0e287227
v0.23.0 â SteamVR / OpenVR : check VrDevice avec batterie + tracking
...
Nouveau Kind « VrDevice » dans le bandeau de santé. Pour chaque appareil
SteamVR (HMD, manettes, trackers, base stations) on lit via OpenVR :
batterie %, état de charge, niveau d'activité (actif / veille / inactif)
et serial number. Severity du pill calculée sur le batterie : < 15% rouge,
< 30% orange, sinon vert ; charging force le vert.
ImplĂ©mentation : P/Invoke direct dans openvr_api.dll (BSD-3) â pas de
NuGet, pas de bundling de la DLL. La runtime est résolue dynamiquement
via DllImportResolver depuis :
1. %LocalAppData%\openvr\openvrpaths.vrpath (canon écrit par SteamVR),
2. fallback C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64\.
Si SteamVR n'est pas installĂ© â pill « SteamVR non installĂ© » (Unknown,
gris). Si SteamVR pas lancĂ© â pill « SteamVR non lancĂ© / aucun HMD »
(Error, rouge). Init en mode VRApplication_Background, donc on lit l'état
sans devenir scene application ni monopoliser le compositor.
Ăditeur : nouveau radio « Appareil SteamVR » Ă cĂŽtĂ© de Process / Ping ;
quand il est sélectionné, le champ Target devient une ComboBox d'alias
(HMD, Manette gauche/droite, Tracker 1-3, Base station 1-2) au lieu du
TextBox libre. Le mapping alias â TrackedDeviceIndex se fait Ă chaque
query (controllers via GetTrackedDeviceIndexForControllerRole, trackers
et base stations par énumération avec compteur de classe).
Robustesse : init lazy une fois pour toute la vie du process (init/shutdown
répétés coûtent 100-500 ms). Vtable IVRSystem_022 résolue par index slot
dans une struct contiguë de IntPtr (pas de struct marshaling, donc immune
aux changements de slots qu'on n'utilise pas). Si SteamVR crash en cours
de session, le service détecte au prochain tick et marque la session
broken pour retenter au tick suivant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 19:29:57 +02:00
211af9dd85
v0.22.0 â Health checks editor : icon picker + per-check refresh
...
Settings â AvancĂ©s â Bandeau de santĂ© : liste Ă©ditable des checks (Add /
Edit / Delete) avec dialog modal pour chaque entry. Picker d'emoji parmi
28 icĂŽnes curatĂ©es (VR / streaming / rĂ©seau / systĂšme). Ădition du nom,
type (Process / Ping), cible, intervalle de refresh en ms (per-check),
et seuils Ping (warn / error / timeout) sous une section Avancé.
La config est persistée dans %LocalAppData%\PSLauncher\config.json donc
elle survit aux auto-updates du launcher. Modifiable aussi Ă la main
dans le json pour les power-users.
Le bandeau hot-reload aprĂšs Save : cancel des boucles de polling
existantes, reconstruction des HealthIndicators depuis la config fraĂźche,
puis relance d'une boucle dédiée par check (chacune cadencée sur son
propre RefreshIntervalMs â Process check rapide peut tourner Ă 1 s,
Ping reste Ă 5-10 s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 19:09:39 +02:00
60358a6cf5
v0.21.0 â System health banner with colored indicators + tooltips
...
New row between top bar and content showing pill-shaped status indicators
for the system dependencies that PROSERVE needs (SteamVR, Vive Business
Streaming process, VR headset reachable on the network, etc.). Each pill
is colored : đą OK, đ limitation dĂ©tectĂ©e (e.g. ping Ă©levĂ©), đŽ KO,
gris = non configurĂ©. Hover â tooltip with full check kind, target,
last RTT/process count and timestamp.
Architecture
- LocalConfig.HealthChecksConfig : list of HealthCheckEntry (Kind=Ping
or Process, Target=IP/host or process name, ping thresholds in ms,
refresh interval in s).
- ISystemHealthService + SystemHealthService : ping via
System.Net.NetworkInformation.Ping (no admin required), process
lookup via System.Diagnostics.Process.GetProcessesByName. Returns
HealthResult { Severity, Detail }.
- HealthIndicatorViewModel : observable wrapper per entry with
Severity-driven Brushes (background pill, border, icon colour) and
composed Tooltip text.
- MainViewModel.InitHealthIndicators + StartHealthLoop : populates
ObservableCollection<HealthIndicatorViewModel> from config and runs
parallel checks every RefreshIntervalSeconds (default 10s).
- MainWindow.xaml : new Row 1 (between top bar and body) hosting an
ItemsControl bound to HealthIndicators. Row collapses cleanly if the
list is empty (HasHealthIndicators=false).
Defaults shipped (all editable in %LocalAppData%\PSLauncher\config.json)
- đź SteamVR (Process : vrserver)
- đĄ Vive Business Streaming (Process : HtcConnectionUtility)
- đ„œ Casque VR (Ping : empty, user fills in the headset IP)
The Settings UI editor for managing the list is intentionally deferred
to a future iteration â config.json edit is enough to start using it.
Versions bumped to 0.21.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 18:44:15 +02:00
30eceaea2c
v0.20.0 â Cancel-from-red-button waits for segments + new Verifying row state
...
Two related UX bugs fixed.
1) Red "Annuler" button on a row : DL appeared to keep going
The button calls RestartFromZeroAsync which used to fire
_activeDownloadCts.Cancel() then sleep 200 ms then delete the .partial.
With 16 parallel segments mid-write, 200 ms is way too short â the
segments were still flushing buffers when the file got deleted, then
recreated it from their own write streams, making it look like the
download "continued".
Now we keep a reference to the in-flight install Task
(_activeInstallTask) and properly await it (with a 10 s safety timeout)
before discarding state. This guarantees all segment FileStreams are
closed and the .partial deletion is the final word.
2) Progress bar said "DownloadingâŠ" during SHA-256 verification
The footer message switched to "đ VĂ©rification SHA-256 vâŠ" but the
row's badge stayed on "⏠DownloadingâŠ" because row.State stayed at
Downloading throughout DownloadAsync (which internally chains DL +
verify). New VersionRowState.Verifying inserted between Downloading
and Installing, applied on the first hashProgress callback. Badge now
reads "đ VĂ©rificationâŠ" / "đ VerifyingâŠ" during the hash phase.
VersionRowViewModel.IsBusy now also includes Verifying so commands
that gate on busy stay disabled during verification.
Versions bumped to 0.20.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 18:34:18 +02:00
3c8438f3fe
v0.19.0 â Bump default parallel segments 8â16 to shorten end-of-DL tail
...
Issue : on a 14 GB download with 8 segments, when 7 finish there's only
1 connection left handling its assigned 1.75 GB â apparent throughput
collapses to 1/8th of the peak for the final stretch (~5 min visible
slowdown at the end).
Quick fix : double the default segment count.
- 8 segments : last has 1.75 GB to finish alone
- 16 segments : last has 875 MB â tail ~halved
Tuning surfaced in Settings â AvancĂ©s â Serveur as
« Connexions parallÚles pour les téléchargements (1-32, défaut 16) ».
Power users on faster servers can push to 24-32. OVH mutualisé tolerates
up to 16-24 ; above that, soft rate-limiting kicks in.
Aligned :
- LocalConfig default : 8 â 16
- DownloadManager clamp : Math.Clamp(_, 1, 16) â 1, 32
- HttpClient handler MaxConnectionsPerServer : 16 â 32 (matches new ceiling)
Versions bumped to 0.19.0.
Existing config.json files will keep their previous value (8) untouched ;
delete config.json to pick up the new default, or just bump it in
Settings â AvancĂ©s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 18:28:35 +02:00
8366ca2669
v0.18.0 â Bluer dark palette to match ASTERION VR identity
...
Shifts the launcher background from pure black to deep navy and adds a
subtle blue tint over the body bitmap so the overall feel is closer to
the brand's blue/cyan accent rather than neutral black-on-grey.
Theme.xaml palette tweaks
- Brush.Bg.Window : #000000 â #0A1220 (deep navy, ~95% black)
- Brush.Bg.Sidebar : #0E1218 â #0E1422
- Brush.Bg.Card : #161B23 â #161D2C
- Brush.Bg.Footer : #050709 â #060A14
- New Brush.Bg.BlueTint : #3050A0 @ 8% opacity, used as overlay
MainWindow.xaml
- Outer Grid Background : hardcoded "Black" â Brush.Bg.Window
(so future tweaks of the brush propagate)
- New Rectangle on top of the body Background.png with the blue tint
brush, IsHitTestVisible=False so it doesn't block clicks.
Versions bumped to 0.18.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 11:30:24 +02:00
e608a5d29d
v0.17.0 â Doc tool auto-deploy + revert (mirror of Report)
...
Adds the same deploy + backup/revert mechanism as Report, but for the
local Documentation web tool :
PROSERVE-source/_doc/ â C:\xampp\htdocs\ProserveDoc\
http://localhost/ProserveDoc/
Default DocumentationUrl in the launcher set to http://localhost/ProserveDoc/
so the Documentation sidebar tab works out of the box on a fresh install
without any manual config.
Implementation
- LocalConfig.DocToolConfig (HtdocsRoot/FolderName/AutoDeploy/MaxBackups,
defaults to ProserveDoc folder, 3 backups kept).
- IDocToolDeployer + DocToolDeployer : near-duplicate of the Report
deployer with SourceSubdir = "_doc". Reuses ReportTool's BackupInfo /
DeployStatus / DeployResult / DeployProgress types so we don't fork
the data model.
- MainViewModel : new install step 7 right after the Report deploy step,
mirrors its progress reporting + error dialogs (silent on
XamppNotFound since the Report step already warned for the same root).
- SettingsViewModel : DocBackupViewModel + Doc properties + RedeployDoc
+ Revert commands. Loads the doc backups list in background like
the Report ones.
- SettingsDialog.xaml : new « OUTIL DOCUMENTATION » card under the
« OUTIL REPORT » one in Avancés, with the same fields and backup table.
- Strings.cs : doc-specific status / progress / error labels in 5 langs ;
reuses some labels from Report where the wording is identical.
Versions bumped to 0.17.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 11:18:13 +02:00
886245cc61
v0.16.0 â ThemedMessageBox replacing native MessageBox everywhere
...
The Win32 MessageBox uses the system white-on-gray look that clashes
with the launcher's dark theme (jarring transition every time we
confirm a cancel, show an error, etc.). Replaced with a custom WPF
dialog that:
- Drops in : same Show(message, title, button, icon, default) API as
System.Windows.MessageBox, returns the same MessageBoxResult.
- Looks native to the launcher : dark Brush.Bg.Window background,
Brush.Border outline, Brush.Text.Primary content, AccentButton for
the default action and SecondaryButton for the others.
- Iconography by emoji + colour code : â red (Error), â amber (Warning),
â neutral (Question), âč blue (Information). Mapped from MessageBoxImage.
- Buttons localised via Strings.ActionOk / ActionYes / ActionNo /
ActionCancel â works in fr/en/zh/th/ar like the rest of the UI.
- Owner = currently active window (falls back to MainWindow then
CenterScreen) so it positions correctly even from a child dialog.
- Esc resolves to Cancel/No matching MessageBox semantics.
The 21 MessageBox.Show call sites across MainViewModel, SettingsViewModel,
LicenseDetailsDialog and MainWindow now use ThemedMessageBox.Show with
no signature change â full grep replace + tiny `using` adjustments.
Versions bumped to 0.16.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 10:46:53 +02:00
24d1841844
v0.15.0 â release notes Markdown : readable inline code
...
Bumps to consolidate the dark-theme fix for inline `code` in the
release notes viewer (commit 6d11b43 ) into a versioned client.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 10:33:23 +02:00
c706d09e72
Drop legacy PSLauncher.exe backward compat (not in prod yet)
...
Simplifies the rename done in ce3979d : PS_Launcher.exe is the only
name. Removes the dual-path lookup in LauncherSelfUpdater, the
duplicated taskkill blocks in the .bat scripts, the legacy patterns
in .gitignore, and the explanatory comments about the migration.
Cleaner code, single source of truth for the binary name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 10:08:23 +02:00
ce3979d131
v0.14.0 â rename PSLauncher.exe â PS_Launcher.exe (with backward compat)
...
Aligns the executable name with the repo / product naming. Backward-
compatible: existing installs that have PSLauncher.exe on disk continue
to work â the self-updater overwrites the file at the running .exe path
without renaming, so the historical filename persists on those machines.
Changes
- AssemblyName : PSLauncher â PS_Launcher (both App and Updater csproj)
- Inno Setup : MyAppExeName, MyUpdaterExeName, OutputBaseFilename all
now use PS_Launcher prefix
- LauncherSelfUpdater : looks for PS_Launcher.Updater.exe first, falls
back to legacy PSLauncher.Updater.exe so old installs keep updating
- Build scripts (build-launcher / build-updater / build-installer /
build-all) : taskkill both legacy AND new names; output paths printed
with new names
- .gitignore : added PS_Launcher.exe / PS_Launcher.Updater.exe /
PS_Launcher-*.exe patterns alongside the legacy ones; also ignored
the WebView2 user-data folder and Office ~$ lock files
- Server admin/launcher.php : URL pattern now generates
PS_Launcher-{ver}.exe ; SignManifest's existing tolerant glob
*{ver}*.exe still matches both names
- Versions bumped to 0.14.0 (App + Updater + installer .iss)
Migration story for clients
- Brand-new install via PS_Launcher-Setup-0.14.0.exe â PS_Launcher.exe
on disk
- Existing install (PSLauncher.exe) auto-updates to v0.14 â file stays
named PSLauncher.exe but contains v0.14 code; self-updater fallback
ensures future updates keep working
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 09:55:41 +02:00
562b4e5ed0
v0.13.0 â Report tool : timestamped backups + manual revert
...
The previous deploy mechanism kept the old version under {target}.old
just long enough to swap, then deleted it â no way back if the new
report had a runtime bug only visible at use time.
Now each deploy keeps the previous active version under
{FolderName}.backup-yyyyMMdd-HHmmss/. The N most recent are kept
(MaxBackups, default 3) and older ones are pruned in best-effort.
UI in Settings â AvancĂ©s â Outil Report :
- "Conserver N backups" input (0 = no history, back to v0.12 behavior)
- List of backups with date + size + cryptic folder name + per-row "â¶ Revert"
- Confirmation dialog: "Revert to {date}? The currently deployed
version will itself be saved as a new backup, so you can switch back."
Implementation
- IReportToolDeployer: + ListBackupsAsync, + RevertAsync, + BackupInfo,
+ DeployStatus.BackupNotFound.
- ReportToolDeployer: deploy renames {target} â backup-{ts}; PruneOldBackups
trims to MaxBackups; RevertAsync atomically swaps current â chosen
backup, the previous current becoming itself a fresh backup.
- ReportBackupViewModel + DataTemplate for the list rows.
- Strings: backup labels, "â¶ Revert", confirmation message.
Caveats documented in the design discussion:
- Backups cover ONLY the report tool files. DB migrations are not reverted
(irreversible). If a migration broke the schema, that's a separate fix.
- No HTTP-HEAD post-deploy auto-revert: too fragile for false positives.
Revert stays a deliberate manual action.
Versions bumped to 0.13.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 09:15:09 +02:00
67f422539d
v0.12.0 â Report tool auto-deploy from PROSERVE ZIP
...
Each PROSERVE ZIP can ship a _report/ subfolder alongside _migrations/.
After install + DB migration, the launcher copies _report/ to the local
XAMPP htdocs (default C:\xampp\htdocs\ProserveReport\) so the Reports
tab in the sidebar always serves the matching version.
Service (PSLauncher.Core/ReportTool)
- IReportToolDeployer + ReportToolDeployer.
- Atomic deploy: copy to {target}.new/ â rename current to .old/ â
rename .new â final â cleanup .old. Apache never sees a half-state.
- Skip silencieux si _report/ absent du ZIP (PROSERVE release qui ne
touche pas au Report).
- DeployStatus enum (SkippedNoSourceFolder, XamppNotFound, Failed,
Deployed) renvoyé pour gestion UI claire.
Config (LocalConfig)
- ReportToolConfig (HtdocsRoot, FolderName, AutoDeploy). Defaults
matchent l'install standard ASTERION (C:\xampp\htdocs\ProserveReport).
Install pipeline (MainViewModel)
- Ătape 6 aprĂšs extraction + migrations. Progress reportĂ© via le footer
Library : « đ DĂ©ploiement Report : 47/120 â assets/main.js ».
XAMPP introuvable â dialog avec lien vers Settings â AvancĂ©s.
Settings UI (SettingsDialog â AvancĂ©s)
- Nouveau bloc « OUTIL REPORT (XAMPP htdocs) » : 2 champs path + checkbox
AutoDeploy + bouton « đ Re-dĂ©ployer maintenant » qui rejoue le deploy
sur la derniÚre version installée. Utile aprÚs changement de chemin
htdocs ou si l'auto-deploy avait foiré (XAMPP éteint).
Versions bumped to 0.12.0 (App + Updater + installer .iss).
CÎté release : tu ajoutes _report/ dans le source de PROSERVE, tu zippes,
tu uploades. Les clients récupÚrent ZIP + migrations + Report en une
seule install.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 09:05:40 +02:00
6d251e9eac
v0.11.0 â right sidebar nav with Reports / Documentation tabs
...
User-visible feature change (sidebar appears on every screen) deserves
a minor bump rather than a patch. WebView2 dependency adds ~5 MB to the
single-file exe (the Edge runtime itself is system-installed, not
shipped).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 08:53:14 +02:00
1473e5185c
v0.10.0 â DB migration support
...
Bumps version across the three places it lives:
- PSLauncher.App.csproj : 0.9.0 â 0.10.0
- PSLauncher.Updater.csproj : 0.8.0 â 0.10.0 (catch-up; the updater
hadn't been bumped since 0.8 â version doesn't gate behavior, just
matches the .exe metadata)
- installer/PSLauncher.iss : 0.8.0 â 0.10.0 (installer artifact name
PSLauncher-Setup-0.10.0.exe)
This release adds bundled SQL migrations applied automatically after
each PROSERVE install, so a new build that needs schema changes ships
the .sql alongside its binaries and the launcher rolls them out without
manual intervention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-03 08:29:02 +02:00
f5f1fe4d36
docs: PowerPoint deck for internal admin + end-user guide, embedded in installer
...
Two presentations generated from pptxgenjs scripts in docs/, both using
the launcher's dark palette (black bg, #161B23 cards, #3B82F6 blue
accent for internal, #16A34A green accent for the user guide):
- PS_Launcher-Documentation-Interne.pptx (17 slides)
Cover, sommaire, architecture client/serveur/MySQL, setup OVH initial,
config.php, génération secrets, backoffice (Dashboard / Licenses /
Versions / Launcher / Audit), workflow release Proserve, workflow
release launcher, security (Ed25519 / HMAC / DPAPI), file locations
+ logs, troubleshooting, closing.
- PS_Launcher-Guide-Utilisateur.pptx (14 slides)
Cover, présentation du launcher, prérequis Windows, installation
setup.exe (avec gestion SmartScreen), activation license PRSRV-,
bibliothĂšque (featured + autres), lancer une version, installer une
nouvelle version, suivi du DL avec resume, menu ⯠par version,
paramĂštres â, auto-update du launcher, FAQ (license expirĂ©e /
offline / changement PC / SHA-256 KO), support.
Both decks use cards with colored left-accent strips, numbered step
flows, code blocks (Consolas on #0A0E14), and consistent footer
branding "© 2026 ASTERION VR".
installer/PSLauncher.iss now copies the user guide into {app}\docs\
and creates a "Guide utilisateur" Start Menu shortcut next to the
launcher. The internal doc stays in the repo, never shipped to clients.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 12:11:35 +02:00
2dc9c98d1e
Bump version 0.7.0 / 0.5.0 â 0.8.0 (synced across launcher, updater, installer)
...
After the auto-update test loop the App was at 0.7.0 and Updater /
Inno Setup were still at 0.5.0. Sync everything to 0.8.0 so a single
version covers the recent batch of branding + UX changes:
- Default installRoot is C:\ASTERION_VR
- ASTERION favicon (window icons + .exe icon + Setup icon)
- ASTERION wordmark left of PROSERVE in the top bar
- Centered, readable "© 2026 ASTERION VR â Tous droits rĂ©servĂ©s" pill
- Window control hover in blue (was unreadable yellow)
- Background image anchored bottom-right so the brand mark doesn't
get cropped on resize or hidden by the download footer
- Launcher window minimizes when a Proserve version is launched
Next time the operator pushes a release, "Définir" 0.8.0 in the
backoffice â upload PSLauncher-0.8.0.exe â click the blue Sync.
Existing 0.7.0 launchers will then auto-update to 0.8.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 12:01:55 +02:00
dfad967eae
branding: ASTERION VR favicon + copyright everywhere
...
Icon
----
src/favicon64.jpg converted to favicon.ico (single-resolution 64x64)
via PowerShell + System.Drawing.Icon.FromHandle. The .ico is now an
EmbeddedResource of PSLauncher.App and referenced as:
- <ApplicationIcon> on PSLauncher.App.csproj â .exe icon in
Explorer / taskbar / Alt-Tab
- Window.Icon on every WPF window: MainWindow, SettingsDialog,
OnboardingDialog, LicenseDetailsDialog, UpdateAvailableDialog,
ReleaseNotesViewerDialog, LauncherUpdateDialog
- <ApplicationIcon> on PSLauncher.Updater.csproj â updater also
carries the brand icon
- SetupIconFile in installer/PSLauncher.iss â the Inno Setup .exe
installer shows the icon too
Copyright
---------
- <Company>, <Product>, <Copyright> assembly attributes set in
both csprojs â properties dialog on the .exe shows "© ASTERION VR".
- MainWindow body has a discreet floating "© ASTERION VR" text in the
bottom-right, mirroring the "Vérifier les MAJ" button on the left.
Opacity 0.7 + secondary color so it doesn't compete with the cards.
- SettingsDialog "Logs & Application" section gains a
"© ASTERION VR â Tous droits rĂ©servĂ©s" line under the version.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 11:44:28 +02:00
f2a1de9aac
Self-update: UAC-aware, de-elevated relaunch, user-mode installer
...
Three coordinated changes so the auto-update flow works on every
existing install location.
LauncherSelfUpdater.cs
----------------------
- Probe the target directory with a write/delete of a temp file. If
it fails (e.g. install in Program Files without admin), set
UseShellExecute=true + Verb=runas on the Updater process so UAC
prompts the user once for elevation. If the directory is writable
(user-mode install or portable layout), skip elevation entirely.
- Switched from ProcessStartInfo.ArgumentList to a quoted Arguments
string because Verb=runas requires UseShellExecute=true, which
ignores ArgumentList. Paths get explicit quotes to survive spaces.
Updater Program.cs
------------------
After the file swap, the relaunch was a direct Process.Start(target).
With UAC elevation that propagates admin to the new launcher, then to
its child PROSERVE_UE_5_5.exe â undesirable. Replace with
`explorer.exe "<target>"`: explorer is always running in the user's
normal token, opens the exe via shell, the new process inherits the
non-elevated session. Standard de-elevation trick.
installer/PSLauncher.iss
------------------------
Switch from system-wide install (Program Files, requires admin) to
per-user (DefaultDirName={localappdata}\Programs\..., PrivilegesRequired=
lowest). Auto-update then never needs UAC at all on fresh installs.
Existing installs in Program Files keep working thanks to the runas
fallback above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 10:57:32 +02:00
6bc5381562
installer: use x64 (Inno Setup 6.0+) instead of x64compatible (6.3+)
...
The value x64compatible was added in Inno Setup 6.3 â earlier 6.x
versions reject it with "Value of [Setup] section directive
ArchitecturesAllowed is invalid". Using plain x64 keeps compatibility
with the whole 6.x line. Modern users on 6.3+ are still happy since
x64 still resolves correctly there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 09:54:05 +02:00
b7de228bc9
v0.6 + v1.0: HMAC download URLs, launcher self-update, Inno Setup installer
...
Three deliverables shipped together so the next deployment cycle has a
clean distribution story.
(1) Auto-update of the launcher itself
---------------------------------------
- Models: RemoteManifest gains an optional `launcher` section
(LauncherInfo: version, minRequired, download {url,size,sha256},
releaseNotesUrl). Server-side, sign-manifest.php passes it through
unchanged; admins edit versions.json with the new launcher entry +
upload PSLauncher-X.Y.Z.exe to /builds/launcher/.
- Core: ILauncherSelfUpdater compares assembly version against
manifest.launcher.version using the existing SemVer parser, and
reuses DownloadManager (Range/resume/sha256 already proven on the
game ZIPs) to download the new exe into
%LocalAppData%/PSLauncher/selfupdate/.
- New project PSLauncher.Updater (~34 MB self-contained console exe):
spawned by the main app with --target / --source / --pid / --launch.
Waits for the main process to exit (or for the file lock to release),
backs up the current exe to .bak, copies the new file in place, and
restarts. .bak survives the swap so the user can roll back manually.
- App.csproj now declares Version=0.5.0 â currently shipped baseline.
PSLauncher.App.csproj sets a fixed AssemblyVersion so reflection-based
comparison works deterministically.
- MainViewModel.PromptLauncherUpdate: dialog after CheckForUpdates if
the manifest advertises a newer launcher. Download with progress in
the existing footer, then Application.Shutdown() so the Updater can
do its job.
(2) Inno Setup installer
------------------------
installer/PSLauncher.iss + build-installer.ps1 produce a single
PSLauncher-Setup-X.Y.Z.exe (~80 MB) that installs into
Program Files\ASTERION VR\PSLauncher\, drops both PSLauncher.exe and
PSLauncher.Updater.exe side by side (the updater MUST live next to
the target), creates Start Menu + optional Desktop shortcuts, and
registers a clean uninstall entry. The user's %LocalAppData%
(license, logs, cache) is intentionally untouched on uninstall â same
license survives a reinstall.
build-installer.ps1 chains dotnet publish for both projects and ISCC
in one command. README explains the bump-version workflow.
(3) HMAC-signed download URLs
-----------------------------
- New PHP route GET /api/download-url/{version} (Authorization: Bearer
<licenseKey> or ?key=...). Validates the license, checks
download_entitlement_until >= minLicenseDate of the version, and
returns a HMAC-signed URL (path|exp|licId, hash_hmac SHA-256, valid
1 h) + sha256 + sizeBytes for verification.
- /builds/.htaccess routes every *.zip request to gate.php. gate.php
validates exp, lic, sig (constant-time hash_equals), then streams
the file with Range: support so the launcher's resume keeps working.
Audit log gets a download_url_issued entry per request.
- Client-side wired transparently: LicenseService gains
GetSignedDownloadUrlAsync(version) that GETs the endpoint with the
decrypted license key from DPAPI. MainViewModel calls it before
every download; if the endpoint returns 404/401/network-error, the
client falls back to the manifest's plain download.url (graceful
degradation for setups that haven't deployed gate.php yet).
Note on PHP streaming for 14 GB ZIPs: gate.php uses set_time_limit(0)
+ ignore_user_abort(true) + 1 MiB chunked fread with periodic flush.
Works on OVH mutualisé but holds a PHP-FPM slot for the duration. If
parallel downloads scale past a few clients, switch to
mod_xsendfile or migrate /builds/ to Cloudflare R2 with native
S3-presigned URLs and remove the gate entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 09:38:13 +02:00