Compare commits

..

216 Commits

Author SHA1 Message Date
Nix
0014978163
Better fallbacks in the login system 2025-12-10 21:18:42 -03:00
Nixietab
01587aa5e7 simple changes to the version managment menu
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-12-08 18:38:54 -03:00
Nixietab
41bca4d23b bumped version, removed drums 2025-12-08 16:07:25 -03:00
Nixietab
9bac8e2d6d added actual progressbar to the version prepare menu 2025-12-08 16:06:16 -03:00
Nix
da0f4449ea
Return None instead of error message when no output 2025-12-08 15:47:15 -03:00
Nix
30e714ac30
Added progress bar and abort button
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-11-28 01:19:20 -03:00
Nix
971b9a07d9
Make the GUI of the instances menu a little better 2025-11-27 19:42:26 -03:00
Nix
c5cfb0176e
Bump version from 0.13.6 to 0.13.7
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-11-02 03:56:15 -03:00
Nix
e2b41ce899
make the holidays icon less ugly 2025-11-02 03:55:56 -03:00
Nix
2ca02b378c
Update README.md 2025-10-02 19:37:24 -03:00
Nix
3c9634bd24
added universall linux install 2025-10-02 19:34:56 -03:00
Nix
13a167f7fa
problems with zucaro/picomc
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-09-26 10:11:59 -03:00
Nix
42dfaf8904
Update version.json 2025-09-26 10:09:20 -03:00
Nix
078518e5ad
hotfix 2025-09-26 10:08:58 -03:00
Nix
7f0108221b
Update README.md 2025-09-21 15:05:59 -03:00
Nix
16936e3a0d
Update PKGBUILD 2025-09-21 15:04:02 -03:00
Nix
8f24903fb3
Zucaro replace (#15)
Some checks failed
Version Change Action / version-release (push) Has been cancelled
* Initial commit of the replace

* Delete .github/workflows/Bleeding-Job.yaml

* remove picomc

* Update README.md

* Update version.json
2025-09-15 00:55:06 -03:00
github-actions[bot]
408afef6f1 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-08-12 23:06:17 +00:00
Nix
2b950c3c2d
Update version.json 2025-08-12 20:06:10 -03:00
github-actions[bot]
12b8b67bbd Update version.json with commit count 2025-08-12 23:05:47 +00:00
Nix
aceb899ea6
add zucaro to the requirements 2025-08-12 20:05:33 -03:00
github-actions[bot]
5e27a78ded Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-05-20 02:43:10 +00:00
Nix
aaf5ccbc90
Update version.json 2025-05-19 23:42:58 -03:00
github-actions[bot]
aaf73533c1 Update version.json with commit count 2025-05-20 02:42:43 +00:00
Nix
8a6246cbbb
fix 2025-05-19 23:42:31 -03:00
github-actions[bot]
723cd1dd56 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-04-23 18:49:27 +00:00
Wabaano
1e30d2a164
PR of recording total playtime (#14)
Added "Playtime" on About menu.
2025-04-23 15:49:13 -03:00
github-actions[bot]
9523f1ab0c Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-04-15 06:23:51 +00:00
Nix
103927328a
little fix to the version managing window 2025-04-15 03:23:40 -03:00
github-actions[bot]
faefc09aad Update version.json with commit count 2025-04-15 06:13:47 +00:00
Nix
dfc2dac3f5
Update version.json 2025-04-15 03:13:37 -03:00
github-actions[bot]
7942c09082 Update version.json with commit count 2025-04-15 06:13:15 +00:00
Nix
d85cfe2de6
Update requirements.txt 2025-04-15 03:13:08 -03:00
github-actions[bot]
d145fb2df1 Update version.json with commit count 2025-04-15 06:13:02 +00:00
OmeletGit
dedc59d09c
Fix the authentication system dont saving tokens (#13) 2025-04-15 03:12:53 -03:00
github-actions[bot]
32e4783218 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-04-07 21:05:29 +00:00
Nix
502e64df83
Update version.json 2025-04-07 18:05:20 -03:00
github-actions[bot]
1cfb6ffcb6 Update version.json with commit count 2025-04-07 20:53:17 +00:00
Nix
77291ad89e
Update version.json 2025-04-07 17:53:08 -03:00
github-actions[bot]
3baf6e0b1d Update version.json with commit count 2025-04-07 20:52:52 +00:00
Nix
0768897706
stoped parsing commands (#12)
* Add files via upload

* auth done even better!
2025-04-07 17:52:43 -03:00
github-actions[bot]
ba8072c669 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-03-03 04:12:50 +00:00
Nix
785e9be9f9
Update version.json 2025-03-03 01:12:37 -03:00
github-actions[bot]
0cbd000be4 Update version.json with commit count 2025-03-03 04:11:40 +00:00
Nixietab
52b635285e Moved the health checks to a OOP method 2025-03-03 01:10:53 -03:00
github-actions[bot]
67a16c008a Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-03-01 08:34:21 +00:00
Nix
a4bd707461
Delete healtcheck.py 2025-03-01 05:34:12 -03:00
github-actions[bot]
1b27fffc96 Update version.json with commit count 2025-03-01 08:34:00 +00:00
Nix
fade5f86b7
Update picodulce.py 2025-03-01 05:33:51 -03:00
github-actions[bot]
823b438840 Update version.json with commit count 2025-03-01 08:33:36 +00:00
Nix
9a8c3f44d0
Update version.json 2025-03-01 05:33:25 -03:00
github-actions[bot]
6b65fb0d1e Update version.json with commit count 2025-03-01 08:33:18 +00:00
Nix
8247009d60
Delete locales directory 2025-03-01 05:33:07 -03:00
github-actions[bot]
e5c395d031 Update version.json with commit count 2025-03-01 08:06:58 +00:00
Nix
263e6eae07
Added rudimentary translations (#11)
* Update picodulce.py

* Update picodulce.py

* Update picodulce.py

* Update picodulce.py

* Create healtcheck.py

* Update picodulce.py

* Update version.json

* Create locales-go-here

* Add files via upload

* Update version.json

* Update picodulce.py

* Update healtcheck.py

* Update picodulce.py

* Added more locales

* Update version.json

* Update picodulce.py
2025-03-01 05:06:48 -03:00
github-actions[bot]
ec99488326 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-02-23 04:26:44 +00:00
Nix
61cd427beb
Delete locales directory 2025-02-23 01:26:35 -03:00
github-actions[bot]
cb2f5b52b3 Update version.json with commit count 2025-02-23 04:26:29 +00:00
Nix
ba40354a5d
Create bogosbinted.json 2025-02-23 01:26:19 -03:00
github-actions[bot]
0c151b058e Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-02-12 19:13:11 +00:00
Nix
fc7f47d273
put the loading theme background in a separate function 2025-02-12 16:12:59 -03:00
github-actions[bot]
4f4ff35ee5 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-02-11 11:52:38 +00:00
Nix
8b9827b422
Update version.json 2025-02-11 08:52:27 -03:00
github-actions[bot]
892cbc4d07 Update version.json with commit count 2025-02-11 11:51:51 +00:00
Nix
f61f15fe7e
Update version.json 2025-02-11 08:51:39 -03:00
github-actions[bot]
d077a922c0 Update version.json with commit count 2025-02-11 11:50:38 +00:00
Nix
9b70503d26
auth done right 2025-02-11 08:50:27 -03:00
github-actions[bot]
ae9f25a7a8 Update version.json with commit count 2025-02-11 11:49:55 +00:00
Nix
00ed5f97b9
authentication done right 2025-02-11 08:49:44 -03:00
github-actions[bot]
5dbbfd5d87 Update version.json with commit count 2025-02-11 05:45:27 +00:00
Nix
37a1c5b0df
Update version.json 2025-02-11 02:45:16 -03:00
github-actions[bot]
f2a1989993 Update version.json with commit count 2025-02-11 05:38:13 +00:00
Nix
3d40ce7df3
fixxed the need of re-opening the settings menu to refresh the themes list 2025-02-11 02:38:02 -03:00
github-actions[bot]
36ff8896ef Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-02-04 16:42:58 +00:00
Nix
5f59acf0b4
Merge pull request #10 from refrigerador67/main
Removed popup when checking for updates on startup when in latest version
2025-02-04 13:42:46 -03:00
github-actions[bot]
c48a193d9a Update version.json with commit count 2025-02-04 16:39:08 +00:00
refrigerador67
47a843c669
Removed popup at latest version when checking updates at startup 2025-02-04 13:38:57 -03:00
github-actions[bot]
52be28bb6c Update version.json with commit count 2025-02-04 16:30:53 +00:00
github-actions[bot]
6522b70066 Update version.json with commit count
Some checks failed
Bleeding Update version / update-version (push) Has been cancelled
Version Change Action / version-release (push) Has been cancelled
2025-01-29 02:03:17 +00:00
Nix
8d486a9af2
Update version.json 2025-01-28 23:03:07 -03:00
github-actions[bot]
97393e4ae7 Update version.json with commit count 2025-01-29 02:02:18 +00:00
Nix
e35120bb36
fixxed bleeding edge 2025-01-28 23:02:07 -03:00
github-actions[bot]
f2cfb3ceb3 Update version.json with commit count 2025-01-29 01:47:18 +00:00
Nix
15246cd535
Create Bleeding-Job.yaml 2025-01-28 22:47:06 -03:00
Nix
3edcd10c12
Update Build.yml 2025-01-28 22:43:44 -03:00
Nix
874e513b47
Update requirements.txt 2025-01-21 10:05:08 -03:00
Nix
a10318e00d
Update picodulce.py 2025-01-20 15:11:04 -03:00
Nix
7608b647fe
minnor fix to the instance stuff
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-01-14 02:22:32 -03:00
Nix
60d16326b0
Update picodulce.py 2025-01-14 02:21:55 -03:00
Nix
0d300f0435
Merge pull request #9 from refrigerador67/main
Update README.md
2025-01-14 01:56:33 -03:00
refrigerador67
3123ed30cf
Update README.md 2025-01-14 04:50:05 +00:00
Nix
db41858aae
Update picodulce.py 2025-01-14 01:07:50 -03:00
Nix
8c0a794202
Merge pull request #8 from refrigerador67/main
Added PKGBUILD
2025-01-14 00:29:43 -03:00
refrigerador67
514f6427ab
Update README.md 2025-01-14 03:25:54 +00:00
refrigerador67
9920636b9c
Merge branch 'nixietab:main' into main 2025-01-14 03:22:20 +00:00
refrigerador67
5182e42f81
Added PKGBUILD 2025-01-14 03:21:48 +00:00
Nix
d2f3aa6a49
Update version.json 2025-01-14 00:15:39 -03:00
Nix
8167462838
added animated themes support! 2025-01-13 20:19:25 -03:00
Nix
51dc126c8a
minnor fixes 2025-01-09 23:20:35 -03:00
Nix
5202f8fb25
Add files via upload 2025-01-08 22:36:18 -03:00
Nix
46d053d952
minor changes 2025-01-06 16:09:57 -03:00
Nix
82f44105b5
Update version.json
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-01-06 06:40:03 -03:00
Nix
fd2e57ffb3
Update picodulce.py 2025-01-06 06:38:11 -03:00
Nix
372075132f
Update picodulce.py 2025-01-06 00:54:35 -03:00
Nix
9c66a3eeb8
theme previews 2025-01-05 06:49:40 -03:00
Nix
27c75d1ef2
Update version.json
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2025-01-02 01:41:00 -03:00
Nix
18692c3abc
Update README.md 2025-01-02 01:40:45 -03:00
Nix
c4f662235e
Update picodulce.py 2025-01-02 01:29:09 -03:00
Nix
bf30a6ecd6
added a better instance GUI 2025-01-02 01:28:37 -03:00
Nix
f4b0b39090
Update version.json
Some checks are pending
Version Change Action / version-release (push) Waiting to run
2025-01-01 13:27:48 -03:00
Nix
e02ca8ab76
Added a rudimentary Instance manager 2025-01-01 13:27:15 -03:00
Nix
b55ac71db1
Update version.json
Some checks are pending
Version Change Action / version-release (push) Waiting to run
2024-12-31 15:38:48 -03:00
Nix
f4eac0f412
Create Build.yml 2024-12-31 15:38:24 -03:00
Nix
dbca6721be
Update version.json 2024-12-31 11:27:29 -03:00
Nix
b0ec76ebc3
Update version.json 2024-12-31 10:55:07 -03:00
Nix
0028de0d69
Update version.json 2024-12-31 10:51:46 -03:00
Nix
17f5f2a8d0
Update version.json 2024-12-31 10:50:11 -03:00
Nix
70f6b2b59c
Update README.md 2024-12-26 08:36:39 -03:00
Nix
b824c2c4fb
minor changes 2024-12-23 22:28:50 -03:00
Nix
b60b5179bc
Update picodulce.py 2024-12-23 22:22:15 -03:00
Nix
58e2b018d8
Update picodulce.py 2024-12-21 21:21:44 -03:00
Nix
3ee4348dfe
Update README.md 2024-12-21 21:15:35 -03:00
Nix
b7c099c0d9
Update version.json 2024-12-21 06:21:40 -03:00
Nix
17ccedfefc
added native theme compatibility and generation
also added some fallbacks
2024-12-21 06:21:28 -03:00
Nix
afac01f3dc
Update version.json 2024-12-20 09:52:18 -03:00
Nix
fd4c23ed3b
added stylesheet support 2024-12-20 09:43:15 -03:00
refrigerador67
a19b8a1545
Minor UI changes 2024-12-18 23:11:00 -03:00
Nix
152b7d82dc
Merge pull request #6 from refrigerador67/patch-1
Update README.md
2024-12-18 22:50:30 -03:00
refrigerador67
566d3fc321
Update README.md 2024-12-18 22:42:43 -03:00
Nix
163d116005
Update version.json 2024-12-18 22:40:08 -03:00
Nix
fc6b45a394
Added full custom theme support
commit 100! yay!
2024-12-18 22:39:55 -03:00
Nix
33d85e4cb2
Update picodulce.py 2024-12-14 22:24:17 -03:00
Nix
06ddb8936a
Update version.json 2024-12-14 22:21:44 -03:00
Nix
36ef0c58ae
even better discord rp 2024-12-14 22:21:30 -03:00
Nix
d0109bb96f
Update picodulce.py 2024-12-14 20:44:54 -03:00
Nix
6226f33de8
Update version.json 2024-12-14 20:15:53 -03:00
Nix
15b1567d3e
better discord rpc 2024-12-14 20:15:30 -03:00
Nix
dc81d4285d
Update picodulce.py 2024-12-10 17:00:35 -03:00
Nix
87fc6e9842
Update version.json 2024-12-10 15:48:04 -03:00
Nix
c59516fc60
keyboard support added 2024-12-10 15:47:38 -03:00
Nix
83d59a6600
keyboard support added 2024-12-10 15:47:25 -03:00
Nix
1e03c986fd
Update version.json 2024-12-09 14:34:12 -03:00
Nix
0ebf31f223
Update picodulce.py 2024-12-09 14:33:44 -03:00
Nix
88cbd449ef
removed nested IF 2024-12-09 14:21:33 -03:00
Nix
5eda9f1de4
Update version.json 2024-12-06 20:24:20 -03:00
Nix
5d586e4b5a
a closer step to fixing the account menu! 2024-12-06 20:23:57 -03:00
Nix
0ea29235cb
changed christmas date to 8 2024-12-04 16:30:22 -03:00
Nix
8ba2aa74a3
Update README.md 2024-12-04 16:27:15 -03:00
Nix
566095978d
Update version.json 2024-11-08 21:31:12 -03:00
Nix
34df942953
fix marroc 2024-11-08 21:30:59 -03:00
Nix
d477e98bd9
Update picodulce.py 2024-10-29 00:28:18 -03:00
Nix
45883d4483
Update version.json 2024-10-28 15:40:50 -03:00
Nix
4ed3d49f6e
Merge pull request #5 from SirOlinad/main
Holiday.Ico update
2024-10-28 15:21:54 -03:00
SirOlinad
4536ca526a
Add files via upload
Holiday Icon for Picodulce!!!!
2024-10-28 15:15:38 -03:00
Nix
c1ecbbc97a
this will be replaced next commit 2024-10-28 14:49:41 -03:00
Nix
7a383875c6
Holiday!!! 2024-10-28 14:48:48 -03:00
Nix
857d0296d4
Update version.json 2024-08-25 00:53:54 -03:00
Nix
ea252215f1
fixed again the same thing 2024-08-25 00:50:15 -03:00
Nix
7917dd0a7b
Update version.json 2024-08-24 23:21:46 -03:00
Nix
593a4566eb
fixed the last version not saving on config file by dumb issue 2024-08-24 23:20:11 -03:00
Nix
6a57a70321
Update version.json 2024-08-13 09:26:55 -03:00
Nix
811caa09ab
added a button in settings to open game directory 2024-08-13 09:25:33 -03:00
Nix
d3ae7302bd
fixed the LastPlayed version not showing 2024-08-13 09:16:19 -03:00
Nix
5e80a215e1
fixed autism 2024-07-05 08:54:19 -03:00
Nix
7a12ac3075
fix the account creation 2024-07-05 08:53:42 -03:00
Nix
2f6858a0c1
Update LICENSE 2024-07-03 14:04:08 -03:00
Nix
7a8aee2681
Update README.md 2024-06-17 21:31:38 -03:00
Nix
9949a9e122
lazy load pypresence 2024-06-12 14:10:14 -03:00
Nix
b817b18331
autism 2024-06-04 20:11:17 -03:00
Nix
b56efa6b1f
Add files via upload 2024-05-29 09:21:31 -03:00
Nix
7793c1876f
Add files via upload 2024-05-29 05:08:04 -03:00
Nix
dbb7d224a2
Update README.md 2024-05-26 00:10:00 -03:00
Nix
293af5b213
Update README.md 2024-05-26 00:07:25 -03:00
Nix
0e6730147d
Update version.json 2024-05-23 21:38:57 -03:00
Nix
9446d6e404
Update picodulce.py 2024-05-23 21:37:19 -03:00
Nix
5709e849d3
Add files via upload 2024-05-15 21:55:51 -03:00
Nix
a047b0142d
added native theme 2024-05-14 23:14:06 -03:00
Nix
70777858f4
Update requirements.txt 2024-05-13 19:44:20 -03:00
Nix
ebc59e4c3e
Update version.json 2024-05-10 16:19:47 -03:00
Nix
ca8f546ac0
Add files via upload 2024-05-10 16:19:32 -03:00
Nix
413839ddbd
Update version.json 2024-05-10 00:14:20 -03:00
Nix
021f31970f
added theme support 2024-05-10 00:14:01 -03:00
Nix
60ef1694b9
Add files via upload 2024-05-09 23:26:33 -03:00
Nix
a1a8667d79
upgraded the gui 2024-05-09 18:57:30 -03:00
Nix
521e61a112
Add files via upload 2024-05-07 00:45:31 -03:00
Nix
4c608d6ab0
fixed bugs 2024-05-07 00:19:07 -03:00
Nix
bb957f07e3
added cool things
added a settings menu, with the option to check updates on start and enable or disable discord RCP
also added discord rcp
2024-05-06 23:29:24 -03:00
Nix
73bb0252f4
Add files via upload 2024-05-06 00:34:28 -03:00
Nix
fbab52db5c
Add files via upload 2024-05-06 00:33:24 -03:00
Nix
154a1dfbad
Add files via upload 2024-05-06 00:32:08 -03:00
Nix
848622a16d
Update version.json 2024-05-06 00:27:27 -03:00
Nix
9c43145e1a
Update version.json 2024-05-06 00:13:58 -03:00
Nix
372733a031
Add files via upload 2024-05-06 00:09:08 -03:00
Nix
9ebec6fb19
added update button 2024-05-06 00:04:59 -03:00
Nix
885913fce7
Update version.json 2024-05-06 00:01:38 -03:00
Nix
cbaaaa1460
Update version.json 2024-05-05 23:58:59 -03:00
Nix
5e0a191555
Update version.json 2024-05-05 23:54:03 -03:00
Nix
d7058326c5
Create version.json 2024-05-05 23:38:36 -03:00
Nix
99c7b42f3c
Delete version 2024-05-05 23:38:08 -03:00
Nix
7ef5dc69c1
Update version 2024-05-05 23:33:00 -03:00
Nix
0a9b9c2966
Create version 2024-05-05 23:24:03 -03:00
Nix
d9b3b976d7
added the auth function 2024-05-03 20:00:28 -03:00
Nix
d7d6b3de4a
Add files via upload 2024-04-25 11:30:33 -03:00
Nix
61f5e3f8dd
Update README.md 2024-04-25 08:44:44 -03:00
Nix
3523325449
Add files via upload 2024-04-25 08:41:49 -03:00
Nix
942daa79ad
fixed things 2024-04-23 21:59:34 -03:00
Nix
ce93c8522e
added the marroc mod manager 2024-04-23 06:43:26 -03:00
Nix
25e1ea4fcf
Add files via upload 2024-04-19 20:59:35 -03:00
Nix
22cbc6915e
Update picodulce.py 2024-04-17 18:53:16 -03:00
Nix
d8e28348be
Update README.md 2024-04-17 18:10:29 -03:00
Nix
65cf81ac4e
added mod loader selection
added remove account button
2024-04-17 17:57:58 -03:00
Nix
fd9dc69096
Delete LICENSE.GPL 2024-04-16 18:59:38 -03:00
Nix
fab4432969
Create LICENSE 2024-04-16 18:59:07 -03:00
Nix
dbc007e951
Update picodulce.py 2024-04-15 23:56:31 -03:00
Nix
b0062b8075
Update README.md 2024-04-15 22:34:34 -03:00
Nix
7696bdff33
Update README.md 2024-04-15 21:21:07 -03:00
17 changed files with 3578 additions and 209 deletions

69
.github/workflows/Build.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Version Change Action
on:
push:
paths:
- version.json # Trigger on changes to version.json
jobs:
version-release:
runs-on: windows-latest # Use Windows 10 runner
if: github.actor != 'github-actions[bot]' # Only run if the actor is not the GitHub Actions bot
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x' # Specify the Python version you need
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyqt5 requests pywin32 pyinstaller pillow # Install specific dependencies
- name: Create actions-temp folder
run: mkdir actions-temp # Create the folder called actions-temp
- name: Download picoBuild.py script
run: curl -L -o actions-temp/picoBuild.py https://raw.githubusercontent.com/nixietab/picodulce-build-script/refs/heads/main/picoBuild.py
- name: Run picoBuild.py script
run: python actions-temp/picoBuild.py
- name: Show directory structure
run: |
dir actions-temp
dir
- name: Get version and name from version.json
id: version_info
run: |
$versionJson = Get-Content version.json | ConvertFrom-Json
echo "RELEASE_NAME=Release $($versionJson.version)" >> $env:GITHUB_ENV
echo "RELEASE_TAG=$($versionJson.version)" >> $env:GITHUB_ENV
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.RELEASE_TAG }}
release_name: ${{ env.RELEASE_NAME }}
body: "This release was created automatically by a GitHub Action."
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/2hsu.exe
asset_name: 2hsu.exe
asset_content_type: application/octet-stream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

76
PKGBUILD Normal file
View File

@ -0,0 +1,76 @@
pkgname=picodulce
pkgver=0.13.5
pkgrel=1
pkgdesc="Launcher for Minecraft based on the zucaro library"
arch=('x86_64')
OPTIONS=(!strip !docs libtool emptydirs)
url="https://github.com/nixietab/picodulce"
license=('MIT')
depends=('python' 'python-virtualenv' 'xdg-utils')
makedepends=('git')
source=("git+https://github.com/nixietab/picodulce.git")
sha256sums=('SKIP')
package() {
cd "$srcdir/$pkgname"
# Create a directory for the application in the user's home directory
install -dm755 "$pkgdir/usr/share/$pkgname"
# Copy all project files to the created directory
cp -r . "$pkgdir/usr/share/$pkgname"
# Create a virtual environment
python -m venv "$pkgdir/usr/share/$pkgname/venv"
# Activate the virtual environment and install dependencies
source "$pkgdir/usr/share/$pkgname/venv/bin/activate"
pip install -r requirements.txt
# Create a run.sh script
install -Dm755 /dev/stdin "$pkgdir/usr/share/$pkgname/run.sh" <<EOF
#!/bin/bash
if [ ! -d "venv" ]; then
echo "venv folder does not exist. Creating virtual environment..."
python3 -m venv venv
source venv/bin/activate
echo "Installing required packages..."
pip install -r requirements.txt
else
source venv/bin/activate
fi
python picodulce.py
EOF
# Make the run.sh script executable
chmod +x "$pkgdir/usr/share/$pkgname/run.sh"
# Create a desktop entry for the application
install -Dm644 /dev/stdin "$pkgdir/usr/share/applications/$pkgname.desktop" <<EOF
[Desktop Entry]
Name=Picodulce
Exec=/usr/share/picodulce/run.sh
Icon=/usr/share/picodulce/launcher_icon.ico
Terminal=true
Type=Application
Comment=Picodulce Launcher
Categories=Game;
EOF
# Ensure the normal user has permission to write to the picodulce folder
chown -R "$USER:$USER" "$pkgdir/usr/share/$pkgname"
chmod -R u+w "$pkgdir/usr/share/$pkgname"
#Install into bin
install -Dm755 /dev/stdin "$pkgdir/usr/bin/picodulce" <<EOF
#!/bin/bash
cd /usr/share/picodulce/
exec ./run.sh
EOF
}
# vim:set ts=2 sw=2 et:

View File

@ -1,12 +1,99 @@
<p align="center">
<img src="https://github.com/nixietab/picodulce/assets/75538775/36fee78f-fb46-400c-8b14-dda5ec6191ef" alt="launcher_icon">
# picodulce
</p>
a launcher for block game made in Qt5 and a GUI for the picomc project.
<h1 align="center">Picodulce Launcher</h1>
it should download all the versions and start them as well. it works offline and online
<p align="center">The simple FOSS launcher you been looking for</p>
![a capture of the launcher](https://i.imgur.com/Ui92vta.png)
<p align="center">
<a href="https://github.com/nixietab/picodulce/releases">
<img src="https://img.shields.io/github/v/release/nixietab/picodulce" alt="Latest Release">
</a>
<a href="https://github.com/nixietab/picodulce/issues">
<img src="https://img.shields.io/github/issues/nixietab/picodulce" alt="Issues">
</a>
<a href="https://github.com/nixietab/picodulce/pulls">
<img src="https://img.shields.io/github/issues-pr/nixietab/picodulce" alt="Pull Requests">
</a>
<a href="https://github.com/nixietab/picodulce/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/nixietab/picodulce" alt="License">
</a>
<a href="https://github.com/nixietab/picodulce">
<img src="https://img.shields.io/github/stars/nixietab/picodulce?style=social" alt="GitHub Stars">
</a>
</p>
Picodulce is a feature-rich launcher for Minecraft, developed using Qt5. It serves as a graphical user interface (GUI) for the [zucaro backend](https://github.com/nixietab/zucaro), providing users with a seamless experience in managing and launching game versions.
![imagen](https://github.com/user-attachments/assets/115b39be-47d3-4ac7-893a-5849c1e4570c)
## Key Features
- **Version Management**: Picodulce is designed to download and launch all available game versions, ensuring users have easy access to the latest updates as well as older versions.
- **Offline and Online Support**: Whether you're connected to Microsoft or not, Picodulce ensures you can still enjoy your game by supporting both offline and online modes.
- **Integrated Mod Manager**: Includes the [Marroc Mod Manager](https://github.com/nixietab/marroc), enabling users to effortlessly manage and customize their game with mods and texturepacks.
- **Custom Theme Support**: Create and apply personalized themes with ease. A dedicated repository and guide are [available to help you get started.](https://github.com/nixietab/picodulce-themes)
# Installation
## Windows
For Windows systems using the [installer](https://github.com/nixietab/picodulce/releases/latest) is recommended
# Linux (Generic)
We have a install script, to use it run:
~~~
curl -sSL https://raw.githubusercontent.com/nixietab/picodulce/refs/heads/main/install-universal.sh | bash
~~~
## Arch Linux
The package is available in the [AUR](https://aur.archlinux.org/packages/picodulce) as ```picodulce```
For installing on Arch without using an AUR helper a PKGBUILD is provided
```
git clone https://github.com/nixietab/picodulce.git
cd picodulce
makepkg -si
```
## Other OS
### 1. Clone the repository
``` git clone https://github.com/nixietab/picodulce ```
### 2. (Optional) Set Up a Virtual Environment
Setting up a virtual environment is recommended to avoid dependency conflicts. Picodulce relies on the path of the `zucaro` project, and using a virtual environment helps prevent errors.
Create the virtual environment:
``` python -m venv venv ```
- **Linux/Mac:**
`source venv/bin/activate`
- **Windows:**
`.\\venv\\Scripts\\activate`
### Install requirements
Now on the venv you can install the requirements safely
```pip install -r requirements.txt ```
### Running the launcher
On the venv run it as a normal python script
```python picodulce.py```
Just make sure you have Java installed for running the actual game, or check the "manage java" option inside the launcher settings
### About the name
pico dulce is the name of a well known argentinean candy and i think is apropiate say that this is a really "sweetened" way of using picomc
The name "Picodulce" comes from a popular Argentinian candy. This reflects the enjoyable and user-friendly experience that the launcher aims to provide, making game management straightforward and pleasant.

390
authser.py Normal file
View File

@ -0,0 +1,390 @@
import sys
import json
import os
import uuid
import asyncio
import aiohttp
from datetime import datetime, timezone
from pathlib import Path
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
QPushButton, QLineEdit, QMessageBox)
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject
from PyQt5.QtGui import QDesktopServices
from zucaro.logging import logger
from zucaro.launcher import get_default_root, Launcher
# Constants for Microsoft Authentication
URL_DEVICE_AUTH = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
URL_TOKEN = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
URL_XBL = "https://user.auth.xboxlive.com/user/authenticate"
URL_XSTS = "https://xsts.auth.xboxlive.com/xsts/authorize"
URL_MC = "https://api.minecraftservices.com/authentication/login_with_xbox"
URL_PROFILE = "https://api.minecraftservices.com/minecraft/profile"
CLIENT_ID = "c52aed44-3b4d-4215-99c5-824033d2bc0f"
SCOPE = "XboxLive.signin offline_access"
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
class AuthDialog(QDialog):
def __init__(self, url, code, parent=None, error_mode=False):
super().__init__(parent)
self.setWindowTitle("Microsoft Authentication")
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.setModal(True)
self.setup_ui(url, code, error_mode)
def setup_ui(self, url, code, error_mode):
layout = QVBoxLayout(self)
if error_mode:
error_label = QLabel("Error in Login - Please try again")
error_label.setStyleSheet("QLabel { color: red; font-weight: bold; }")
layout.addWidget(error_label)
instructions = QLabel(
"To authenticate your Microsoft Account:\n\n"
"1. Click 'Open Authentication Page' or visit:\n"
"2. Copy the code below\n"
"3. Paste the code on the Microsoft website\n"
"4. After completing authentication, click 'I've Completed Authentication'"
)
instructions.setWordWrap(True)
layout.addWidget(instructions)
url_label = QLabel(url)
url_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
url_label.setWordWrap(True)
layout.addWidget(url_label)
self.code_input = QLineEdit(code)
self.code_input.setReadOnly(True)
self.code_input.setAlignment(Qt.AlignCenter)
self.code_input.setStyleSheet("""
QLineEdit {
font-size: 16pt;
font-weight: bold;
padding: 5px;
}
""")
layout.addWidget(self.code_input)
copy_button = QPushButton("Copy Code")
copy_button.clicked.connect(self.copy_code)
layout.addWidget(copy_button)
open_url_button = QPushButton("Open Authentication Page")
open_url_button.clicked.connect(lambda: self.open_url(url))
layout.addWidget(open_url_button)
continue_button = QPushButton("I've Completed Authentication")
continue_button.clicked.connect(self.accept)
layout.addWidget(continue_button)
def copy_code(self):
clipboard = QApplication.clipboard()
clipboard.setText(self.code_input.text())
def open_url(self, url):
QDesktopServices.openUrl(QUrl(url))
class AuthenticationThread(QThread):
auth_data_received = pyqtSignal(dict)
error_occurred = pyqtSignal(str)
auth_error_detected = pyqtSignal(str)
finished = pyqtSignal()
access_token_received = pyqtSignal(dict)
def __init__(self, username):
super().__init__()
self.username = username
self.device_code = None
self.is_running = True
async def _ms_oauth(self):
data = {"client_id": CLIENT_ID, "scope": SCOPE}
async with aiohttp.ClientSession() as session:
async with session.post(URL_DEVICE_AUTH, data=data) as resp:
if resp.status != 200:
raise Exception(f"Failed to get device code: {await resp.text()}")
j = await resp.json()
self.device_code = j["device_code"]
self.auth_data_received.emit({
'url': j["verification_uri"],
'code': j["user_code"]
})
while self.is_running:
data = {
"grant_type": GRANT_TYPE,
"client_id": CLIENT_ID,
"device_code": self.device_code
}
async with session.post(URL_TOKEN, data=data) as resp:
j = await resp.json()
if resp.status == 400:
if j["error"] == "authorization_pending":
await asyncio.sleep(2)
continue
else:
raise Exception(j["error_description"])
elif resp.status != 200:
raise Exception(f"Token request failed: {j}")
return j["access_token"], j["refresh_token"]
raise Exception("Authentication cancelled by user")
async def _xbl_auth(self, access_token):
data = {
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": f"d={access_token}"
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_XBL, json=data) as resp:
if resp.status != 200:
raise Exception(f"XBL auth failed: {await resp.text()}")
j = await resp.json()
return j["Token"], j["DisplayClaims"]["xui"][0]["uhs"]
async def _xsts_auth(self, xbl_token):
data = {
"Properties": {
"SandboxId": "RETAIL",
"UserTokens": [xbl_token]
},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_XSTS, json=data) as resp:
if resp.status != 200:
raise Exception(f"XSTS auth failed: {await resp.text()}")
j = await resp.json()
return j["Token"]
async def _mc_auth(self, uhs, xsts_token):
data = {
"identityToken": f"XBL3.0 x={uhs};{xsts_token}"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_MC, json=data) as resp:
if resp.status != 200:
raise Exception(f"MC auth failed: {await resp.text()}")
j = await resp.json()
return j["access_token"]
async def _get_profile(self, mc_token):
headers = {
"Authorization": f"Bearer {mc_token}"
}
async with aiohttp.ClientSession() as session:
async with session.get(URL_PROFILE, headers=headers) as resp:
if resp.status != 200:
raise Exception(f"Profile request failed: {await resp.text()}")
return await resp.json()
async def _auth_flow(self):
try:
ms_access_token, refresh_token = await self._ms_oauth()
xbl_token, uhs = await self._xbl_auth(ms_access_token)
xsts_token = await self._xsts_auth(xbl_token)
mc_token = await self._mc_auth(uhs, xsts_token)
profile = await self._get_profile(mc_token)
self.access_token_received.emit({
'access_token': mc_token,
'refresh_token': refresh_token,
'profile': profile
})
except Exception as e:
self.error_occurred.emit(str(e))
def run(self):
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self._auth_flow())
except Exception as e:
self.error_occurred.emit(str(e))
finally:
self.finished.emit()
def stop(self):
self.is_running = False
class MinecraftAuthenticator(QObject):
auth_finished = pyqtSignal(bool, str)
def __init__(self, parent=None):
super().__init__(parent)
self.auth_thread = None
self.auth_dialog = None
self.success = False
self.error_message = None
self.username = None
# Initialize the launcher to get the correct config path
with Launcher.new() as launcher:
self.config_path = launcher.root
def authenticate(self, username):
self.username = username
self.success = False
# Create accounts.json if it doesn't exist
if not self.save_to_accounts_json():
self.auth_finished.emit(False, self.error_message)
return
# Check if account is online
if not self.validate_account_type():
self.auth_finished.emit(False, "Cannot authenticate an offline account")
return
self.auth_thread = AuthenticationThread(username)
self.auth_thread.auth_data_received.connect(self.show_auth_dialog)
self.auth_thread.error_occurred.connect(self.show_error)
self.auth_thread.access_token_received.connect(self.on_access_token_received)
self.auth_thread.finished.connect(self.on_authentication_finished)
self.auth_thread.start()
def show_auth_dialog(self, auth_data):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
result = self.auth_dialog.exec_()
if result != QDialog.Accepted:
self.auth_thread.stop()
def show_error(self, error_msg):
self.error_message = error_msg
self.success = False
def validate_account_type(self):
try:
accounts_file = Path(self.config_path) / "accounts.json"
if accounts_file.exists():
with open(accounts_file) as f:
config = json.load(f)
if self.username in config["accounts"]:
return config["accounts"][self.username].get("microsoft", False)
return True # New account, will be created as microsoft
except Exception as e:
logger.error(f"Failed to validate account type: {str(e)}")
return False
def save_to_accounts_json(self):
try:
accounts_file = Path(self.config_path) / "accounts.json"
if accounts_file.exists():
with open(accounts_file) as f:
config = json.load(f)
else:
config = {
"default": None,
"accounts": {},
"client_token": str(uuid.uuid4())
}
accounts_file.parent.mkdir(parents=True, exist_ok=True)
# Only create/update if account doesn't exist
if self.username not in config["accounts"]:
config["accounts"][self.username] = {
"uuid": "-",
"online": True,
"microsoft": True,
"gname": "-",
"access_token": "-",
"refresh_token": "-",
"is_authenticated": False
}
# Set as default if no default exists
if config["default"] is None:
config["default"] = self.username
with open(accounts_file, 'w') as f:
json.dump(config, f, indent=4)
return True
except Exception as e:
logger.error(f"Failed to initialize account data: {str(e)}")
self.error_message = f"Failed to initialize account data: {str(e)}"
return False
def on_access_token_received(self, data):
try:
accounts_file = Path(self.config_path) / "accounts.json"
with open(accounts_file) as f:
config = json.load(f)
if self.username in config["accounts"]:
config["accounts"][self.username].update({
"access_token": data['access_token'],
"refresh_token": data['refresh_token'],
"uuid": data['profile']['id'],
"gname": data['profile']['name'],
"is_authenticated": True
})
with open(accounts_file, 'w') as f:
json.dump(config, f, indent=4)
self.success = True
else:
raise Exception("Account not found in configuration")
except Exception as e:
logger.error(f"Failed to update account data: {str(e)}")
self.error_message = f"Failed to update account data: {str(e)}"
self.success = False
# We don't emit here, we wait for the thread to finish
def on_authentication_finished(self):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
if self.auth_thread:
self.auth_thread.stop()
self.auth_thread = None
if not self.success:
self.auth_finished.emit(False, self.error_message)
else:
self.auth_finished.emit(True, f"Successfully authenticated account: {self.username}")
def cleanup(self):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
if self.auth_thread and self.auth_thread.isRunning():
self.auth_thread.stop()
self.auth_thread.wait()
def create_authenticator():
"""Factory function to create a new MinecraftAuthenticator instance"""
return MinecraftAuthenticator()

231
healthcheck.py Normal file
View File

@ -0,0 +1,231 @@
import os
import json
import shutil
import modulecli
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QLabel, QProgressBar
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import sys
class CopyThread(QThread):
progress_changed = pyqtSignal(int)
finished = pyqtSignal()
def __init__(self, src_dir, dst_dir):
super().__init__()
self.src_dir = src_dir
self.dst_dir = dst_dir
def run(self):
# Gather all files recursively
files_to_copy = []
for root, dirs, files in os.walk(self.src_dir):
for f in files:
full_path = os.path.join(root, f)
relative_path = os.path.relpath(full_path, self.src_dir)
files_to_copy.append(relative_path)
total_files = len(files_to_copy)
copied_files = 0
for relative_path in files_to_copy:
src_path = os.path.join(self.src_dir, relative_path)
dst_path = os.path.join(self.dst_dir, relative_path)
dst_folder = os.path.dirname(dst_path)
if not os.path.exists(dst_folder):
try:
os.makedirs(dst_folder)
except PermissionError:
print(f"Skipping folder {dst_folder} (permission denied)")
continue
try:
shutil.copy2(src_path, dst_path)
except PermissionError:
print(f"Skipping file {dst_path} (permission denied)")
copied_files += 1
progress_percent = int((copied_files / total_files) * 100)
self.progress_changed.emit(progress_percent)
self.finished.emit()
class HealthCheck:
def __init__(self):
self.config = None
def check_config_file(self):
config_path = "config.json"
default_config = {
"IsRCPenabled": False,
"CheckUpdate": False,
"IsBleeding": False,
"LastPlayed": "",
"TotalPlaytime": 0,
"IsFirstLaunch": True,
"Instance": "default",
"Theme": "Dark.json",
"ThemeBackground": True,
"ThemeRepository": "https://raw.githubusercontent.com/nixietab/picodulce-themes/main/repo.json",
"Locale": "en",
"ManageJava": False,
"MaxRAM": "2G",
"JavaPath": "",
"ZucaroCheck": False,
}
if not os.path.exists(config_path):
with open(config_path, "w") as config_file:
json.dump(default_config, config_file, indent=4)
self.config = default_config
return
try:
with open(config_path, "r") as config_file:
self.config = json.load(config_file)
except (json.JSONDecodeError, ValueError):
with open(config_path, "w") as config_file:
json.dump(default_config, config_file, indent=4)
self.config = default_config
return
updated = False
for key, value in default_config.items():
if key not in self.config:
self.config[key] = value
updated = True
if updated:
with open(config_path, "w") as config_file:
json.dump(self.config, config_file, indent=4)
def get_folder_size(self, folder_path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(folder_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if os.path.isfile(fp):
total_size += os.path.getsize(fp)
return total_size
def zucaro_health_check(self):
if self.config.get("ZucaroCheck"):
return
output = modulecli.run_command("instance dir").strip()
instance_dir = os.path.abspath(output)
base_dir = os.path.abspath(os.path.join(instance_dir, "..", ".."))
possible_zucaro = [os.path.join(base_dir, "zucaro"), os.path.join(base_dir, ".zucaro")]
possible_picomc = [os.path.join(base_dir, "picomc"), os.path.join(base_dir, ".picomc")]
zucaro_dir = next((d for d in possible_zucaro if os.path.exists(d)), None)
picomc_dir = next((d for d in possible_picomc if os.path.exists(d)), None)
if picomc_dir is None or zucaro_dir is None:
print("Required directories not found. Skipping copy.")
# Mark the check as done so it wont run again
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
return
picomc_size = self.get_folder_size(picomc_dir)
zucaro_size = self.get_folder_size(zucaro_dir)
if picomc_size <= zucaro_size:
print("No action needed. Zucaro folder is not smaller than Picomc.")
# Update config so the check is considered done
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
return
print(f"Copying Picomc ({picomc_size} bytes) to Zucaro ({zucaro_size} bytes)...")
app = QApplication.instance() or QApplication(sys.argv)
dialog = QDialog()
dialog.setWindowTitle("Working...")
dialog.setWindowModality(Qt.ApplicationModal)
layout = QVBoxLayout()
label = QLabel("Working on stuff, please wait...")
progress = QProgressBar()
progress.setValue(0)
layout.addWidget(label)
layout.addWidget(progress)
dialog.setLayout(layout)
# Setup copy thread
thread = CopyThread(picomc_dir, zucaro_dir)
thread.progress_changed.connect(progress.setValue)
thread.finished.connect(dialog.accept)
thread.start()
dialog.exec_() # Runs the modal event loop
# Mark as done
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
print("Copy completed.")
def themes_integrity(self):
themes_folder = "themes"
dark_theme_file = os.path.join(themes_folder, "Dark.json")
native_theme_file = os.path.join(themes_folder, "Native.json")
dark_theme_content = {
"manifest": {
"name": "Dark",
"description": "The default picodulce launcher theme",
"author": "Nixietab",
"license": "MIT"
},
"palette": {
"Window": "#353535",
"WindowText": "#ffffff",
"Base": "#191919",
"AlternateBase": "#353535",
"ToolTipBase": "#ffffff",
"ToolTipText": "#ffffff",
"Text": "#ffffff",
"Button": "#353535",
"ButtonText": "#ffffff",
"BrightText": "#ff0000",
"Link": "#2a82da",
"Highlight": "#4bb679",
"HighlightedText": "#ffffff"
},
"background_image_base64": ""
}
native_theme_content = {
"manifest": {
"name": "Native",
"description": "The native looks of your OS",
"author": "Your Qt Style",
"license": "Any"
},
"palette": {}
}
if not os.path.exists(themes_folder):
print(f"Creating folder: {themes_folder}")
os.makedirs(themes_folder)
if not os.path.isfile(dark_theme_file):
print(f"Creating file: {dark_theme_file}")
with open(dark_theme_file, "w", encoding="utf-8") as file:
json.dump(dark_theme_content, file, indent=2)
print("Dark.json has been created successfully.")
if not os.path.isfile(native_theme_file):
print(f"Creating file: {native_theme_file}")
with open(native_theme_file, "w", encoding="utf-8") as file:
json.dump(native_theme_content, file, indent=2)
print("Native.json has been created successfully.")
if os.path.isfile(dark_theme_file) and os.path.isfile(native_theme_file):
print("Theme Integrity OK")

BIN
holiday.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

110
install-universal.sh Normal file
View File

@ -0,0 +1,110 @@
#!/bin/bash
set -e
PICODULCE_DIR="$HOME/.picodulce"
GIT_URL="https://github.com/nixietab/picodulce.git"
DESKTOP_FILE="$HOME/.local/share/applications/picodulce.desktop"
BIN_FILE="/usr/bin/picodulce"
# --- Helper functions ---
msg() {
echo -e "\033[1;32m$1\033[0m"
}
err() {
echo -e "\033[1;31m$1\033[0m" >&2
exit 1
}
pause() {
read -rp "Press Enter to continue..."
}
# --- Check dependencies ---
msg "Checking Python3..."
if ! command -v python3 >/dev/null; then
err "Python3 is not installed. Please install it first."
fi
msg "Checking venv module..."
if ! python3 -m venv --help >/dev/null 2>&1; then
err "python3-venv is not available. Please install it."
fi
# --- Clone repo ---
msg "Cloning Picodulce repo..."
rm -rf "$PICODULCE_DIR"
git clone "$GIT_URL" "$PICODULCE_DIR"
# --- Create virtual environment ---
cd "$PICODULCE_DIR"
msg "Creating virtual environment..."
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# --- Create run.sh ---
msg "Creating run.sh..."
cat > "$PICODULCE_DIR/run.sh" <<'EOF'
#!/bin/bash
cd "$(dirname "$0")"
if [ ! -d "venv" ]; then
echo "venv folder does not exist. Creating virtual environment..."
python3 -m venv venv
source venv/bin/activate
echo "Installing required packages..."
pip install -r requirements.txt
else
source venv/bin/activate
fi
exec python picodulce.py
EOF
chmod +x "$PICODULCE_DIR/run.sh"
# --- Create .desktop entry ---
msg "Creating .desktop entry..."
mkdir -p "$(dirname "$DESKTOP_FILE")"
cat > "$DESKTOP_FILE" <<EOF
[Desktop Entry]
Name=Picodulce
Exec=$PICODULCE_DIR/run.sh
Icon=$PICODULCE_DIR/launcher_icon.ico
Terminal=true
Type=Application
Comment=Picodulce Launcher
Categories=Game;
EOF
# --- Ask if install in /usr/bin ---
echo
read -rp "Do you want to install the "picodulce" command? it requires sudo. (y/n) " choice
if [[ "$choice" =~ ^[Yy]$ ]]; then
if [ "$(id -u)" -ne 0 ]; then
echo "Root permissions required to install into /usr/bin"
sudo bash -c "cat > $BIN_FILE" <<EOF
#!/bin/bash
cd $PICODULCE_DIR
exec ./run.sh
EOF
sudo chmod +x "$BIN_FILE"
else
cat > "$BIN_FILE" <<EOF
#!/bin/bash
cd $PICODULCE_DIR
exec ./run.sh
EOF
chmod +x "$BIN_FILE"
fi
msg "Installed 'picodulce' command in /usr/bin"
fi
msg "Installation complete!"
echo "You can run Picodulce with:"
echo " $PICODULCE_DIR/run.sh"
echo "Or from your applications menu."

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

367
loaddaemon.py Normal file
View File

@ -0,0 +1,367 @@
import sys
import threading
import time
import shlex
import gc
import re
from io import StringIO
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QProgressBar, QPushButton, QHBoxLayout
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer, QEvent
from PyQt5.QtGui import QFont
class LaunchSignals(QObject):
log_update = pyqtSignal(str)
launch_complete = pyqtSignal()
launch_aborted = pyqtSignal()
cleanup_done = pyqtSignal()
class AbortException(Exception):
pass
class StreamingCapture(StringIO):
def __init__(self, log_signal, launch_signal):
super().__init__()
self.log_signal = log_signal
self.launch_signal = launch_signal
self.launch_detected = False
self.abort_requested = False
def write(self, text):
if self.abort_requested:
raise AbortException("Launch aborted by user")
if text and text.strip():
# Check if signal is still valid before emitting
try:
self.log_signal.emit(text.strip())
except RuntimeError:
# Signal/Object might be deleted
pass
if not self.launch_detected and self.launch_signal:
lower_text = text.lower()
if "launching" in lower_text and ("game" in lower_text or "version" in lower_text or "minecraft" in lower_text):
self.launch_detected = True
try:
self.launch_signal.emit()
except RuntimeError:
pass
return super().write(text)
class LaunchWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Launching Minecraft")
self.setModal(True)
self.resize(400, 150)
layout = QVBoxLayout()
self.status_label = QLabel("Initializing...")
self.status_label.setWordWrap(True)
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 0) # Indeterminate progress
self.progress_bar.setTextVisible(False)
layout.addWidget(self.progress_bar)
# Add a manual close/cancel button
button_layout = QHBoxLayout()
button_layout.addStretch()
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.request_abort)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
self.setLayout(layout)
self.signals = LaunchSignals()
self.signals.log_update.connect(self.update_status)
self.signals.launch_complete.connect(self.on_launch_complete)
self.signals.launch_aborted.connect(self.on_launch_aborted)
self.signals.cleanup_done.connect(self.on_cleanup_done)
self.launch_detected = False
self.closing_scheduled = False
self.aborting = False
self.capture_streams = []
self.thread_running = False
def update_status(self, text):
if len(text) > 100:
text = text[:97] + "..."
self.status_label.setText(text)
def on_launch_complete(self):
if not self.closing_scheduled and not self.aborting:
self.closing_scheduled = True
self.status_label.setText("Game Launched! Closing window...")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(100)
self.cancel_button.setEnabled(False)
QTimer.singleShot(3000, self.accept)
def on_launch_aborted(self):
self.status_label.setText("Launch Aborted.")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
def on_cleanup_done(self):
self.thread_running = False
# Now it is safe to close the window
super().reject()
def request_abort(self):
if self.thread_running and not self.aborting:
self.aborting = True
self.status_label.setText("Aborting...")
self.cancel_button.setEnabled(False)
# Signal streams to stop
for stream in self.capture_streams:
stream.abort_requested = True
elif not self.thread_running:
super().reject()
def reject(self):
# Handle Esc key or X button
self.request_abort()
def closeEvent(self, event):
if self.thread_running:
event.ignore()
self.request_abort()
else:
event.accept()
def launch_game(self, command):
self.thread_running = True
thread = threading.Thread(target=self._run_launch, args=(command,), daemon=True)
thread.start()
def _run_launch(self, command):
try:
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
from zucaro.cli.main import zucaro_cli
old_stdout, old_stderr = sys.stdout, sys.stderr
stdout_capture = StreamingCapture(self.signals.log_update, self.signals.launch_complete)
stderr_capture = StreamingCapture(self.signals.log_update, self.signals.launch_complete)
self.capture_streams = [stdout_capture, stderr_capture]
sys.stdout = stdout_capture
sys.stderr = stderr_capture
try:
zucaro_cli.main(args=shlex.split(command), standalone_mode=False)
except AbortException:
self.signals.launch_aborted.emit()
except SystemExit:
pass
except Exception as e:
self.signals.log_update.emit(f"Error: {str(e)}")
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
if not stdout_capture.launch_detected and not stderr_capture.launch_detected and not stdout_capture.abort_requested:
self.signals.launch_complete.emit()
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
self.signals.cleanup_done.emit()
except Exception as e:
try:
self.signals.log_update.emit(f"Error launching game: {str(e)}")
self.signals.cleanup_done.emit()
except:
pass
class PrepareWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Preparing Version")
self.setModal(True)
self.resize(400, 150)
layout = QVBoxLayout()
self.status_label = QLabel("Initializing...")
self.status_label.setWordWrap(True)
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 0) # Indeterminate progress
self.progress_bar.setTextVisible(False)
layout.addWidget(self.progress_bar)
# Add a manual close/cancel button
button_layout = QHBoxLayout()
button_layout.addStretch()
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.request_abort)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
self.setLayout(layout)
self.signals = LaunchSignals()
self.signals.log_update.connect(self.update_status)
self.signals.launch_complete.connect(self.on_prepare_complete)
self.signals.launch_aborted.connect(self.on_prepare_aborted)
self.signals.cleanup_done.connect(self.on_cleanup_done)
self.aborting = False
self.capture_streams = []
self.thread_running = False
self.success = False
def update_status(self, text):
# Parse output for progress updates
if "Downloading" in text and "libraries" in text:
try:
count = int(re.search(r'\d+', text).group())
self.status_label.setText(f"Downloading {count} libraries...")
except:
self.status_label.setText(text)
elif "Checking" in text and "assets" in text:
try:
count = int(re.search(r'\d+', text).group())
self.status_label.setText(f"Checking {count} assets...")
except:
self.status_label.setText(text)
elif "Jar file" in text and "downloaded" in text:
self.status_label.setText("Downloading game jar...")
elif "Checking libraries" in text:
self.status_label.setText("Checking libraries...")
else:
if len(text) > 100:
text = text[:97] + "..."
self.status_label.setText(text)
def on_prepare_complete(self):
if not self.aborting:
self.success = True
self.status_label.setText("Version prepared successfully!")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(100)
self.cancel_button.setEnabled(False)
QTimer.singleShot(1500, self.accept)
def on_prepare_aborted(self):
self.status_label.setText("Preparation Aborted.")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.success = False
def on_cleanup_done(self):
self.thread_running = False
if not self.success:
super().reject()
def request_abort(self):
if self.thread_running and not self.aborting:
self.aborting = True
self.status_label.setText("Aborting...")
self.cancel_button.setEnabled(False)
# Signal streams to stop
for stream in self.capture_streams:
stream.abort_requested = True
elif not self.thread_running:
super().reject()
def reject(self):
self.request_abort()
def closeEvent(self, event):
if self.thread_running:
event.ignore()
self.request_abort()
else:
event.accept()
def prepare_version(self, version):
command = f"version prepare {version}"
self.thread_running = True
thread = threading.Thread(target=self._run_prepare, args=(command,), daemon=True)
thread.start()
def _run_prepare(self, command):
try:
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
from zucaro.cli.main import zucaro_cli
old_stdout, old_stderr = sys.stdout, sys.stderr
stdout_capture = StreamingCapture(self.signals.log_update, None)
stderr_capture = StreamingCapture(self.signals.log_update, None)
self.capture_streams = [stdout_capture, stderr_capture]
sys.stdout = stdout_capture
sys.stderr = stderr_capture
try:
zucaro_cli.main(args=shlex.split(command), standalone_mode=False)
except AbortException:
self.signals.launch_aborted.emit()
except SystemExit:
pass
except Exception as e:
self.signals.log_update.emit(f"Error: {str(e)}")
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
if not stdout_capture.abort_requested:
self.signals.launch_complete.emit()
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
self.signals.cleanup_done.emit()
except Exception as e:
try:
self.signals.log_update.emit(f"Error preparing version: {str(e)}")
self.signals.cleanup_done.emit()
except:
pass
def launch_instance_with_window(command, parent=None):
window = LaunchWindow(parent)
window.launch_game(command)
window.exec_()
return window
def prepare_version_with_window(version, parent=None):
window = PrepareWindow(parent)
window.prepare_version(version)
result = window.exec_()
return window.success

BIN
marroc.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

416
marroc.py Normal file
View File

@ -0,0 +1,416 @@
import sys
import os
import shutil
import json
import threading
import requests
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem, QMessageBox, QComboBox, QDialog, QTabWidget, QMainWindow, QSpacerItem, QSizePolicy
from PyQt5.QtCore import Qt, QSize, QObject, pyqtSignal
from PyQt5.QtGui import QIcon, QPalette, QColor, QPixmap
CONFIG_FILE = "config.json"
class IconLoader(QObject, threading.Thread):
icon_loaded = pyqtSignal(QPixmap)
def __init__(self, icon_url):
super().__init__()
threading.Thread.__init__(self)
self.icon_url = icon_url
def run(self):
try:
response = requests.get(self.icon_url)
if response.status_code == 200:
pixmap = QPixmap()
pixmap.loadFromData(response.content)
self.icon_loaded.emit(pixmap.scaled(QSize(42, 42), Qt.KeepAspectRatio, Qt.SmoothTransformation))
else:
self.icon_loaded.emit(QPixmap("missing.png"))
except Exception as e:
print("Error loading icon:", e)
self.icon_loaded.emit(QPixmap("missing.png"))
class ModrinthSearchApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Marroc Mod Manager")
self.setGeometry(100, 100, 500, 400)
self.ensure_directories_exist()
layout = QVBoxLayout()
tab_widget = QTabWidget()
self.search_tab = QWidget()
self.mods_tab = QWidget()
tab_widget.addTab(self.search_tab, "Search")
tab_widget.addTab(self.mods_tab, "Manager")
self.init_search_tab()
self.init_mods_tab()
layout.addWidget(tab_widget)
self.setLayout(layout)
def keyPressEvent(self, event):
focus_widget = self.focusWidget()
if event.key() == Qt.Key_Down:
self.focusNextChild() # Move focus to the next widget
elif event.key() == Qt.Key_Up:
self.focusPreviousChild() # Move focus to the previous widget
elif event.key() in [Qt.Key_Return, Qt.Key_Enter]:
if isinstance(focus_widget, QPushButton):
focus_widget.click() # Trigger the button click
elif isinstance(focus_widget, QComboBox):
focus_widget.showPopup() # Show dropdown for combo box
else:
super().keyPressEvent(event)
def ensure_directories_exist(self):
directories = ["marroc/mods", "marroc/resourcepacks"]
for directory in directories:
if not os.path.exists(directory):
os.makedirs(directory)
def init_search_tab(self):
layout = QVBoxLayout()
search_layout = QHBoxLayout()
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Enter a search term...")
search_layout.addWidget(self.search_input)
self.search_button = QPushButton("Search")
self.search_button.clicked.connect(self.search_mods)
search_layout.addWidget(self.search_button)
self.search_type_dropdown = QComboBox()
self.search_type_dropdown.addItems(["Mod", "Texture Pack"])
search_layout.addWidget(self.search_type_dropdown)
layout.addLayout(search_layout)
self.mods_list = QListWidget()
layout.addWidget(self.mods_list)
self.select_button = QPushButton("Select")
self.select_button.clicked.connect(self.show_mod_details_window)
layout.addWidget(self.select_button)
self.selected_mod = None
self.search_tab.setLayout(layout)
def init_mods_tab(self):
layout = QVBoxLayout()
self.mod_manager_window = ModManagerWindow()
layout.addWidget(self.mod_manager_window)
self.mods_tab.setLayout(layout)
def search_mods(self):
self.mods_list.clear()
mod_name = self.search_input.text()
search_type = self.search_type_dropdown.currentText().lower()
if search_type == "texture pack":
api_url = f"https://api.modrinth.com/v2/search?query={mod_name}&limit=20&facets=%5B%5B%22project_type%3Aresourcepack%22%5D%5D"
else:
api_url = f"https://api.modrinth.com/v2/search?query={mod_name}&limit=20&facets=%5B%5B%22project_type%3A{search_type}%22%5D%5D"
response = requests.get(api_url)
if response.status_code == 200:
mods_data = json.loads(response.text)
for mod in mods_data['hits']:
mod_name = mod['title']
mod_description = mod['description']
icon_url = mod['icon_url']
item = QListWidgetItem(f"Title: {mod_name}\nDescription: {mod_description}")
item.setSizeHint(QSize(200, 50))
icon_loader = IconLoader(icon_url)
icon_loader.icon_loaded.connect(lambda pixmap, item=item: self.set_item_icon(item, pixmap))
icon_loader.start()
item.mod_data = mod
self.mods_list.addItem(item)
else:
self.mods_list.addItem("Failed to fetch mods. Please try again later.")
def set_item_icon(self, item, pixmap):
if pixmap:
item.setData(Qt.DecorationRole, pixmap)
else:
# Set a default icon if loading failed
item.setIcon(QIcon("missing.png"))
def show_mod_details_window(self):
selected_item = self.mods_list.currentItem()
if selected_item is not None:
mod_data = selected_item.mod_data
mod_slug = mod_data.get('slug')
if mod_slug:
api_url = f"https://api.modrinth.com/v2/project/{mod_slug}"
response = requests.get(api_url)
if response.status_code == 200:
mod_info = json.loads(response.text)
icon_url = mod_info.get('icon_url')
mod_versions = self.get_mod_versions(mod_slug)
mod_details_window = ModDetailsWindow(mod_data, icon_url, mod_versions)
mod_details_window.exec_()
else:
QMessageBox.warning(self, "Failed to Fetch Mod Details", "Failed to fetch mod details. Please try again later.")
else:
QMessageBox.warning(self, "No Mod Slug", "Selected mod has no slug.")
else:
QMessageBox.warning(self, "No Mod Selected", "Please select a mod first.")
def get_mod_versions(self, mod_slug):
api_url = f"https://api.modrinth.com/v2/project/{mod_slug}/version"
response = requests.get(api_url)
if response.status_code == 200:
versions = json.loads(response.text)
mod_versions = []
for version in versions:
version_name = version['name']
version_files = version.get('files', [])
if version_files:
file_urls = [file['url'] for file in version_files]
mod_versions.append({'version': version_name, 'files': file_urls})
else:
mod_versions.append({'version': version_name, 'files': []})
return mod_versions
else:
return []
class ModManagerWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Mod Manager")
self.setGeometry(100, 100, 600, 400)
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QHBoxLayout(self.central_widget)
self.file_type_combo_box = QComboBox()
self.file_type_combo_box.addItems(["Mods", "Resource Packs"])
self.file_type_combo_box.currentIndexChanged.connect(self.load_files)
self.available_files_widget = QListWidget()
self.installed_files_widget = QListWidget()
self.button_dropdown_layout = QVBoxLayout()
self.button_dropdown_layout.addWidget(self.file_type_combo_box)
self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
self.move_right_button = QPushButton(">")
self.move_right_button.clicked.connect(self.move_right)
self.button_dropdown_layout.addWidget(self.move_right_button)
self.move_left_button = QPushButton("<")
self.move_left_button.clicked.connect(self.move_left)
self.button_dropdown_layout.addWidget(self.move_left_button)
self.delete_button = QPushButton("Delete")
self.delete_button.clicked.connect(self.delete_selected_item)
self.button_dropdown_layout.addWidget(self.delete_button)
self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
self.layout.addWidget(self.available_files_widget)
self.layout.addLayout(self.button_dropdown_layout)
self.layout.addWidget(self.installed_files_widget)
self.load_files()
def load_files(self):
file_type = self.file_type_combo_box.currentText()
if file_type == "Mods":
self.load_mods()
elif file_type == "Resource Packs":
self.load_resource_packs()
def load_mods(self):
mods_directory = "marroc/mods"
if os.path.exists(mods_directory) and os.path.isdir(mods_directory):
mods = os.listdir(mods_directory)
self.available_files_widget.clear()
self.available_files_widget.addItems(mods)
self.load_installed_mods("mods")
def load_resource_packs(self):
resource_packs_directory = "marroc/resourcepacks"
if os.path.exists(resource_packs_directory) and os.path.isdir(resource_packs_directory):
resource_packs = os.listdir(resource_packs_directory)
self.available_files_widget.clear()
self.available_files_widget.addItems(resource_packs)
self.load_installed_mods("resourcepacks")
def load_installed_mods(self, file_type):
if sys.platform.startswith('linux'):
minecraft_directory = os.path.expanduser("~/.local/share/picomc/instances/default/minecraft")
elif sys.platform.startswith('win'):
minecraft_directory = os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft')
else:
minecraft_directory = ""
if minecraft_directory:
installed_files_directory = os.path.join(minecraft_directory, file_type)
if os.path.exists(installed_files_directory) and os.path.isdir(installed_files_directory):
installed_files = os.listdir(installed_files_directory)
self.installed_files_widget.clear()
self.installed_files_widget.addItems(installed_files)
def move_right(self):
selected_item = self.available_files_widget.currentItem()
if selected_item:
source_directory = self.get_source_directory()
destination_directory = self.get_destination_directory()
file_name = selected_item.text()
source_path = os.path.join(source_directory, file_name)
destination_path = os.path.join(destination_directory, file_name)
shutil.move(source_path, destination_path)
self.load_files()
def move_left(self):
selected_item = self.installed_files_widget.currentItem()
if selected_item:
source_directory = self.get_destination_directory()
destination_directory = self.get_source_directory()
file_name = selected_item.text()
source_path = os.path.join(source_directory, file_name)
destination_path = os.path.join(destination_directory, file_name)
shutil.move(source_path, destination_path)
self.load_files()
def delete_selected_item(self):
selected_item = self.available_files_widget.currentItem() or self.installed_files_widget.currentItem()
if selected_item:
file_name = selected_item.text()
reply = QMessageBox.question(self, 'Delete Item', f'Are you sure you want to delete "{file_name}"?',
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
file_type = self.file_type_combo_box.currentText()
if file_type == "Mods":
directory = "marroc/mods"
elif file_type == "Resource Packs":
directory = "marroc/resourcepacks"
else:
return
file_path = os.path.join(directory, file_name)
if os.path.exists(file_path):
os.remove(file_path)
self.load_files()
else:
QMessageBox.warning(self, 'File Not Found', 'The selected file does not exist.')
def get_source_directory(self):
file_type = self.file_type_combo_box.currentText()
if file_type == "Mods":
return "marroc/mods"
elif file_type == "Resource Packs":
return "marroc/resourcepacks"
else:
return ""
def get_destination_directory(self):
file_type = self.file_type_combo_box.currentText()
if file_type == "Mods":
if sys.platform.startswith('linux'):
return os.path.expanduser("~/.local/share/picomc/instances/default/minecraft/mods")
elif sys.platform.startswith('win'):
return os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft/mods')
elif file_type == "Resource Packs":
if sys.platform.startswith('linux'):
return os.path.expanduser("~/.local/share/picomc/instances/default/minecraft/resourcepacks")
elif sys.platform.startswith('win'):
return os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft/resourcepacks')
else:
return ""
class ModDetailsWindow(QDialog):
def __init__(self, mod_data, icon_url, mod_versions):
super().__init__()
self.setWindowTitle("Mod Details")
self.setGeometry(100, 100, 400, 300)
self.mod_data = mod_data
layout = QVBoxLayout()
mod_name_label = QLabel(f"<h2>{mod_data['title']}</h2>")
mod_name_label.setAlignment(Qt.AlignCenter)
layout.addWidget(mod_name_label)
mod_description_label = QLabel(mod_data['description'])
mod_description_label.setWordWrap(True)
layout.addWidget(mod_description_label)
icon_pixmap = self.load_icon(icon_url)
icon_label = QLabel()
if icon_pixmap:
icon_label.setPixmap(icon_pixmap)
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
self.version_dropdown = QComboBox()
for version in mod_versions:
self.version_dropdown.addItem(version['version'])
self.version_dropdown.setItemData(self.version_dropdown.count() - 1, version['files'], Qt.UserRole)
layout.addWidget(self.version_dropdown)
self.download_button = QPushButton("Download")
self.download_button.clicked.connect(self.download_mod)
layout.addWidget(self.download_button)
self.download_url_label = QLabel()
self.download_url_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.download_url_label)
layout.addStretch(1)
self.setLayout(layout)
def load_icon(self, icon_url):
try:
response = requests.get(icon_url)
if response.status_code == 200:
pixmap = QPixmap()
pixmap.loadFromData(response.content)
return pixmap.scaled(QSize(128, 128), Qt.KeepAspectRatio, Qt.SmoothTransformation)
else:
return None
except Exception as e:
print("Error loading icon:", e)
return None
def download_mod(self):
selected_version_index = self.version_dropdown.currentIndex()
selected_version_files = self.version_dropdown.itemData(selected_version_index, Qt.UserRole)
if selected_version_files:
for file_url in selected_version_files:
filename = os.path.basename(file_url)
try:
response = requests.get(file_url)
response.raise_for_status()
save_dir = "marroc/mods" if filename.endswith('.jar') else "marroc/resourcepacks"
with open(os.path.join(save_dir, filename), 'wb') as f:
f.write(response.content)
QMessageBox.information(self, "Download Mod", f"Downloaded {filename} successfully.")
return
except requests.exceptions.RequestException as e:
QMessageBox.warning(self, "Download Error", f"Error downloading mod: {e}")
return
QMessageBox.warning(self, "Download Mod", "Failed to download the mod.")
if __name__ == "__main__":
app = QApplication(sys.argv)
app_icon = QIcon('marroc.ico')
app.setWindowIcon(app_icon)
window = ModrinthSearchApp()
window.show()
sys.exit(app.exec_())

BIN
missing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

46
modulecli.py Normal file
View File

@ -0,0 +1,46 @@
from io import StringIO
import sys
import shlex
import gc
def run_command(command="zucaro"):
# Remove all zucaro-related modules from sys.modules BEFORE import
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
# Import zucaro_cli dynamically
from zucaro.cli.main import zucaro_cli
# Redirect stdout and stderr to capture the command output
old_stdout, old_stderr = sys.stdout, sys.stderr
sys.stdout = mystdout = StringIO()
sys.stderr = mystderr = StringIO()
try:
# Use shlex.split to properly parse the command string
# This will call Click's CLI as if from command line, using args
zucaro_cli.main(args=shlex.split(command))
except SystemExit as e:
if e.code != 0:
print(f"Command exited with code {e.code}", file=sys.stderr)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
finally:
# Restore stdout and stderr
sys.stdout = old_stdout
sys.stderr = old_stderr
output = mystdout.getvalue().strip()
error = mystderr.getvalue().strip()
# Cleanup: remove zucaro-related modules from sys.modules and force garbage collection
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
if not output:
return None
return output

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,6 @@
picomc
zucaro
PyQt5
requests
aiohttp
pypresence
tqdm

15
version.json Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.13.10",
"links": [
"https://raw.githubusercontent.com/nixietab/picodulce/main/version.json",
"https://raw.githubusercontent.com/nixietab/picodulce/main/picodulce.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/requirements.txt",
"https://raw.githubusercontent.com/nixietab/picodulce/main/marroc.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico",
"https://raw.githubusercontent.com/nixietab/picodulce/main/authser.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/healthcheck.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/modulecli.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/loaddaemon.py"
],
"versionBleeding": "0.13.3-212"
}