Skip to content

Commit a7d14ba

Browse files
authored
Add files via upload
1 parent 9d4aa3f commit a7d14ba

26 files changed

Lines changed: 1744 additions & 64 deletions

README.md

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,41 @@
55
</h1>
66

77
<p><strong>A beautiful, free, open-source personal game library for Windows.</strong><br/>
8-
Track, organize, rate and launch every game you own — Steam, Epic, GOG, and custom — from one sleek desktop app.</p>
8+
Track, organize, rate and launch every game you own — Steam, Epic, GOG, Ubisoft Connect, and custom — from one sleek desktop app.</p>
99

1010
<p>
1111
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-7c3aed?style=flat-square" alt="MIT License"/></a>
1212
<img src="https://img.shields.io/badge/Platform-Windows%2010%2F11-0078D4?style=flat-square&logo=windows" alt="Windows"/>
13-
<img src="https://img.shields.io/badge/Version-1.2.0-22c55e?style=flat-square" alt="v1.2.0"/>
13+
<img src="https://img.shields.io/badge/Version-1.2.1-22c55e?style=flat-square" alt="v1.2.1"/>
1414
<a href="https://tauri.app"><img src="https://img.shields.io/badge/Built%20with-Tauri%202-FFC131?style=flat-square" alt="Tauri 2"/></a>
1515
<img src="https://img.shields.io/badge/React-18-61DAFB?style=flat-square&logo=react" alt="React 18"/>
1616
<img src="https://img.shields.io/badge/Rust-backend-CE422B?style=flat-square&logo=rust" alt="Rust"/>
1717
</p>
1818

1919
<p>
2020
<a href="https://zsync.eu/zgamelib/"><strong>🌐 Website</strong></a> &nbsp;·&nbsp;
21-
<a href="https://zsync.eu/zgamelib/app/ZGameLib_1.2.0_x64_en-US.msi"><strong>⬇ Download MSI</strong></a> &nbsp;·&nbsp;
22-
<a href="https://zsync.eu/zgamelib/app/ZGameLib_1.2.0_x64-setup.exe"><strong>⬇ Download EXE</strong></a> &nbsp;·&nbsp;
21+
<a href="https://zsync.eu/zgamelib/app/ZGameLib_1.2.1_x64_en-US.msi"><strong>⬇ Download MSI</strong></a> &nbsp;·&nbsp;
22+
<a href="https://zsync.eu/zgamelib/app/ZGameLib_1.2.1_x64-setup.exe"><strong>⬇ Download EXE</strong></a> &nbsp;·&nbsp;
2323
<a href="https://github.com/TheHolyOneZ/ZGameLib"><strong>GitHub</strong></a>
2424
</p>
2525

2626
</div>
2727

2828
---
2929

30-
## What's New in v1.2.0
30+
## What's New in v1.2.1
3131

32-
v1.2.0 brings **game session management**, **security hardening**, and **8 targeted bug fixes**.
32+
v1.2.1 adds **Ubisoft Connect** as a full first-class platform and introduces **collapsible library sections**.
3333

3434
| Feature | Description |
3535
|---------|-------------|
36-
| **Game-Already-Running Detection** | Launching while another session is active shows a confirm dialog with "Stop & Launch" to switch games instantly |
37-
| **Live "Playing" Indicator** | Play button shows a pulsing green "Playing" state while a game session is active |
38-
| **Stop & Launch** | Terminates the running game process (`TerminateProcess`) before launching the new one |
39-
| **ZipSlip Patch** | Path traversal vulnerability patched in BepInEx/MelonLoader installer |
40-
| **Filesystem Hardening** | `save_file` restricted to safe directories (AppData, Documents, Desktop) |
41-
| **IGDB Token Cache** | OAuth tokens cached with 60s expiry buffer — no redundant round-trips |
42-
| **Soft-Delete Fix** | Partial UNIQUE indexes prevent duplicate blocking on soft-deleted records |
43-
| **Transaction Safety** | `reorder_games` and `batch_update_games` wrapped in SQLite transactions |
44-
| **Game Tracking Fallback** | Steam/Epic games without install directory stay tracked instead of resetting instantly |
36+
| **Ubisoft Connect** | Added alongside Steam, Epic Games, and GOG as a default platform |
37+
| **Ubisoft Auto-Scan** | Reads Windows registry (`HKLM\SOFTWARE\WOW6432Node\Ubisoft\Launcher\Installs`) to detect installed games and executables |
38+
| **Ubisoft Launch** | Launches games directly via `ubisoft://launch/{id}` protocol with full playtime tracking |
39+
| **Ubisoft Platform Badge** | Sidebar filter, spin wheel filter, stats chart, library growth chart, and Year in Review all support Ubisoft |
40+
| **Filter Builder Support** | Ubisoft Connect available as a platform option in the advanced filter builder |
41+
| **Game Detail Panel** | Shows Ubisoft Game ID in the info section |
42+
| **Collapsible Sections** | Play Next and Recently Played rows can be collapsed with one click — preference persists across sessions |
4543

4644
---
4745

@@ -61,16 +59,16 @@ v1.2.0 brings **game session management**, **security hardening**, and **8 targe
6159

6260
| Installer | Format | Notes |
6361
|-----------|--------|-------|
64-
| [ZGameLib_1.2.0_x64_en-US.msi](https://zsync.eu/zgamelib/app/ZGameLib_1.2.0_x64_en-US.msi) | `.msi` | **Recommended** — Windows Installer |
65-
| [ZGameLib_1.2.0_x64-setup.exe](https://zsync.eu/zgamelib/app/ZGameLib_1.2.0_x64-setup.exe) | `.exe` | NSIS alternative installer |
62+
| [ZGameLib_1.2.1_x64_en-US.msi](https://zsync.eu/zgamelib/app/ZGameLib_1.2.1_x64_en-US.msi) | `.msi` | **Recommended** — Windows Installer |
63+
| [ZGameLib_1.2.1_x64-setup.exe](https://zsync.eu/zgamelib/app/ZGameLib_1.2.1_x64-setup.exe) | `.exe` | NSIS alternative installer |
6664

6765
> **Windows SmartScreen:** On first launch you may see *"Windows protected your PC"* — click **More info → Run anyway**. This is expected for unsigned indie apps.
6866
6967
---
7068

7169
## Table of Contents
7270

73-
- [What's New in v1.2.0](#whats-new-in-v120)
71+
- [What's New in v1.2.1](#whats-new-in-v121)
7472
- [Features](#features)
7573
- [Onboarding Tour](#-interactive-onboarding-tour)
7674
- [Library & Scanning](#-library--scanning)
@@ -167,7 +165,7 @@ The signature 1.0 feature. On first launch, users pick a tour mode — ZGameLib
167165
- **Drag-and-drop reordering** — select "Custom Order" sort to drag cards into any order; persisted to the database
168166

169167
**Filtering**
170-
- Filter by platform: All / Steam / Epic / GOG / Custom
168+
- Filter by platform: All / Steam / Epic / GOG / Ubisoft Connect / Custom
171169
- Filter by status (any custom status)
172170
- Filter by cover art: Has Cover / Missing Cover (sidebar, with counts)
173171
- Favorites-only toggle
@@ -252,12 +250,24 @@ The signature 1.0 feature. On first launch, users pick a tour mode — ZGameLib
252250
</td>
253251
<td width="33%" valign="top">
254252

253+
**🔵 Ubisoft Connect**
254+
- Launches via `ubisoft://launch/{gameId}` protocol
255+
- Registry-based game ID and install directory detection
256+
- Full playtime tracking with directory-based process monitoring
257+
258+
</td>
259+
</tr>
260+
<tr>
261+
<td width="33%" valign="top">
262+
255263
**⬜ GOG / Custom**
256264
- Direct executable spawn
257265
- Full process monitoring from launch
258266
- Instant playtime tracking start
259267

260268
</td>
269+
<td width="33%" valign="top"></td>
270+
<td width="33%" valign="top"></td>
261271
</tr>
262272
</table>
263273

@@ -391,7 +401,7 @@ Can't decide what to play? Let the wheel decide.
391401
<td width="50%" valign="top">
392402

393403
**Controls & Result**
394-
- Filter pool: All · Steam · Epic · GOG · Custom · Favorites
404+
- Filter pool: All · Steam · Epic · GOG · Ubisoft · Custom · Favorites
395405
- Free-text search to include only matching games
396406
- **Exclude last winner** toggle
397407
- Pool preview: first 5 game covers + "+X more"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "zgamelib",
33
"private": true,
4-
"version": "1.0.0",
4+
"version": "1.2.1",
55
"type": "module",
66
"scripts": {
77
"dev": "cargo tauri dev",

src/components/game/GameDetail.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ export default function GameDetail() {
10801080

10811081
<GameNotes gameId={game.id} />
10821082

1083-
{(game.steam_app_id || game.epic_app_name || game.install_dir) && (
1083+
{(game.steam_app_id || game.epic_app_name || game.ubisoft_game_id || game.install_dir) && (
10841084
<div className="glass rounded-xl p-4 space-y-1.5">
10851085
<p className="text-[10px] text-slate-600 uppercase tracking-[0.15em] font-semibold mb-2">Info</p>
10861086
{game.steam_app_id && (
@@ -1095,6 +1095,12 @@ export default function GameDetail() {
10951095
<span className="text-slate-400 truncate">{game.epic_app_name}</span>
10961096
</p>
10971097
)}
1098+
{game.ubisoft_game_id && (
1099+
<p className="text-[11px] text-slate-600 flex gap-2">
1100+
<span className="text-slate-500 shrink-0">Ubisoft ID</span>
1101+
<span className="text-slate-400 truncate">{game.ubisoft_game_id}</span>
1102+
</p>
1103+
)}
10981104
{game.install_dir && (
10991105
<p className="text-[11px] text-slate-600 flex gap-2">
11001106
<span className="text-slate-500 shrink-0">Path</span>
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { useCallback, useEffect, useRef, useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { useGameStore } from "@/store/useGameStore";
4+
import { useUIStore } from "@/store/useUIStore";
5+
import { useScan } from "@/hooks/useGames";
6+
import { api } from "@/lib/tauri";
7+
import { getFastSteps, getStandardSteps, getDetailedSteps } from "./steps";
8+
import TourSpotlight from "./TourSpotlight";
9+
import TourCard from "./TourCard";
10+
import TourFinale from "./TourFinale";
11+
import type { TourStep } from "./steps";
12+
13+
function getSteps(mode: "fast" | "standard" | "detailed", deps: Parameters<typeof getFastSteps>[0]): TourStep[] {
14+
if (mode === "fast") return getFastSteps(deps);
15+
if (mode === "standard") return getStandardSteps(deps);
16+
return getDetailedSteps(deps);
17+
}
18+
19+
export default function OnboardingTour() {
20+
const tourOpen = useUIStore((s) => s.tourOpen);
21+
const setTourOpen = useUIStore((s) => s.setTourOpen);
22+
const tourMode = useUIStore((s) => s.tourMode);
23+
const setDetailOpen = useUIStore((s) => s.setDetailOpen);
24+
const setAddGameOpen = useUIStore((s) => s.setAddGameOpen);
25+
const setSelectedGameId = useGameStore((s) => s.setSelectedGameId);
26+
const navigate = useNavigate();
27+
const { scan } = useScan();
28+
29+
const [stepIndex, setStepIndex] = useState(0);
30+
const [steps, setSteps] = useState<TourStep[]>([]);
31+
const [loading, setLoading] = useState(false);
32+
const [showFinale, setShowFinale] = useState(false);
33+
const stepIndexRef = useRef(0);
34+
35+
useEffect(() => {
36+
if (!tourOpen || !tourMode) return;
37+
const deps = { navigate, setSelectedGameId, setDetailOpen, setAddGameOpen, triggerScan: scan };
38+
const newSteps = getSteps(tourMode, deps);
39+
setSteps(newSteps);
40+
setStepIndex(0);
41+
stepIndexRef.current = 0;
42+
setShowFinale(false);
43+
44+
(async () => {
45+
const first = newSteps[0];
46+
if (first?.before) {
47+
setLoading(true);
48+
try { await first.before(); } catch {}
49+
setLoading(false);
50+
}
51+
if (first?.afterRender) {
52+
try { await first.afterRender(); } catch {}
53+
}
54+
})();
55+
}, [tourOpen, tourMode]);
56+
57+
const finishTour = useCallback(async () => {
58+
setTourOpen(false);
59+
setShowFinale(false);
60+
setDetailOpen(false);
61+
setAddGameOpen(false);
62+
navigate("/");
63+
await api.saveSetting("onboarding_completed", "true");
64+
if (tourMode) await api.saveSetting("onboarding_tour_mode", tourMode);
65+
}, [tourMode, setTourOpen, setDetailOpen, setAddGameOpen, navigate]);
66+
67+
const completeTour = useCallback(async () => {
68+
const currentStep = steps[stepIndexRef.current];
69+
if (currentStep?.after) {
70+
try { await currentStep.after(); } catch {}
71+
}
72+
73+
if (currentStep?.id === "done" || steps[stepIndexRef.current]?.id === "shortcuts") {
74+
setDetailOpen(false);
75+
setAddGameOpen(false);
76+
navigate("/");
77+
setShowFinale(true);
78+
return;
79+
}
80+
81+
await finishTour();
82+
}, [steps, setDetailOpen, setAddGameOpen, navigate, finishTour]);
83+
84+
const goToStep = useCallback(async (index: number) => {
85+
if (index < 0 || index >= steps.length) return;
86+
setLoading(true);
87+
88+
const currentStep = steps[stepIndexRef.current];
89+
if (currentStep?.after) {
90+
try { await currentStep.after(); } catch {}
91+
}
92+
93+
const nextStep = steps[index];
94+
95+
if (nextStep.id === "done") {
96+
setDetailOpen(false);
97+
setAddGameOpen(false);
98+
navigate("/");
99+
await new Promise((r) => requestAnimationFrame(r));
100+
setShowFinale(true);
101+
setLoading(false);
102+
return;
103+
}
104+
105+
if (nextStep.before) {
106+
try { await nextStep.before(); } catch {}
107+
}
108+
109+
await new Promise((r) => requestAnimationFrame(r));
110+
stepIndexRef.current = index;
111+
setStepIndex(index);
112+
setLoading(false);
113+
114+
if (nextStep.afterRender) {
115+
try { await nextStep.afterRender(); } catch {}
116+
}
117+
}, [steps, setDetailOpen, setAddGameOpen, navigate]);
118+
119+
const handleNext = useCallback(async () => {
120+
if (stepIndexRef.current >= steps.length - 1) { await completeTour(); return; }
121+
await goToStep(stepIndexRef.current + 1);
122+
}, [steps.length, completeTour, goToStep]);
123+
124+
const handleBack = useCallback(async () => {
125+
if (stepIndexRef.current > 0) await goToStep(stepIndexRef.current - 1);
126+
}, [goToStep]);
127+
128+
useEffect(() => {
129+
if (!tourOpen || showFinale) return;
130+
const handler = (e: KeyboardEvent) => {
131+
if (e.key === "Escape") { completeTour(); return; }
132+
if (e.key === "ArrowRight" || e.key === "Enter") { e.preventDefault(); handleNext(); return; }
133+
if (e.key === "ArrowLeft") { e.preventDefault(); handleBack(); }
134+
};
135+
window.addEventListener("keydown", handler, true);
136+
return () => window.removeEventListener("keydown", handler, true);
137+
}, [tourOpen, showFinale, handleNext, handleBack, completeTour]);
138+
139+
if (!tourOpen || steps.length === 0) return null;
140+
141+
if (showFinale) {
142+
return <TourFinale onComplete={finishTour} />;
143+
}
144+
145+
const step = steps[stepIndex];
146+
const showChapters = tourMode === "detailed";
147+
148+
return (
149+
<>
150+
<TourSpotlight target={step.target} stepId={step.id} />
151+
<TourCard
152+
step={step}
153+
stepIndex={stepIndex}
154+
totalSteps={steps.length - 1}
155+
onNext={handleNext}
156+
onBack={handleBack}
157+
onSkip={completeTour}
158+
showChapters={showChapters}
159+
loading={loading}
160+
/>
161+
</>
162+
);
163+
}

0 commit comments

Comments
 (0)