Compare commits

..

529 Commits

Author SHA1 Message Date
Petr Mrázek
80beccb2c4 NOISSUE Add small workaround for presenting Japanese with Kanji 2021-12-08 01:23:12 +01:00
Petr Mrázek
dba4c452e0 NOISSUE account tweaks 2021-12-08 01:22:57 +01:00
Petr Mrázek
90a62c429a NOISSUE remove the hardcoded blocking of Forge installs
Doesn't make it work, but makes it theoretically possible.
2021-12-06 22:07:41 +01:00
Petr Mrázek
a5581b479e NOISSUE fix launching offline while online 2021-12-06 20:17:31 +01:00
Petr Mrázek
825ef52dd5 NOISSUE fix up some corner cases around migrating accounts 2021-12-06 20:16:13 +01:00
Petr Mrázek
3a940ffb52 NOISSUE Add cat xcf file 2021-12-06 02:40:53 +01:00
Petr Mrázek
70d400f205 NOISSUE party hat for a party cat
On the 30th of November, it's been 10 years since the first
(documented) release of MultiMC. Party hats for everyone :)
2021-12-05 23:26:04 +01:00
Petr Mrázek
cd513c02c4 NOISSUE bump version and update the changelog 2021-12-05 22:06:01 +01:00
Petr Mrázek
c8ca6acc15 NOISSUE fix some error mappings for Mojang accounts 2021-12-05 03:48:07 +01:00
Petr Mrázek
d37003b1de NOISSUE fix builds, make account refresh queue user friendly 2021-12-04 02:10:14 +01:00
Petr Mrázek
db6431d9e0 NOISSUE add missing chrono include 2021-12-04 01:27:58 +01:00
Petr Mrázek
3c46d8a412 GH-4071 Heavily refactor and rearchitect account system
This makes the account system much more modular
and makes it treat errors as something recoverable,
unless they come directly from the MSA refresh token
becoming invalid.
2021-12-04 01:18:05 +01:00
Petr Mrázek
ffcef673de Merge pull request #4285 from khenriks/develop
Fix instructions for building on Mac
2021-11-30 20:24:19 +01:00
Petr Mrázek
241086883e Merge pull request #4230 from NewoIsTaken/patch-1
GH-4224 Scan 64 bit lib directory to find Java
2021-11-28 21:45:36 +01:00
K Henriksson
20eada7bbe Fix instructions for building on Mac
Relative paths and .. components don't work with CMake's bundle support.

The also adds a note about potential code signing problems.
2021-11-28 10:44:51 -08:00
Petr Mrázek
859d710581 GH-4071 handle invalid MSA refresh token as a hard error 2021-11-28 19:01:21 +01:00
Petr Mrázek
285188ea53 GH-4071 handle network errors when logging in with MSA as 'soft'
This makes the tokens not expire when such errors happen.

Only applies to MSA, not the XBox and Mojang steps afterwards.
Further testing and improvements are still needed.
2021-11-28 18:42:01 +01:00
Petr Mrázek
0e31f77468 Merge pull request #4283 from khenriks/fixicon
Install launcher icns to correct file name
2021-11-28 13:10:15 +01:00
K Henriksson
024f5952ce Install launcher icns to correct file name 2021-11-25 15:14:28 -08:00
Petr Mrázek
a522cad6d6 NOISSUE also undo the AUTORCC change because it was also broken
It broke 32bit linux builds...
2021-11-23 01:37:13 +01:00
Petr Mrázek
b49987e876 NOISSUE fix fix fix the accounts again 2021-11-23 01:25:24 +01:00
Petr Mrázek
27e328c044 NOISSUE Do not rely on AUTOUIC
It is bugged and does not detect changes in .ui files, which makes development painful.
2021-11-22 14:29:38 +01:00
Petr Mrázek
b258eac215 NOISSUE continue reshuffling the codebase 2021-11-22 03:55:16 +01:00
Petr Mrázek
5040231f8d NOISSUE fix build on macOS 2021-11-21 23:42:55 +01:00
Petr Mrázek
9fc677c2a4 NOISSUE more refactoring 2021-11-21 23:36:55 +01:00
Petr Mrázek
69213b1206 NOISSUE continue refactoring things to make tests pass 2021-11-21 23:21:12 +01:00
Petr Mrázek
c2c56a2f6c NOISSUE fix build 2021-11-20 17:08:34 +01:00
Petr Mrázek
0c861db7a2 NOISSUE Some happy little refactors 2021-11-20 16:22:22 +01:00
Petr Mrázek
eafeb64dec NOISSUE qnam -> network 2021-11-17 13:20:50 +01:00
Petr Mrázek
0022aed8bb Merge pull request #4254 from jamierocks/java-17-requirement
NOISSUE Error on launch when launching 1.18 with < Java 17
2021-11-16 19:33:36 +01:00
Jamie Mansfield
014e65220e NOISSUE Error on launch when launching 1.18 with < Java 17 2021-11-16 18:25:16 +00:00
Petr Mrázek
6c82883206 NOISSUE fix account re-adding not updating current account 2021-11-16 02:21:59 +01:00
Petr Mrázek
25fbeb265a NOISSUE fix build some more 2021-11-10 03:16:04 +01:00
Petr Mrázek
30d5a7ab48 NOISSUE fix build 2021-11-10 03:12:01 +01:00
Petr Mrázek
475d949a1e GH-4217 Add support for GamePass accounts and MC profile setup
- We now use the new endpoint for loggiong in via XBox tokens (/launcher/login)
- We now check game entitlements instead of only relying on MC profile presence
- Accounts can now be added even when they do not have a profile
- The launcher will guide you through selecting a Minecraft name if you don't have one yet
2021-11-10 03:02:51 +01:00
Petr Mrázek
32f9c61c6e Merge pull request #4233 from ImperatorStorm/update-man
NOISSUE Update manpage to include new `-a / --profile` option.
2021-11-08 20:05:04 +01:00
Petr Mrázek
86f6a3e751 Merge pull request #4231 from NewoIsTaken/patch-2
GH-4200 Search Eclipse Foundation and Adoptium in Registry
2021-11-08 20:03:08 +01:00
ImperatorStorm
9ccce62f50 NOISSUE Update manpage to include new -a / --profile option. 2021-11-07 20:48:15 -08:00
Owen Wang
0660768478 GH-4200 Search Eclipse Foundation and Adoptium in Registry 2021-11-07 21:35:43 -05:00
Owen Wang
b1beeee11f GH-4224 Scan 64 bit lib directory to find Java 2021-11-07 21:19:35 -05:00
Petr Mrázek
30602363d7 Merge pull request #4229 from Janrupf/develop
GH-4227 Don't crash when mods.toml is invalid
2021-11-06 23:07:23 +01:00
Janrupf
0423464b88 GH-4227 Don't blindly trust mods.toml to be valid 2021-11-06 22:43:16 +01:00
Janrupf
2576a28f73 NOISSUE Exclude run directory 2021-11-06 22:20:26 +01:00
Petr Mrázek
7b4c52e1e3 NOISSUE fix some small build issues 2021-11-03 15:45:42 +01:00
Petr Mrázek
27f276ef13 GH-1795 add terminal launch option to use a specific Minecraft profile
Used like this:
```
./MultiMC --launch 1.17.1 --profile MultiMCTest --server mc.hypixel.net
```
2021-10-31 21:43:33 +01:00
Petr Mrázek
393d17b8d3 GH-4164 set instance drag image hotspot based on where the drag started 2021-10-27 10:25:18 +02:00
Petr Mrázek
ae4939e0d2 GH-4164 Assign instances to groups using drag & drop 2021-10-25 23:51:42 +02:00
Petr Mrázek
85ecbad467 GH-3490 sort instances by name is now aware of numbers 2021-10-25 21:43:00 +02:00
Petr Mrázek
040af58070 NOISSUE add more logging when java checker fails to start 2021-10-25 12:01:08 +02:00
Petr Mrázek
f11d68e2a2 Merge pull request #4197 from Sebastian-byte/patch-1
NOISSUE Fix typo making Windows build fail
2021-10-25 05:48:27 +02:00
Sebastian
73995106d3 NOISSUE Fix typo making Windows build fail 2021-10-24 18:16:00 -05:00
Petr Mrázek
6f6c9c6f68 NOISSUE fix debranding changes affecting version file format 2021-10-24 12:25:44 +02:00
Petr Mrázek
7f28f0bf01 Merge pull request #4186 from ImperatorStorm/addman
MultiMCGH-4120 Add manpage to ubuntu and rpm packages
2021-10-24 02:51:37 +02:00
Petr Mrázek
45520129d9 Merge pull request #4193 from jamierocks/modpack-name-version
Include modpack version in the default instance title
2021-10-24 02:03:03 +02:00
Jamie Mansfield
41d855fd11 GH-4185 Include the modpack version in instance title for ATLauncher 2021-10-24 00:39:42 +01:00
Jamie Mansfield
4b13577a59 GH-4185 Include the modpack version in instance title for modpacks.ch 2021-10-24 00:39:02 +01:00
Jamie Mansfield
c377ad6025 NOISSUE Use MiB suffix for memory and permgen 2021-10-24 00:26:37 +01:00
Jamie Mansfield
a6df7d709f NOISSUE Correct spelling of miscellaneous 2021-10-24 00:26:16 +01:00
Petr Mrázek
cbe1b3353c Merge pull request #4172 from jamierocks/global-playtime
NOISSUE Add option to disable global play time status
2021-10-24 01:21:26 +02:00
Jamie Mansfield
2e60413f7f NOISSUE Add option to disable global play time status 2021-10-24 00:17:07 +01:00
Petr Mrázek
98887911c1 Merge pull request #4173 from jamierocks/common-time-duration-format
NOISSUE Use common duration format for global and instances
2021-10-24 01:11:52 +02:00
Jamie Mansfield
5bc6dd8f97 NOISSUE Remove lingering full stop in playtime status 2021-10-24 00:11:09 +01:00
Jamie Mansfield
7cbca6ab20 NOISSUE Use common duration format for global and instances 2021-10-24 00:09:21 +01:00
Petr Mrázek
ddf98c59f5 Merge pull request #4184 from kb-1000/update-repo-links
NOISSUE Update repository links
2021-10-24 01:09:11 +02:00
Petr Mrázek
110c73edf2 Merge pull request #4178 from jamierocks/qt-clear-button
NOISSUE Use Qt's clear button for ATLauncher page
2021-10-24 01:01:58 +02:00
Petr Mrázek
e6cb7b7710 Merge pull request #4177 from jamierocks/mch-search
NOISSUE Fix modpacks.ch search
2021-10-24 01:01:38 +02:00
ImperatorStorm
aed8d5f8f5 MultiMCGH-4120 Add manpage to ubuntu and rpm packages 2021-10-21 19:17:51 -07:00
Petr Mrázek
8bc6560b5e NOISSUE fix translations 2021-10-21 23:27:01 +02:00
Petr Mrázek
7c86732a47 NOISSUE Update links to repo, fix up library README 2021-10-21 23:25:52 +02:00
Petr Mrázek
a8458e36ff NOISSUE remove extra file 2021-10-21 02:07:29 +02:00
kb1000
8bc5b5a01f NOISSUE Update repository links 2021-10-21 01:46:13 +02:00
Petr Mrázek
264d3017aa NOISSUE update notsecrets README.md 2021-10-21 01:11:08 +02:00
Petr Mrázek
d9b46289a1 NOISSUE finish up the obvious parts of debranding
This is not absolutely complete, but reasonably so
2021-10-21 00:47:53 +02:00
Petr Mrázek
5b3dffce62 NOISSUE continue debranding... 2021-10-21 00:47:53 +02:00
Petr Mrázek
297d4b4196 NOISSUE continue the debranding 2021-10-21 00:47:53 +02:00
Petr Mrázek
e12a769800 NOISSUE fix windows rc file and mac icon 2021-10-21 00:47:53 +02:00
Petr Mrázek
f39c313c5f NOISSUE fix _ICONFIX_EXPORT 2021-10-21 00:47:53 +02:00
Petr Mrázek
462a44b4be GH-4011 fix license text missing from About dialog 2021-10-21 00:47:53 +02:00
Petr Mrázek
441ab7eedc NOISSUE debranding for real, initial work
This is probably very broken on macOS and Windows and will need a lot of work to complete fully.
2021-10-21 00:47:53 +02:00
Petr Mrázek
6a4130c914 NOISSUE re-align the status bar 2021-10-21 00:47:53 +02:00
Jamie Mansfield
3f0e729815 NOISSUE Use Qt's clear button for ATLauncher page
This replaces our 'Reset' button.
2021-10-17 20:18:18 +01:00
Jamie Mansfield
b93997501d NOISSUE Use Qt's clear button for modpacks.ch page
This replaces our 'Reset' button.
2021-10-17 20:14:16 +01:00
Jamie Mansfield
1869dd0de3 NOISSUE Search as you type for modpacks.ch
Since we just filter data locally now, this isn't painfully slow -
indeed it's very quick. This also matches other platforms, such as
ATLauncher.
2021-10-16 23:36:31 +01:00
Jamie Mansfield
175132539b NOISSUE Filter all pack's by name to search for modpacks.ch
modpacks.ch searching has changed, and while likely a bug - we may as
well make this change while we fetch all packs anyway. This makes MMC
more reactive for searchs for the platform.

This should be reverted if/when the modpacks.ch hits a size where we
need to restrict how many packs are fetched.
2021-10-16 23:31:27 +01:00
Petr Mrázek
6cc7788b4a Merge pull request #4135 from Leo40Git/notsecrets-readme
Add README to notsecrets library
2021-10-08 23:25:17 +02:00
Petr Mrázek
0c798b8fc7 Merge pull request #3185 from kb-1000/remove-status
GH-4157 Remove broken Mojang services status
2021-10-08 23:18:15 +02:00
kb1000
fa7a7d52d0 Remove broken Mojang services status 2021-10-08 19:29:49 +02:00
Petr Mrázek
8c4fb86ba0 Merge pull request #4103 from tobfos2611/develop
Fix underaged account that isn't linked to a family help link.
2021-10-03 21:59:46 +02:00
Petr Mrázek
668d31b79e Merge pull request #4143 from phit/patch-1
NOISSUE Remove outdated SSL docs
2021-10-03 21:52:13 +02:00
Petr Mrázek
3138e58c75 NOISSUE fix typo in FMLLibrariesTask.cpp 2021-10-03 21:46:10 +02:00
Philip T
0b312956db NOISSUE Remove outdated SSL docs
the link is dead and the equivalent newer page is useless, https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn265983(v=ws.11)?redirectedfrom=MSDN#related-content
2021-10-03 13:43:43 +02:00
ADudeCalledLeo
04b2c7ee0d Add README to notsecrets library 2021-10-01 15:46:32 +03:00
Petr Mrázek
3f3fac8462 Merge pull request #4117 from jamierocks/fix-4114
GH-4114 Allow the same file for be downloaded to multiple paths
2021-09-28 18:25:42 +02:00
Petr Mrázek
e46dc03eb1 NOISSUE add all missing shader packs icons 2021-09-26 02:54:52 +02:00
Petr Mrázek
0bb0578f2b NOISSUE add more shaderpacks icons 2021-09-25 01:46:39 +02:00
Petr Mrázek
cdf8d20c95 NOISSUE add shaderpacks icon for iOS icon theme 2021-09-25 01:43:19 +02:00
Petr Mrázek
560f7fa61a NOISSUE bump deb package version 2021-09-24 15:39:21 +02:00
Petr Mrázek
0b0e7aa633 Merge pull request #4080 from Zetabite/patch-1
NOISSUE: Add notice on support of selfbuilds
2021-09-24 15:24:44 +02:00
Petr Mrázek
1752d47035 Merge pull request #4100 from kb-1000/run-sh-exec
Use exec to launch the MultiMC script in the Debian/RPM packages
2021-09-24 15:24:18 +02:00
Petr Mrázek
0f3cf0595b NOISSUE add shader pack page for instances
Only theme with an icon is simple colored, rest is TBD
2021-09-23 21:26:56 +02:00
Jamie Mansfield
433dd2d161 GH-4114 Allow the same file for be downloaded to multiple paths
This is a potential fix to GH-4114.
2021-09-23 00:52:10 +01:00
Petr Mrázek
a5956194df NOISSUE Remove Requestor, it is unused 2021-09-21 22:02:12 +02:00
Petr Mrázek
d2a44effb5 Merge pull request #4106 from jamierocks/fix-4055
GH-4055 Don't download multiple files to the same path
2021-09-21 09:12:56 +02:00
Jamie Mansfield
0dcd24a53b GH-4055 Don't download multiple files to the same path
FTB should fix their metadata, but this should resolve issues
downloading their packs at present.
2021-09-17 16:00:43 +01:00
tobfos
28d7c5d309 Removed unnecessary text in link.
Thanks @RDKRACZ.
2021-09-15 20:46:07 +10:00
Toby Foster
7229ebbb08 Fix underaged account that isn't linked to a family help link. 2021-09-15 18:10:00 +10:00
kb1000
29f304f070 Use exec to launch the MultiMC script in the Debian/RPM packages 2021-09-14 12:13:41 +02:00
Petr Mrázek
e2355eb276 NOISSUE enable listing symlinks in mod/world lists 2021-09-09 00:27:46 +02:00
Adrian
d1b1113798 Update BUILD.md 2021-09-08 20:45:01 +02:00
Petr Mrázek
7185fd9a8f Merge pull request #4060 from StaticRocket/develop
Add custom-commands icon for other themes
2021-09-08 20:22:13 +02:00
Adrian
06661bff35 NOISSUE: Add notice on support of selfbuilds
Add notice that building MultiMC, besides development purposes, is unsupported.
2021-09-08 19:17:44 +02:00
Petr Mrázek
6c9dc4c86a NOISSUE fix typos in changelog 2021-09-07 21:02:41 +02:00
Petr Mrázek
426135b76a NOISSUE bump version to 0.6.13 and update changelog 2021-09-05 22:21:59 +02:00
Petr Mrázek
46468c8f14 NOISSUE block MS account adding on macOS < 10.13 builds
It's never going to work with Qt 5.6, so there's no point.
People need to update.
2021-09-05 18:54:27 +02:00
Petr Mrázek
878c4fb810 NOISSUE Provide dummy implementation for the secrets library 2021-09-05 18:23:49 +02:00
Petr Mrázek
d644fb2094 GH-4014 do not switch to Qt 5.15.2 on Windows
It is unstable for reasons unknown.
2021-09-04 23:51:57 +02:00
Petr Mrázek
823e7d22c7 GH-4014 fix kernel version scanning on macOS and linux some more 2021-09-04 22:18:29 +02:00
Petr Mrázek
c17b359d03 GH-4014 fix kernel version scanning on macOS and linux 2021-09-04 22:10:57 +02:00
Petr Mrázek
938f896bfa GH-4014 change updater to recognize new Qt 5.15.2 builds 2021-09-04 21:27:09 +02:00
Petr Mrázek
cd87029e6f NOISSUE add style plugins to packaging if present 2021-08-31 18:55:56 +02:00
StaticRocket
4f7aad0f8d Add custom-commands to pe_light icon theme 2021-08-30 23:35:16 -04:00
StaticRocket
b47d986f22 Add custom-commands to pe_dark icon theme 2021-08-30 23:34:59 -04:00
StaticRocket
51cdb8c790 Add custom-commands to pe_colored icon theme 2021-08-30 23:34:21 -04:00
StaticRocket
acbca16013 Add custom-commands to pe_blue icon theme 2021-08-30 23:33:56 -04:00
StaticRocket
62ecb3e58d Add custom-commands to iOS icon theme 2021-08-30 23:33:41 -04:00
StaticRocket
92895f11d1 Add custom-commands to OSX icon theme 2021-08-30 23:33:22 -04:00
Petr Mrázek
23442442d8 GH-3392 fix a bunch of bugs and implement STS error states 2021-08-31 01:11:52 +02:00
Petr Mrázek
3171014301 GH-3392 checking for migration status and refresh button in accounts list 2021-08-29 22:55:33 +02:00
Petr Mrázek
7239502675 GH-3392 Add recognition of already migrated Mojang accounts 2021-08-29 19:59:18 +02:00
Petr Mrázek
1e1655bc4b NOISSUE update README.md
It has not been touched in a long time. This brings it a bit more up to date.
2021-08-29 19:59:18 +02:00
Petr Mrázek
d4a3fc5195 Merge pull request #4054 from StaticRocket/develop
Add flat icon for custom-commands
2021-08-29 03:40:33 +02:00
StaticRocket
93c527ed3d Add flat icon for custom-commands 2021-08-28 21:13:35 -04:00
Petr Mrázek
b2c1100b1c NOISSUE introduce the concept of secrets static library 2021-08-27 22:35:17 +02:00
Petr Mrázek
34a5459dce NOISSUE cut down Requestor 2021-08-25 21:27:51 +02:00
Petr Mrázek
5c0e70e237 Merge pull request #4017 from jamierocks/fix-4012
GH-4012 Disable Xbox login if no MS client token is specified
2021-08-22 20:34:25 +00:00
Petr Mrázek
e2be2ada05 NOISSUE fix build
Missing QUrl include
2021-08-22 20:10:57 +02:00
Petr Mrázek
eae65da110 GH-3392 Switch MS account login to use device flow instead
Device flow involves the user manually opening a web page and putting in
a code. We no longer need to interact with the browser.
2021-08-22 20:01:18 +02:00
Petr Mrázek
50b92c1af2 NOISSUE Markdown is not available in Qt 5.4 ... who would have thought? 2021-08-20 01:57:59 +02:00
Petr Mrázek
1b68d51da6 NOISSUE add setting capes, tweak missing profile message, fix cape IDs 2021-08-20 01:34:32 +02:00
Petr Mrázek
94fd9a3535 NOISSUE fix linux builds 2021-08-19 10:27:30 +02:00
Petr Mrázek
345641f7d2 NOISSUE sanitize some MSA auth logging 2021-08-19 00:43:19 +02:00
Petr Mrázek
4a283fe4c1 NOISSUE print errorString in Requestor 2021-08-18 04:18:59 +02:00
Petr Mrázek
f1a5f7bc4d NOISSUE add ssl error logging to Requestor 2021-08-18 03:43:55 +02:00
Jamie Mansfield
2a21e28ffd GH-4012 Disable Xbox login if no MS client token is specified 2021-08-17 13:19:04 +01:00
Petr Mrázek
4ea52f4758 GH-3392 make sure skin upload at least doesn't fail completely 2021-08-15 23:46:12 +02:00
Petr Mrázek
44d634f564 GH-3392 Fix strings in AuthContext and make them translateable 2021-08-15 23:40:37 +02:00
Petr Mrázek
3a53349e33 GH-3392 dirty initial MSA support that shares logic with Mojang flows
Both act as the first step of AuthContext.
2021-08-15 23:18:50 +02:00
Petr Mrázek
fca2e9e44c Merge pull request #3997 from phit/detectadoptium
GH-3996 Detect Adoptium JDK's
2021-08-09 19:14:23 +02:00
phit
c2ec2a4af5 GH-3996 Detect Adoptium JDK's 2021-08-09 18:43:55 +02:00
Petr Mrázek
17af6d70b4 GH-3974 fix pinning to taskbar in various linux environments 2021-07-27 21:46:38 +02:00
Petr Mrázek
d2de849c86 NOISSUE set the required defs for main library 2021-07-26 14:03:59 +02:00
Petr Mrázek
7921f47ec4 NOISSUE loosen requirements on CMake version in katabasis 2021-07-25 19:50:44 +02:00
Petr Mrázek
20b9f2b42a NOISSUE Flatten gui and logic libraries into MultiMC 2021-07-25 19:50:44 +02:00
Petr Mrázek
dd13368085 NOISSUE bulk addition of code from Katabasis 2021-07-25 19:50:44 +02:00
Petr Mrázek
2568752af5 Merge pull request #3961 from phit/patch-1
GH-3939 fix suggestion template
2021-07-25 18:16:47 +02:00
Philip T
fbd93a47a3 GH-3939 fix suggestion template 2021-07-23 20:41:23 +02:00
Petr Mrázek
0c466bc530 Merge pull request #3919 from hamarb123/develop
Stop application freezes on macOS by moving data to MultiMC.app/Data
2021-07-23 16:13:32 +02:00
Petr Mrázek
b902c5cd78 Merge pull request #3956 from jamierocks/block-forge-117-install
NOISSUE Disable Install Forge button on 1.17 (and above)
2021-07-23 13:41:23 +02:00
Petr Mrázek
78124f6fba Merge pull request #3958 from jamierocks/fix-3957
GH-3957 Fix 'Add Instance' label
2021-07-22 22:34:07 +02:00
Jamie Mansfield
34f74fff0a GH-3957 Fix 'Add Instance' label 2021-07-22 21:21:17 +01:00
Jamie Mansfield
211cfb4af7 NOISSUE Disable Install Forge button on 1.17 (and above)
It appears that Minecraft Forge for Minecraft 1.17 will require setting
JVM arguments to operate, this is not currently possible with MultiMC's
meta.

Therefore, when Forge for 1.17 is released a solution will need to be
looked into.
2021-07-22 20:50:58 +01:00
Petr Mrázek
c5d0348181 NOISSUE Fix minutes display for total playtime 2021-07-22 21:18:03 +02:00
Leland Liu
1762d2fc7d Added total playtime 2021-07-22 20:26:59 +02:00
Petr Mrázek
8ea500de68 Merge pull request #2997 from ChrisLane/man-page
Add man page doc
2021-07-22 18:18:40 +02:00
Petr Mrázek
9e9281f06e Merge pull request #3728 from jamierocks/sort-languages
NOISSUE Sort languages alphabetically by their key
2021-07-22 18:15:46 +02:00
Petr Mrázek
6f12b31ead Merge pull request #3299 from QazCetelic/develop
Update instance icons, using the newer minecraft textures
2021-07-22 17:56:18 +02:00
Petr Mrázek
3974f12643 Merge pull request #3831 from kb-1000/rpm-cleanup
NOISSUE Some cleanups of the RPM package
2021-07-22 17:42:30 +02:00
Petr Mrázek
66fc707105 Merge pull request #3903 from Sebastian-byte/patch-1
NOISSUE: Fix typo and remove SSH URL
2021-07-22 17:41:16 +02:00
Petr Mrázek
4fc37f5a35 Merge pull request #3931 from jamierocks/july-wrk2
NOISSUE Debranding, fix annoying warning, updated Patreon logo
2021-07-22 17:40:36 +02:00
Petr Mrázek
295bcbe3a7 Merge pull request #3952 from Tkain/windows-icon-fix
NOISSUE: Fix Windows icon scaling issues
2021-07-22 17:40:16 +02:00
Thomas Kain
4b3305afbe Fix Windows icon scaling issues
Recalculated the icon using the official logo SVG and GIMP.
Appears visually identical to the old one at all resolutions.
2021-07-21 13:46:21 -04:00
Jamie Mansfield
74f5255eef GH-3930 Track duration of previous game sessions 2021-07-13 17:01:53 +01:00
Jamie Mansfield
d63ef939be NOISSUE Update Patreon logo
The logo was taken from Patreon's brand assets [1] archive, and shrunk
to size with KolourPaint.

[1] https://www.patreon.com/brand
2021-07-13 16:02:04 +01:00
Jamie Mansfield
66fde9e6b7 NOISSUE Correct trending URL string for Technic packs
This seems to have just been an inadvertent mistake when copy-pasting.
This commit just removes an annoying warning.
2021-07-13 15:42:44 +01:00
Jamie Mansfield
4401b9e137 NOISSUE Specify Imgur client ID at build time 2021-07-13 15:15:42 +01:00
Jamie Mansfield
20c393321c NOISSUE Use constants for user agents
Here lies yet another early-stage move to debrand the MultiMC codebase,
as well as reducing the burden of updating strings across the codebase
for a future MultiMC6.
2021-07-13 15:15:33 +01:00
Hamish Arblaster
e883cf2359 GH-2529 Move data path on macOS to MultiMC.app/Data
macOS seems to dislike changing files in the APPBUNDLE.app/Contents directory because it has to re-scan the directory every launch. As a result, large amounts of data there seems to cause freezes of MultiMC. Moving the default location outside of this directory, and thus the data, stops these freezes.
There is also a dialogue when the user first opens the app that asks them if they'd like to migrate their data folder, if they select yes it will move it, and if they select no it will not move it and allow them to move it later with an option in settings.
2021-07-11 09:36:21 +10:00
Petr Mrázek
df1d3dbae2 Merge pull request #3925 from jamierocks/fix-1949
GH-1949 Allow modpack downloads to be aborted
2021-07-06 22:01:33 +02:00
Jamie Mansfield
220971fadd GH-1949 Allow ATLauncher pack downloads to be aborted 2021-07-06 15:22:41 +01:00
Jamie Mansfield
d5c4489313 GH-1949 Allow Technic pack downloads to be aborted
This supports both 'single zip' modpacks and Solder packs, through the
Technic mod platform page.
2021-07-06 15:22:26 +01:00
Jamie Mansfield
db392b4994 GH-1949 Allow modpacks.ch pack downloads to be aborted 2021-07-06 15:12:07 +01:00
Jamie Mansfield
d1a142f040 GH-1949 Allow Legacy FTB pack downloads to be aborted
It looks like this was just an mistake when implementing support for
legacy FTB.
2021-07-06 15:10:35 +01:00
Petr Mrázek
417994735a Merge pull request #3914 from jamierocks/mch-check-checksums
NOISSUE Cache modpacks.ch files and check their checksums
2021-06-30 17:25:50 +02:00
Jamie Mansfield
2e78b64058 NOISSUE Fix detection for 32-bit Azul or Bellsoft Java
Co-authored-by: Pedro Cunha <pedroagracio@gmail.com>
2021-06-30 00:54:19 +01:00
Jamie Mansfield
c15bd655f1 NOISSUE Cache file downloads for modpacks.ch 2021-06-28 22:36:32 +01:00
Jamie Mansfield
f51efc9109 NOISSUE Verify file checksums for modpacks.ch 2021-06-28 22:09:52 +01:00
Sebastían
665b9213c6 NOISSUE Fix typo and remove SSH URL 2021-06-25 20:15:19 -05:00
Petr Mrázek
dc3a4cebce Merge pull request #3896 from jamierocks/mib-suffix
NOISSUE Use MiB suffix for Java memory options
2021-06-25 12:27:57 +02:00
Petr Mrázek
d92733feae Merge pull request #3897 from jamierocks/atl-opt-mod-install-btn
NOISSUE Close optional mod dialog with Install button
2021-06-25 12:20:27 +02:00
Jamie Mansfield
a20a7e987f NOISSUE Fail launch if minimum Java requirement is not met
This will fail launch in the following conditions:
  1. A version greater than or equal to Minecraft 17w13a, and less than
     21w19a - and the Java version is less than 8.
  2. A version greater than or equal to Minecraft 21w19a - and the Java
     version is less than 16.
2021-06-23 21:24:25 +01:00
Jamie Mansfield
7c0fdaa730 NOISSUE Check mod and config checksums for ATLauncher 2021-06-23 18:20:25 +01:00
Jamie Mansfield
c77f5285e3 NOISSUE Close optional mod dialog with Install button
This was a silly ommision I made.
2021-06-23 15:49:31 +01:00
Jamie Mansfield
d8598d6901 NOISSUE Use MiB suffix for Java memory options 2021-06-23 09:57:55 +01:00
Petr Mrázek
27d3ae145a Merge pull request #3888 from jamierocks/atl-version-selection-width
NOISSUE Fix ATLauncher version selection combo box width
2021-06-22 19:57:49 +02:00
Petr Mrázek
5479fbec92 Merge pull request #3819 from jamierocks/atl-optional-mods
NOISSUE Support ATLauncher optional mods
2021-06-22 19:55:53 +02:00
Petr Mrázek
bc2c6cf030 Merge pull request #3893 from colbiedison/detect_jdks_in_opt
[Linux] Add detection of manually installed JDKs in /opt
2021-06-22 19:14:37 +02:00
Colbie Dison
ba8af797a9 Add detection of manually installed JDKs in /opt 2021-06-22 11:56:05 -05:00
Jamie Mansfield
4ba0c9c298 NOISSUE Support mod grouping and dependencies 2021-06-21 16:29:16 +01:00
Jamie Mansfield
74311a54cf NOISSUE Support ATLauncher optional mods 2021-06-21 16:29:11 +01:00
Jamie Mansfield
a87c64d7d1 NOISSUE Fix ATLauncher version selection combo box width
Resolves a bug that was introduced with [1], furthermore and in
specific relation to the intent of said commit, this brings the
version selection combo box inline with other mod platforms.

[1] f7c144c393
2021-06-21 15:26:10 +01:00
Petr Mrázek
8179a89103 Merge pull request #3886 from phit/fixftblegacy
NOISSUE Fix FTB Legacy Pack Selection
2021-06-21 00:46:16 +02:00
phit
e439ce6e0b NOISSUE Fix Modplatform Scrollbars properly 2021-06-20 22:59:58 +02:00
phit
f6d6e4c1c4 NOISSUE Fix FTB Legacy Pack Selection 2021-06-20 22:52:10 +02:00
Petr Mrázek
bace6fec1b Merge pull request #3880 from phit/fix/GH-3720
GH-3720 Fix UI inconsistencies with Modplatforms
2021-06-20 22:39:03 +02:00
Petr Mrázek
a487234968 Revert "GH-3507 Modernize MacOS Icon"
This reverts commit d08a2f00a2.
2021-06-20 21:53:02 +02:00
phit
f7c144c393 GH-3720 Fix UI inconsistencies with Modplatforms
Fixes GH-3118
Fixes GH-3720
Fixes GH-3731

Icons and Ok button state will now switch consistently when moving
between tabs. ATLaunchers packlist is now no longer redownloaded
each time you open its Tab. All lists are striped now. And all
search and filter fields now have a placeholder text.
2021-06-20 01:03:17 +02:00
Petr Mrázek
6c0ff0b46f Merge pull request #3881 from jamierocks/notice-login-msa
NOISSUE Add notice re MSA to login dialog
2021-06-20 00:07:01 +02:00
Jamie Mansfield
81d4dc09cc NOISSUE Add notice re MSA to login dialog
This commit should be reverted when support for Microsoft/Xbox
authentication is introduced.
2021-06-19 20:31:49 +01:00
Petr Mrázek
5f8d07c009 Merge pull request #3875 from jamierocks/feature/gh-3033
GH-3033 Add filtering for version components
2021-06-19 12:50:21 +02:00
Petr Mrázek
25955c0817 Merge pull request #3877 from Zetabite/feature_offline_status_log
NOISSUE Add info for instance launch mode and server status when online
2021-06-19 11:59:04 +02:00
Adrian
6db6ebe37f NOISSUE Add info for instance launch mode and server status when online 2021-06-19 03:15:21 +02:00
Jamie Mansfield
b246fc171e GH-2971 Brand mod model for resource/texture pack pages 2021-06-19 00:59:48 +01:00
Jamie Mansfield
c92b44e6d6 GH-3719 Translate some missing strings when changing locale 2021-06-19 00:14:36 +01:00
Jamie Mansfield
e148cfbbfd NOISSUE Don't translate logged entries 2021-06-18 23:57:58 +01:00
Jamie Mansfield
c0f72488d0 GH-3033 Add filtering for version components 2021-06-18 23:21:12 +01:00
Petr Mrázek
cb22d5fb09 Merge pull request #3874 from phit/macosicon
GH-3507 Modernize MacOS Icon
2021-06-18 20:39:32 +02:00
phit
d08a2f00a2 GH-3507 Modernize MacOS Icon
full credit to @ThePotatoKing55
2021-06-18 20:27:25 +02:00
Petr Mrázek
0c147fb5ee Merge pull request #3843 from phit/adoptjdkdetection
Update Java detection
2021-06-18 19:14:15 +02:00
Petr Mrázek
09ce3d8f40 Merge pull request #3872 from phit/fixcursedropdown
GH-3731 Limit Curserforge version height
2021-06-18 19:10:18 +02:00
phit
00820df656 GH-3731 Limit Curserforge version height 2021-06-18 18:31:14 +02:00
phit
c2c288a956 NOISSUE Add BellSoft and Azul Java 2021-06-18 17:12:44 +02:00
phit
7ac6c4f3d9 NOISSUE Cleanup duplication and Microsoft JDK 2021-06-18 17:02:41 +02:00
phit
fd04ff2b08 NOISSUE Add AdoptOpenJDK Java detection 2021-06-18 16:10:48 +02:00
Petr Mrázek
434adc4cd7 Merge pull request #3870 from jamierocks/build-time-support-links
NOISSUE Specify support URLs at build time
2021-06-18 13:43:35 +02:00
Jamie Mansfield
40f41e5fbe NOISSUE Specify support URLs at build time
Support URLs (bug tracker, Discord guild, subreddit) are now specified
as cache variables in cmake, and the buttons are not shown if no value
is set for them.

This is an early-stage move towards debranding the MultiMC codebase,
and will (hopefully) alleviate support requests coming to us from
illicit forks.
2021-06-18 12:24:20 +01:00
kb1000
663a1a5a83 Add xrandr to the dependencies of the RPM package 2021-06-14 20:34:51 +02:00
Petr Mrázek
9fafe3ffe6 Merge pull request #3833 from jamierocks/disable-forge-btn-when-game-running
NOISSUE Disable 'Install Forge' button when game is running
2021-06-09 21:54:22 +02:00
Jamie Mansfield
3390367d93 NOISSUE Support CurseForge modpacks using Fabric Loader 2021-06-09 20:49:26 +01:00
Jamie Mansfield
6dd1fdbaf9 NOISSUE Fail pack installation for download errors for FTB
This effectively reverts the changes made in [1] to resolve GH-3304.

[1] 05ffcf706b
2021-06-03 15:16:03 +01:00
Jamie Mansfield
efa3bb33f5 NOISSUE Disable 'Install Forge' button when game is running 2021-06-03 15:03:19 +01:00
Petr Mrázek
60b686f014 Merge pull request #3804 from Janrupf/feature/default-server
Add ability to select a server to join in the instance settings
2021-05-24 02:41:54 +02:00
Petr Mrázek
3a8068e75f Merge pull request #3803 from phit/patch-1
NOISSUE Fix new Oracle Java Detection on Windows
2021-05-24 02:41:08 +02:00
Janrupf
52c1150522 NOISSUE Add --server argument for --launch 2021-05-23 14:42:20 +02:00
Janrupf
58ab005f7e NOISSUE Add missing license header 2021-05-22 18:10:17 +02:00
Janrupf
ea6c42a93c NOISSUE Allow joining servers from the servers page 2021-05-22 18:07:08 +02:00
Janrupf
f33fe05e5f NOISSUE Use minecraft logic for parsing adresses 2021-05-22 17:24:37 +02:00
Janrupf
d97f13b4aa NOISSUE Use Vanilla logic for resolving servers 2021-05-22 17:00:14 +02:00
Janrupf
0ccd7223fd NOISSUE Make LauncherPart aware of server to join 2021-05-22 16:33:16 +02:00
Janrupf
23a706bbae NOISSUE Resolve minecraft server using DNS SRV 2021-05-22 16:14:25 +02:00
Janrupf
cc6cd0648a NOISSUE Add server launch arguments 2021-05-22 13:54:34 +02:00
Philip T
f78152d725 NOISSUE Fix new Oracle Java Detection on Windows
Those were changed with Java 9 https://docs.oracle.com/javase/9/install/installation-jdk-and-jre-microsoft-windows-platforms.htm#JSJIG-GUID-47C269A3-5220-412F-9E31-4B8C37A82BFB
2021-05-22 13:37:51 +02:00
Janrupf
f0eb5b4a0c NOISSUE Register settings for setting a server 2021-05-22 13:28:23 +02:00
Janrupf
2e2a5d0943 NOISSUE Required UI elements for setting a server 2021-05-22 13:15:59 +02:00
Petr Mrázek
911074e966 Merge pull request #3735 from kumquat-ir/develop
NOISSUE Parse META-INF/mods.toml for Forge 1.14+ mod metadata
2021-05-15 00:36:46 +02:00
Petr Mrázek
b8cd13bb21 Merge pull request #3747 from runlevel5/patch-1
Add limits header
2021-05-15 00:35:36 +02:00
Petr Mrázek
deac64e0a2 Merge pull request #3787 from JoelTroch/feature/gh-3450
GH-3450 Add checkboxes to display and record game time
2021-05-15 00:35:05 +02:00
Petr Mrázek
2f1e8e82a3 Merge pull request #3729 from jamierocks/atl-loader-targets
Various ATLauncher improvements and bug fixes
2021-05-15 00:33:53 +02:00
Joël Troch
de089195cd GH-3450 Add checkboxes to display and record game time 2021-05-13 19:38:24 +02:00
Jamie Mansfield
df7873eb9a GH-3764 Only install client mods for ATLauncher packs 2021-05-06 17:14:49 +01:00
Petr Mrázek
3d11f9a7e9 NOISSUE fix issue templates 2021-05-01 10:29:43 +02:00
Trung Lê
c2fd714f8d Add limits header 2021-04-29 12:42:49 +10:00
Petr Mrázek
a09d03d71d NOISSUE stop relying on forge servers for old FML libs 2021-04-29 02:32:21 +02:00
kumquat-ir
e2e5294fb9 reimplement parsing logic for tomlc99 2021-04-17 10:33:45 -07:00
kumquat-ir
e668aa0f95 switch to new toml library 2021-04-17 09:46:11 -07:00
kumquat-ir
13afad80fb replace ${file.jarVersion} with something useful 2021-04-16 17:45:55 -07:00
kumquat-ir
7156e086f6 parse META-INF/mods.toml for metadata 2021-04-16 13:33:56 -07:00
kumquat-ir
860706caec allow parsing toml from a QByteArray 2021-04-16 11:02:02 -07:00
kumquat-ir
42253150e4 add toml11 as dependency 2021-04-15 23:19:01 -07:00
Jamie Mansfield
438ddfb88d NOISSUE Support Fabric modpacks on ATLauncher
Annoyingly the metadata structure is loader dependent :(
2021-04-16 01:51:25 +01:00
Jamie Mansfield
73788f5d2f NOISSUE Emit failure on failure conditions
The 'Installing modpack' dialog will no longer stay open forever,
even though the installation has failed.
2021-04-16 01:51:25 +01:00
Jamie Mansfield
87dbe82474 NOISSUE Support custom,latest,recommended loader versions for ATL
This provides support for modpacks using the new loader mechanism in
ATLauncher and using a non-specific version target.
2021-04-16 01:51:23 +01:00
Jamie Mansfield
88ce42bc0a NOISSUE Sort languages alphabetically by their key
This will provide a consistent arrangement of languages in MultiMC.
2021-04-13 13:53:21 +01:00
Petr Mrázek
8b926d29d7 NOISSUE fix build 2021-04-08 22:28:55 +02:00
Petr Mrázek
4ac38991ad Merge pull request #3691 from phit/feature/fixcurse
NOISSUE Curseforge makeover
2021-04-08 22:07:03 +02:00
Petr Mrázek
4ca481b2b3 Merge pull request #3715 from jamierocks/atl-trycatch-json
NOISSUE Fedora build, ATL exceptions, and language tweaks
2021-04-08 21:29:10 +02:00
Jamie Mansfield
524fc5b6ec NOISSUE Fix string formatting issues
This allows translations to have more control over the output :)
2021-04-08 18:55:20 +01:00
Jamie Mansfield
64617201b0 GH-3334 Show English variants correctly
This will no longer show 'American English' with the statistics of
'British English', yet show the correct translations - and will now
display 'British English'.
2021-04-08 18:53:16 +01:00
Jamie Mansfield
d6dc22d57c NOISSUE Handle JSON exceptions in ATLauncher support
Thanks to phit for pointing this out :)
2021-04-08 18:51:04 +01:00
Jamie Mansfield
3a1abb555b GH-3575 Fix build on Fedora 34
Presumably this is caused by the bump to GCC 11 in Fedora 34. See
the error that did occur below...

    ./MultiMC5/application/KonamiCode.cpp: In member function ‘void KonamiCode::input(QEvent*)’:
    ./MultiMC5/application/KonamiCode.cpp:38:23: error: comparison of integer expressions of different signedness: ‘int’ and ‘std::array<Qt::Key, 10>::size_type’ {aka ‘long unsigned int’} [-Werror=sign-compare]
       38 |         if(m_progress == konamiCode.size())
          |            ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
2021-04-08 18:50:07 +01:00
phit
1f8408c793 NOISSUE Curseforge makeover
update UI to match other modpack platforms
add sorting
add version selection, fixes GH-3667
add installing beta versions, fixes GH-3611
2021-04-08 19:32:34 +02:00
Petr Mrázek
e5804b1279 NOISSUE add some logging to the system theme determination logic 2021-04-04 21:19:49 +02:00
Petr Mrázek
7246d8a779 NOISSUE improve GradleSpecifier and handle broken library names 2021-04-01 03:50:28 +02:00
Petr Mrázek
fbe9d15875 NOISSUE fix it some more in different file 2021-03-29 22:03:52 +02:00
Petr Mrázek
094ed0bc81 NOISSUE fix build issue with QJsonValueRef on macOS 11
This will remove support for macOS 10.7 and 10.8, but their numbers in analytics are 0
2021-03-29 21:51:28 +02:00
Petr Mrázek
43cf647642 Revert "GH-3666 Fix multi-monitor dpi scaling on windows"
This reverts commit 34bf688479.
2021-03-28 19:46:01 +02:00
Petr Mrázek
5400d4e613 Merge pull request #3690 from jamierocks/ftb-search-edge
NOISSUE Fix edgecase where new searches won't be processed
2021-03-26 20:37:48 +00:00
Petr Mrázek
adf2301b2a Merge pull request #3684 from KGB-8375/patch-1
GH-3666 Fix multi-monitor dpi scaling on windows
2021-03-26 20:36:28 +00:00
Petr Mrázek
9d76219434 Merge pull request #3686 from phit/fix/3633
GH-3633 assume latest MC version, if CurseForge pack supports multiple
2021-03-26 20:36:00 +00:00
Jamie Mansfield
8e6400e8d8 NOISSUE Fix edgecase where new searches won't be processed
This resolves an issue with the modpacks.ch search functionality, in
which a search issued while one is currently in progress won't be made
and the UI won't allow for the search to be made after.

Reproduction Steps:
1. Open the FTB pane in the Add Instance Dialog
2. Perform a search while MMC is still performing the initial search

The search won't be performed, the existing search will have been
aborted, and you are unable to try the search again (without trying a
different search in the meantime).

This was caused by 2 things:
1. A search cannot be re-attempted, and this logic doesn't consider
   failures.
2. The failure slot wasn't called when the NetJob was aborted, so
   the search would never be performed - but the term would be
   stored as if it had (trigering point 1).

I have resolved this by doing 2 things:
1. If the failure slot is called, set a searchState of Failed. Allow
   search re-attempts in this case.
2. If there is a present NetJob, abort and reset it. The immediately
   continue with the search.
2021-03-26 20:03:57 +00:00
KGB-8375
34bf688479 GH-3666 Fix multi-monitor dpi scaling on windows
Disable qt 5.6 DPI scaling and use windows' builtin DPI scaling, which works much better on multi-monitor setups
2021-03-26 09:54:28 -04:00
phit
ba13e33ccc GH-3633 assume latest MC version, if CurseForge pack supports multiple
right now these are unused anyway
2021-03-26 14:18:47 +01:00
Petr Mrázek
73af0f271a NOISSUE fix build - missing includes in RWStorage.h 2021-03-26 01:48:37 +01:00
Petr Mrázek
b8ee9a2a8e NOISSUE update ubuntu packaging bits 2021-03-26 01:48:37 +01:00
Petr Mrázek
369a243f1f Merge pull request #3682 from kb-1000/rename-twitch-curseforge
NOISSUE rename Twitch to flame internally for consistency and to CurseForge for user displayed strings
2021-03-26 00:38:58 +00:00
Petr Mrázek
1db6985be2 Merge pull request #3681 from kb-1000/fix-moc-warning
NOISSUE fix MOC warning complaining about an unused MOC file include
2021-03-26 00:37:25 +00:00
Petr Mrázek
cc8ac7bdbe Merge pull request #3680 from kb-1000/log-system-glfw-openal
NOISSUE print information about whether the system GLFW or OpenAL workarounds are active to the instance log
2021-03-26 00:37:09 +00:00
Petr Mrázek
8f4062b5e6 Merge pull request #3677 from kb-1000/datapacks-button-disable
NOISSUE disable datapacks button in the world screen if no world is selected
2021-03-26 00:36:30 +00:00
kb1000
a0cb1a0d42 NOISSUE rename Twitch to flame internally for consistency and to CurseForge for user displayed strings 2021-03-24 00:59:43 +01:00
kb1000
7acad35c3f NOISSUE fix MOC warning complaining about an unused MOC file include 2021-03-23 23:05:10 +01:00
kb1000
9d7ba275ab NOISSUE print information about whether the system GLFW or OpenAL workarounds are active to the instance log 2021-03-23 22:57:39 +01:00
kb1000
4f3328e71c NOISSUE disable datapacks button in the world screen if no world is selected 2021-03-23 21:54:48 +01:00
Petr Mrázek
cbc973a5af NOISSUE remove text about future plans
It was confusing people.
2021-03-22 02:17:01 +01:00
Petr Mrázek
464b05f6bf NOISSUE fix typo in changelog 2021-03-21 20:45:06 +01:00
Petr Mrázek
4b02b3f84a NOISSUE add warning to the ATLauncher pack page 2021-03-21 20:34:28 +01:00
Petr Mrázek
b1047d839e NOISSUE update changelog 2021-03-21 20:21:50 +01:00
AppleTheGolden
613afce705 NOISSUE add github issue forms 2021-03-19 00:24:52 +01:00
Petr Mrázek
af5b1beb64 Merge pull request #3646 from sambhavsaggi/openssl_doc_update
Fix #3638: OpenSSL download links in BUILD.md are dead
2021-03-10 04:19:43 +01:00
Petr Mrázek
84c673c5ba GH-3467 fix stall-out in ScanModFolders when the folders don't exist 2021-03-10 03:58:24 +01:00
Petr Mrázek
0c98589a7f GH-3602 Create .minecraft before running pre-launch commands 2021-03-10 03:58:24 +01:00
Sambhav Saggi
10e7f48347 Fix #3638 2021-03-08 09:24:37 -05:00
Petr Mrázek
0a869fc9ed Merge pull request #3623 from jamierocks/ftb/jarmods
Support jarmods and Fabric for modpacks.ch
2021-02-27 04:10:06 +01:00
Jamie Mansfield
9c69b0cdde NOISSUE Install jarmods for modpacks.ch
This patch will now install any jarmods from modpacks.ch packs,
searching the 'jarmods' directory to find them.
2021-02-25 14:43:04 +00:00
Jamie Mansfield
9d91cd496f NOISSUE Download all mods before writing the instance for modpacks.ch
This is prepatory work for implementing jarmods support for
modpacks.ch, where we will need to look through files in a directory -
which would require that those files are present at such time.

This might even fix some weird bugs, maybe - I've not encountered any
bugs from how this previously worked, but I feel that what's going on
is slightly clearer now.
2021-02-25 14:34:51 +00:00
Jamie Mansfield
384680ca13 NOISSUE Support Fabric mod loader for modpacks.ch
There aren't any (currently) packs on modpacks.ch that use Fabric, but
they do have support in their modpacklauncher. This patch just means
we preemptively have support should any packs that require it be
introduced.
2021-02-25 13:55:26 +00:00
Petr Mrázek
1edcd9b86e NOISSUE implement deleting skins 2021-02-11 02:23:00 +01:00
Petr Mrázek
280903e52b Merge pull request #3597 from jamierocks/atl/search
NOISSUE Add search to ATLauncher
2021-02-11 01:59:24 +01:00
Jamie Mansfield
152d476f20 NOISSUE Add search to ATLauncher 2021-02-11 00:57:07 +00:00
Petr Mrázek
40bdb0ffd6 Merge pull request #3593 from jamierocks/atl/description-view
NOISSUE Show ATLauncher pack descriptions in text browser
2021-02-10 18:38:04 +01:00
Petr Mrázek
1e59dafd75 Merge pull request #3594 from jamierocks/ftb/description-view
NOISSUE Show FTB pack descriptions in text browser
2021-02-10 18:37:34 +01:00
Jamie Mansfield
6eabd336ee NOISSUE Show FTB pack descriptions in text browser 2021-02-10 16:57:46 +00:00
Jamie Mansfield
003e019048 NOISSUE Show ATLauncher pack descriptions in text browser 2021-02-10 16:11:59 +00:00
Petr Mrázek
6b3b7ded2d GH-3130 fix uploading skins by using the new skins endpoint 2021-02-10 03:32:17 +01:00
Petr Mrázek
485f123362 Merge pull request #3311 from AbigailBuccaneer/develop
Fix endianness handling errors
2021-02-09 23:12:32 +01:00
Petr Mrázek
7265abf763 NOISSUE sprinkle suspendSave all over pack import tasks 2021-02-09 22:46:51 +01:00
Petr Mrázek
7ca5cf0e70 Merge pull request #3592 from Scotsguy/develop
NOISSUE Swap discord invite with vanity url
2021-02-09 22:02:12 +01:00
AppleTheGolden
f3205ebf25 NOISSUE Swap discord invite with vanity url 2021-02-09 22:00:20 +01:00
Petr Mrázek
e8b9176736 NOISSUE update macOS build instructions 2021-02-09 21:08:45 +01:00
Petr Mrázek
13a7f8d3b7 NOISSUE fix multiple issues in ATLauncher integration 2021-02-09 05:04:23 +01:00
Petr Mrázek
434369ca7c NOISSUE tweak ATLauncher pack cache names to avoid filesystem issues 2021-02-08 02:23:35 +01:00
Petr Mrázek
c54d922f26 NOISSUE and fix one more build fail 2021-02-07 23:54:29 +01:00
Petr Mrázek
85d5e9ff23 NOISSUE fix some build fails on linux and windows 2021-02-07 23:51:10 +01:00
Jamie Mansfield
ab19b86341 GH-405 ATLauncher Support 2021-02-07 23:30:24 +01:00
Petr Mrázek
5e980ceef2 Merge pull request #3556 from Zipdox/develop
Fix packaging issue caused by qt5-default
2021-02-07 02:24:42 +01:00
Petr Mrázek
1cf60a01d5 Merge pull request #3538 from Scotsguy/develop
NOISSUE Update Copyright Year
2021-02-07 02:23:31 +01:00
Petr Mrázek
ba4a1a68bd Merge pull request #3537 from rbutoi/argv0
Use argv[0] to print program name on a CLI error.
2021-02-07 02:21:25 +01:00
Petr Mrázek
11f7a432c4 Merge pull request #3583 from chrhasse/develop
Fix catching polymorphic type by value error
2021-02-07 02:16:59 +01:00
Christopher Hasse
047546c59e Fix catching polymorphic type by value error
fixes #3579
2021-02-06 18:55:44 -06:00
Petr Mrázek
75c14990e5 Merge pull request #3580 from kb-1000/remove-proxy-auth-from-log
NOISSUE Remove proxy user and password from the MultiMC log
2021-02-06 20:47:56 +01:00
kb1000
71bb50f64c NOISSUE Remove proxy user and password from the MultiMC log 2021-02-06 20:16:04 +01:00
Petr Mrázek
1868e0ccf1 GH-3229 fix copy seed button not working for newer worlds
Added the `optional-bare` library and refactored NBT reading
code to support this change.
2021-02-06 15:58:03 +01:00
Petr Mrázek
f8ca96a335 NOISSUE update changelog for 0.6.12 2021-02-06 13:13:53 +01:00
Petr Mrázek
5a7c1f9edf NOISSUE force kill button to reevaluate its style on state change 2021-02-05 03:18:21 +01:00
Petr Mrázek
359905a8ef NOISSUE set instance dialog launch/kill button object name 2021-02-05 02:46:59 +01:00
Petr Mrázek
8eee4e3e6c Merge pull request #3562 from kb-1000/technic-fix-1710-forge
Fix up 1.7.10 forge versions for Technic platform import by removing -1.7.10 suffix
2021-01-29 13:02:04 +01:00
kb1000
df2b9adc1e Fix up 1.7.10 forge versions for Technic platform import by removing -1.7.10 suffix 2021-01-29 10:18:33 +01:00
Zipdox
61004a25ec Fix packaging issue caused by qt5-default 2021-01-26 21:47:26 +01:00
AppleTheGolden
0de064eb35 NOISSUE Update Copyright Year 2021-01-18 08:28:54 +01:00
Radu Butoi
1cb9382282 Use argv[0] to print program name on a CLI error. 2021-01-17 16:58:32 -05:00
Petr Mrázek
02887536f7 Merge pull request #3524 from TreyRuffy/SessionServerFix
Switch Session Server
2021-01-17 18:53:03 +00:00
TreyRuffy
9100373af2 Switch Session Server
sessionserver.mojang.com has been down for a while and it looks like it is no longer in use.
https://bugs.mojang.com/browse/WEB-3166?focusedCommentId=820167&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-820167

I have switched the url to session.minecraft.net
2021-01-11 19:26:40 -07:00
Petr Mrázek
dc45d46e9c NOISSUE fix the metainfo file 2021-01-07 11:54:08 +01:00
Petr Mrázek
e9abf8a2d4 Merge pull request #3436 from Moresteck/develop
NOISSUE Corrected the fix for Classic saving, fixes Indev compatibility
2021-01-07 10:27:25 +00:00
Petr Mrázek
a1a3f0d492 Merge pull request #3476 from joshua-stone/metainfo-support
NOISSUE Add metainfo support
2021-01-07 10:05:59 +00:00
Joshua Stone
331f6df9e5 Improve metadata for screenshots 2020-12-10 11:43:37 -05:00
Joshua Stone
e9b779aa77 Update documentation WRT .rpm contents 2020-12-08 13:25:34 -05:00
Joshua Stone
42d124ed2b Move metainfo to ubuntu oflder so it can be used in both .rpm and .deb builds 2020-12-08 13:23:52 -05:00
Joshua Stone
a9a78c74db Fix contact email 2020-12-08 01:13:11 -05:00
Joshua Stone
d55030367e Use metainfodir macro directly 2020-12-08 00:55:50 -05:00
Joshua Stone
65990a1bf1 Remove disclaimer 2020-12-08 00:42:16 -05:00
Joshua Stone
399109728f Update RPM .spec to include metainfo file 2020-12-08 00:38:43 -05:00
Joshua Stone
c1161e2377 Add metainfo.xml for improving package metadata 2020-12-08 00:30:29 -05:00
Petr Mrázek
66b0ed2669 Merge pull request #3400 from MatejKafka/patch-1
Update BUILD.md with details for Windows build process
2020-12-02 01:57:30 +01:00
kb1000
f4d58e17ee NOISSUE Add pack author and description to technic modpack import page 2020-11-30 00:31:27 +01:00
Petr Mrázek
6aa126be30 Merge pull request #3460 from kb-1000/rpm-package
NOISSUE Add an RPM package
2020-11-30 00:19:25 +01:00
Petr Mrázek
58d168bda2 Merge pull request #3442 from jamierocks/ftb-sorts
NOISSUE Add sorting options to FTB pack install page
2020-11-30 00:13:41 +01:00
Jamie Mansfield
5eace10d51 NOISSUE Continue requesting packs after ignoring one
This was a mishap on my part, fortunately quickly exposed as the
combination of GH-3463 and GH-3464 made no packs visible.
2020-11-29 13:58:20 +00:00
Jamie Mansfield
b8c6a42f49 NOISSUE Add sorting options to FTB pack install page 2020-11-29 13:46:14 +00:00
Petr Mrázek
ed11d33054 Merge pull request #3464 from jamierocks/ftb/ignore-dud-packs
NOISSUE Ignore 'dud' FTB packs
2020-11-29 00:16:00 +01:00
Petr Mrázek
11a21b57e4 Merge pull request #3463 from jamierocks/ftb/new-api
NOISSUE Use new modpacks.ch route for listing packs
2020-11-29 00:15:24 +01:00
Jamie Mansfield
7321a4fd3d NOISSUE Ignore 'dud' FTB packs
There is no guarantee from modpacks.ch that modpacks contain any
versions, which is currently an issue with pack 63 (Direwolf20 1.5).
2020-11-28 22:13:53 +00:00
Jamie Mansfield
6249a54ba2 NOISSUE Use new modpacks.ch route for listing packs 2020-11-28 22:06:35 +00:00
kb1000
58c2228247 NOISSUE Add an RPM package for MultiMC
The RPM package reuses the files from the Ubuntu package.
2020-11-27 19:28:46 +01:00
Petr Mrázek
88d6b6ea3f GH-3427 add low effort datapack button to worlds 2020-11-24 22:31:49 +01:00
Petr Mrázek
66c0999901 Merge pull request #3438 from jamierocks/technic-zip-fix
GH-3437 Extract Technic single zip packs to correct place
2020-11-14 16:14:50 +01:00
Jamie Mansfield
edfea9894e NOISSUE Cleanup Technic support 2020-11-13 16:38:18 +00:00
Jamie Mansfield
fd1a8f039a GH-3437 Extract Technic single zip packs to correct place 2020-11-13 16:33:52 +00:00
Moresteck
49126fa8e2 NOISSUE Corrected the fix for Classic saving, fixes Indev compatibility 2020-11-12 14:58:12 +00:00
Matej Kafka
7da2fd5b8b Update BUILD.md
Extended description of Windows build process based on about 6 hours wasted on trial and error.
2020-10-25 03:54:59 +01:00
Petr Mrázek
eb3e6e4c6a NOISSUE give technic icon a darker outline 2020-10-13 22:35:43 +02:00
Petr Mrázek
4197ae0128 NOISSUE remove obsolete TODO comment from SolderPackInstallTask 2020-10-13 22:20:28 +02:00
Petr Mrázek
a7216ecca1 NOISSUE use Json parsing helpers to process technic search results 2020-10-13 22:18:42 +02:00
Petr Mrázek
4689571c24 NOISSUE fix build: QTemporaryDir::filePath was added in Qt 5.9 2020-10-13 22:01:01 +02:00
Petr Mrázek
4c62776044 Merge pull request #3195 from kb-1000/technic-import
Technic pack import
2020-10-13 21:44:20 +02:00
kb1000
8021fb25d0 GH-469 Implement support for importing and searching for Technic Platform and Solder modpacks
This does not support any custom modpack.jar for 1.6 or newer, it simply uses standard Forge then.
Supports Forge and Fabric, and JAR mods for 1.5 and older.
2020-10-13 21:34:16 +02:00
Petr Mrázek
762ddaea65 Merge pull request #3372 from jamierocks/ftb-data-temp-fix
GH-3304 Temporarily ignore download failures for FTB packs
2020-10-12 01:36:56 +02:00
Jamie Mansfield
05ffcf706b GH-3304 Temporarily ignore download failures for FTB packs
This is a temporary measure, to match FTB's own installer, to allow
packs to download while FTB work on fixing the data from their API.
2020-10-12 00:28:05 +01:00
Petr Mrázek
dded11004f NOISSUE adapt to work on Windows, fix reading symlink targets on POSIX 2020-10-12 01:01:51 +02:00
Petr Mrázek
f1284ab96b NOISSUE just eliminate QCOMPARE. it doesn't work at all. 2020-10-11 23:50:36 +02:00
Petr Mrázek
3aca4cbe20 NOISSUE tweak QCOMPARE vs QVERIFY
... the Qt test framework is annoyingly complicated and brittle.
... it may have to be replaced with something more reliable.
2020-10-11 23:45:41 +02:00
Petr Mrázek
015c34bb38 NOISSUE dis-ambiguate call to ensureBoolean in PackageManifest 2020-10-11 23:32:57 +02:00
Petr Mrázek
a49472349d NOISSUE fix constructing Path from iterators
Old Qt doesn't have the iterator-based constructors...
2020-10-11 23:28:38 +02:00
Petr Mrázek
0946c7c138 NOISSUE basic code for downloading JREs from Mojang
Not integrated yet, but the logic has tests and shouldn't
be too shaky. Integration comes next.
2020-10-11 23:20:35 +02:00
Petr Mrázek
5180536cc3 NOISSUE add a way to use native system versions of OpenAL and GLFW
If your OS comes with patched/fixed/newer versions of those,
you can now check the checkboxes and stop using the old ones
shipped by Mojang.
2020-09-10 23:10:17 +02:00
Petr Mrázek
27e43d037e NOISSUE remove translations from CheckJava launch step 2020-09-07 22:33:57 +02:00
Petr Mrázek
feae420450 NOISSUE add 'java.vendor' to the checker and display/log the value 2020-09-07 22:28:41 +02:00
AbigailBuccaneer
6995a2e1ba Avoid undefined behaviour when byteswapping
`a << b` is undefined when `a` is negative, and `a >> b` is
implementation-defined. The correct thing to do here is to cast to
unsigned, swap the bytes there and then swap back.

This also improves performance on some compilers: Clang is smart enough
to recognise that we're byteswapping here and reduce it to a single
`bswap` instruction on x86_64, but only for the unsigned versions.
2020-08-24 18:52:08 +01:00
AbigailBuccaneer
0f2757f000 Remove extra semicolons from big-endian handling
Fixes #3296.
2020-08-24 17:43:58 +01:00
Petr Mrázek
8a0027c73a NOISSUE Add world icons and world icon reset button 2020-08-22 01:34:55 +02:00
Petr Mrázek
c6c9feb3a2 NOISSUE attempt to fix build on macOS 2020-08-21 02:40:19 +02:00
Jamie Mansfield
b0f5f4cb13 GH-3095 New FTB platform support
Models are based on the models from my go-modpacksch library.

License:
========
The MIT License (MIT)

Copyright (c) Jamie Mansfield <https://www.jamiemansfield.me/>
Copyright (c) contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
2020-08-21 02:24:29 +02:00
Qaz
f6097ef2b7 Update iron.png 2020-08-19 17:45:59 +02:00
Qaz
bb88697354 Update gold.png 2020-08-19 17:45:57 +02:00
Qaz
762a019a49 Update diamond.png 2020-08-19 17:45:53 +02:00
Qaz
f44bab0e0c Update brick.png 2020-08-19 17:45:50 +02:00
Qaz
883dbe2933 Update stone.png 2020-08-19 17:42:16 +02:00
Qaz
6753e217a9 Update planks.png
Replace it with the new plank texture
2020-08-19 17:40:01 +02:00
Petr Mrázek
e7f373496e GH-3234 Add support for custom meta URLs at build time
This is not particularly interesting for non-developers.
Also includes some internal restructuring of URL constants in general.
2020-07-18 16:22:05 +02:00
Petr Mrázek
3158082b16 Merge pull request #3239 from haykam821/intermediary-errors
Fix Fabric loader error messages being reused for intermediary mappings
2020-07-18 12:31:45 +02:00
Petr Mrázek
814407ccec Merge pull request #3241 from Bowser65/feature/render-overlay
Minor: Render the skin overlay in SkinUtils
2020-07-18 12:31:14 +02:00
Bowser65
155f4f7471 NOISSUE render the skin overlay in SkinUtils 2020-07-12 19:36:40 +02:00
haykam821
90d45d2abf Fix Fabric loader error messages being reused for intermediary mappings 2020-07-09 12:17:59 -04:00
Petr Mrázek
cd57e354fe NOISSUE fix a bunch of valgrind errors 2020-06-28 13:25:53 +02:00
Petr Mrázek
a0ef20a264 NOISSUE rename ComponentList to PackProfile
It's not just components, so the naming needed cleaning up.
2020-06-27 12:02:31 +02:00
Petr Mrázek
4ca62916f5 GH-3189 update the nbt library 2020-06-07 21:32:42 +02:00
Petr Mrázek
41590f2e50 NOISSUE Switch twitch modpack page to use a QListView
This makes the lazy loading of the paginated search results
actually work.
2020-06-07 11:15:17 +02:00
Petr Mrázek
03dc8ddf81 Merge pull request #3169 from kb-1000/fix-accessibility-2
Allow compiling MultiMC using Qt builds without accessibility again
2020-05-30 15:34:18 +02:00
kb1000
79208a7383 Allow compiling MultiMC using Qt builds without accessibility again 2020-05-30 15:32:38 +02:00
Petr Mrázek
3680f1e599 Merge pull request #2864 from Hubcapp/patch-1
Update BUILD.md
2020-05-29 01:56:53 +02:00
Petr Mrázek
c4779e5a35 Merge pull request #3127 from OverMighty/develop
fix: add support for args with spaces to MultiMC::messageReceived()
2020-05-29 01:55:27 +02:00
Petr Mrázek
aad34f3679 Merge pull request #3165 from kvverti/wsl-messages
Clarify WSL error messages
2020-05-29 00:59:28 +02:00
Thalia Nero
0047ca454f Clarify WSL error messages 2020-05-28 17:59:54 -04:00
Petr Mrázek
07a1052c69 Fix. 2020-05-28 23:31:50 +02:00
Petr Mrázek
ba6a97557a NOISSUE prevent evil from winning 2020-05-28 23:17:50 +02:00
Petr Mrázek
e7f79c9076 Remove some old forge hacks
Forge apparently removed all `.pack.xz` files without warning.
It broke a bunch of stuff, as always. But it also means we don't need some ugly code anymore.

This is removed:

- Support for 'forge-pack-xz' and the forge-specific file download compression.
- The pack200 library we no longer need.

This stays:

- The LZMA decompression library - we may still want to use it.
2020-05-19 15:13:16 +02:00
Petr Mrázek
9eaa636908 GH-2802 remove extra remapping of indexes in VersionPage model 2020-05-17 19:56:59 +02:00
Petr Mrázek
997c25620a GH-3131 fix not working with proxy ports > 32767 2020-05-17 13:59:30 +02:00
OverMighty
bbcacec6ec fix: add support for args with spaces to MultiMC::messageReceived()
Previously, when the main instance of MultiMC would receive an `import`
or `launch` message from another instance, it would split the message on
each space, and only read the first word of the argument (zip path/URL
or instance ID). This commit fixes that problem by sectioning the
message string instead.
2020-05-04 11:37:02 +02:00
OverMighty
313a6574c1 chore: pull changes from upstream 2020-05-04 11:36:41 +02:00
Petr Mrázek
5ca5661c23 NOISSUE expose twitch pack url, description and author list 2020-04-29 21:17:51 +02:00
Petr Mrázek
cb0887ffa8 Merge branch 'develop' of https://github.com/Moresteck/MultiMC5 into develop 2020-04-29 20:32:47 +02:00
Moresteck
1f9378af9f NOISSUE Fixed online saving in early Classic versions 2020-04-19 20:31:04 +01:00
John C. Allwein
5c921589f1 NOISSUE fix compiling of api/logic/Version.cpp
-Wrange-loop-construct triggers this error in clang
2020-04-19 14:36:42 -04:00
Petr Mrázek
130e1263b6 Merge pull request #3046 from Ghalid/mac-build-instructions
Fix macOS build instructions so that git submodule commands succeed
2020-04-11 02:33:02 +02:00
Petr Mrázek
296ff6de96 NOISSUE Add pagination support to twitch pack search
Try searching for 'craft'. Now it gives ALL the results, not just
the first page of 25.
2020-04-01 02:08:11 +02:00
Petr Mrázek
5e951299b2 NOISSUE fix build 2020-04-01 00:59:18 +02:00
Petr Mrázek
3ff93a4216 NOISSUE Bare-bones twitch pack browser 2020-04-01 00:44:24 +02:00
Petr Mrázek
21ac860e27 Bump dev version to 0.6.12 2020-03-29 04:03:04 +02:00
Petr Mrázek
3ad9ea507e NOISSUE update version number, changelog and credits in about dialog 2020-03-29 03:12:57 +02:00
Petr Mrázek
c9e851f12f GH-2544 enable Forge install button for >= 1.13 2020-03-28 15:55:12 +01:00
Petr Mrázek
0281845fc8 GH-2544 allow adding files to libraries without affecting classpath
This is done by adding library-like objects into the `mavenFiles`
list in version JSONs.
2020-03-27 02:23:15 +01:00
Petr Mrázek
e6cc65cf69 NOISSUE no means no, #2 2020-03-26 10:38:13 +01:00
Petr Mrázek
ebb17cb5f8 NOISSUE no means no. 2020-03-26 03:51:14 +01:00
Ghalid
164bb6c78c NOISSUE change directory before running git submodule commands in mac instructions 2020-03-22 12:17:56 -04:00
Petr Mrázek
69490535d0 Merge pull request #2979 from Brottweiler/desktop-entry-tweak
Tweak GenericName for Linux desktop entry
2020-02-25 18:02:09 +01:00
Brottweiler
95a09ba099 GH-2979 Tweak GenericName 2020-02-24 19:39:22 +01:00
Petr Mrázek
6cb956b45b NOISSUE Nice. 2020-02-24 18:59:36 +01:00
OverMighty
47fa7b3f8c GH-2988 add --import command-line option
When specified, opens the "Import from zip" dialog as soon as the main
window is shown, with the URL field prefilled with the argument given to
the option.

Closes #2998
2020-02-24 18:52:26 +01:00
OverMighty
381c12547f feat: allow telling the main process to import a zip
This commit enhances the IPC message system and adds a new IPC command
in order to allow secondary MultiMC processes that were started by
executing MultiMC with the `--import` command-line option to tell the
main MutliMC process to open the "Import from zip" dialog window and
prefill the URL field with the specified URL.
2020-02-09 16:17:41 +01:00
OverMighty
060d6955e1 style: follow upstream code style 2020-02-09 15:45:31 +01:00
Petr Mrázek
d58481e0de NOISSUE fix some changelog wording 2020-02-09 00:03:20 +01:00
Chris Lane
a6085db0ae Add manpage doc 2020-02-08 16:58:45 +00:00
Petr Mrázek
08f85f1a93 Update changelog and set version to 0.6.8 2020-02-08 15:00:20 +01:00
OverMighty
bc04d89c32 feat: add --import command-line option
When specified, opens the "Import from zip" dialog as soon as the main
window is shown, with the URL field prefilled with the argument given to
the option.

Closes #2998
2020-02-05 12:37:09 +01:00
Petr Mrázek
bc98181ec2 GH-2769 add an option to not copy play time when copying instances 2020-01-09 15:31:32 +01:00
Petr Mrázek
6a095deea6 GH-2832 add .minecraft and libraries buttons to version page 2020-01-09 13:45:46 +01:00
Petr Mrázek
355e5e24da GH-2819 mod list filter now also looks at descriptions and authors 2020-01-08 21:12:45 +01:00
Petr Mrázek
8bdff97ac0 GH-2839 remove username wording from login and account dialogs 2020-01-08 13:37:05 +01:00
Petr Mrázek
6288805f37 GH-2853 fix collapsing state being sticky in group view
Now it resets properly and collapsing a group doesn't make
all subsequent clicks collapse groups.
2020-01-08 09:00:54 +01:00
Petr Mrázek
0d157d86b5 NOISSUE add wget as a dependency for deb package 2020-01-08 06:28:42 +01:00
Petr Mrázek
f413e61cd8 NOISSUE Do not crash when dependencies are customized and conflict 2020-01-08 04:41:47 +01:00
Petr Mrázek
3581f5384f GH-2880 make services status widget open a valid page
https://help.mojang.com/ no longer has API status
2020-01-07 08:56:00 +01:00
Petr Mrázek
480b298635 NOISSUE Attempt to add build status and test teamcity build reporting 2019-11-27 00:35:42 +01:00
Petr Mrázek
b54b25231e Remove travis-ci and github actions leftovers 2019-11-26 21:09:37 +01:00
Petr Mrázek
43628556ed NOISSUE fix comment in Commandline.h 2019-11-26 19:06:45 +01:00
Petr Mrázek
b5adff14ab NOISSUE remove dead reference to TwitchPage.ui 2019-11-18 02:38:08 +01:00
Petr Mrázek
af5120c828 GH-2859 remove twitch page and modpack import from URL
The functionality was broken, beyond repair and an ongoing maintenance
nightmare.
2019-11-18 00:38:36 +01:00
Petr Mrázek
47ed2f48d4 NOISSUE put legacy FTB support in a namespace, fix its base URL 2019-11-03 23:48:12 +01:00
Petr Mrázek
0c9340a3d2 NOISSUE fix translation string for Twitch drop area 2019-10-14 23:51:36 +02:00
Petr Mrázek
9cc5ebcdd1 GH-2859 improve UI for twitch pack import with drag&drop 2019-10-14 02:31:53 +02:00
Petr Mrázek
c60647523e NOISSUE remove remains of what could have been technic integration 2019-10-14 01:05:38 +02:00
Petr Mrázek
9165232ba4 NOISSUE remove unused 'PackagesPage' 2019-10-13 22:31:14 +02:00
Petr Mrázek
31d0507e07 NOISSUE make the main window title only 'MultiMC' for screen readers 2019-10-05 22:51:55 +02:00
Petr Mrázek
cca766cfd9 Merge pull request #2871 from kb-1000/fix-accessibility-fix
Remove include of qtguiglobal.h
2019-10-01 16:15:45 +02:00
kb1000
0d41bc5e13 Remove include of qtguiglobal.h 2019-10-01 15:55:40 +02:00
Petr Mrázek
e27309d08a Merge pull request #2870 from kb-1000/fix-accessibility
Add checks for QT_NO_ACCESSIBILITY to prevent build issues with Qt without accessibility
2019-10-01 15:40:39 +02:00
kb1000
dec6759e61 Add checks for QT_NO_ACCESSIBILITY to prevent build issues with Qt without accessibility 2019-10-01 14:28:06 +02:00
Petr Mrázek
ce7917048a NOISSUE remove more html rom About dialog 2019-09-30 23:50:32 +02:00
Hubcapp
e6b5d9656a Update BUILD.md
the `git clone` command provided doesn't work and gives this error on Debian
```
$ git clone --recursive git@github.com:MultiMC/MultiMC5.git src
Cloning into 'src'...
The authenticity of host 'github.com (192.30.253.113)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,192.30.253.113' (RSA) to the list of known hosts.
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
```
I am suggesting an alternative command to provide to users in BUILD.md instead.
2019-09-27 09:01:25 -05:00
Petr Mrázek
e6936212d6 NOISSUE remove some ugly html strings to make translating slightly nicer 2019-09-27 00:23:30 +02:00
Petr Mrázek
1210d3abf1 NOISSUE fix display of european portuguese in language lists 2019-09-27 00:23:03 +02:00
Petr Mrázek
ffe84d6ec7 NOISSUE remove some dead things 2019-09-26 20:08:47 +02:00
Petr Mrázek
19015de258 NOISSUE correct some parts of the README, one more translation string change 2019-09-26 09:03:25 +02:00
Petr Mrázek
5c0c26cd25 TRANSLATIONS-82 fix typos: resove -> resolve 2019-09-25 22:31:09 +02:00
Petr Mrázek
4cc7427eb4 TRANSLATIONS-73 remove some strings that don't show up in the UI 2019-09-25 22:29:50 +02:00
Petr Mrázek
6531aaa7bc Try using github actions
Let's see how badly this goes
2019-09-19 10:56:11 +02:00
Petr Mrázek
a35a2e877e NOISSUE remove nonsensical logic related to 'MultiMC.app/' prefixes in update manifests 2019-09-19 01:13:02 +02:00
Petr Mrázek
4e93c4d012 NOISSUE escape tcversion more 2019-09-17 01:26:37 +02:00
Petr Mrázek
7bb23b4142 NOISSUE add some escaping to make the tc version print actually do something 2019-09-17 01:22:00 +02:00
Petr Mrázek
b420f4bafb Merge pull request #2849 from AshleighTheCutie/develop
Message cleanup
2019-09-17 01:12:45 +02:00
Petr Mrázek
0e0a017175 NOISSUE add a way to extract the version into a TC variable 2019-09-17 00:53:30 +02:00
Ashleigh
137fe7e3c0 Cleaned up messages, made some more descriptive, made some nicer. 2019-09-14 22:31:13 -05:00
Ashleigh
9689e5cea7 Added text notifying that password change may cause logout 2019-09-14 22:12:23 -05:00
Petr Mrázek
1ede75aa79 Merge pull request #2848 from AshleighTheCutie/develop
Window Size to 800x600
2019-09-15 04:34:03 +02:00
Ashleigh
9ee3a84817 Window Size to 800x600 2019-09-14 21:15:51 -05:00
Petr Mrázek
8750ca8b36 NOISSUE add bee icon 2019-08-26 02:25:42 +02:00
Petr Mrázek
1747f413b9 GH-851 save, load and use group expansion status 2019-08-20 02:58:27 +02:00
Petr Mrázek
6d975748c0 Merge pull request #2796 from QuImUfu/bug_fix
GH-2787 fix "Download All" button
2019-08-14 22:12:58 +02:00
Petr Mrázek
b050c7c075 Merge pull request #2803 from kb-1000/escape-semicolon
Escape ; too in instance folder names
2019-08-14 22:12:39 +02:00
Kaeptm Blaubaer
84e0cb1daa Escape ; too in instance folder names 2019-08-13 07:39:00 +02:00
Petr Mrázek
5074a97cb3 NOISSUE tweak toolbar and custom widget titles for better translations 2019-08-10 20:12:42 +02:00
Petr Mrázek
441e8980b8 NOISSUE fix small memory leaks 2019-08-10 19:58:58 +02:00
Johannes Michael Andreas Merl
84c53273ce GH-2787 fix "Download All" button 2019-08-09 00:11:42 +02:00
Petr Mrázek
c291946d2a NOISSUE do not lose selection on mod enable/disable toggle 2019-08-05 00:46:59 +02:00
Petr Mrázek
dfb30d9139 NOISSUE warn users about MS Edge being bad 2019-08-04 21:14:59 +02:00
Petr Mrázek
4ed67413ac GH-988 add ability to toggle mods with keyboard 2019-08-04 21:13:50 +02:00
Petr Mrázek
d31184f9a4 GH-2738 check for being in the temp folder better 2019-08-04 18:11:55 +02:00
Petr Mrázek
b75ba53d4b GH-2785 fix crash caused by starting multiple mod folder update tasks 2019-08-04 11:12:19 +02:00
Petr Mrázek
9037873928 NOISSUE update changelog 2019-08-04 06:26:09 +02:00
Petr Mrázek
ce4a55bc3b NOISSUE fix listing of mods in log, improve display with unicode 2019-08-04 05:08:40 +02:00
Petr Mrázek
6b82e942d0 NOISSUE fix build on linux 2019-08-04 03:39:25 +02:00
Petr Mrázek
a3ffa3d665 NOISSUE asynchronous, parallel mod folder listing and mod resolving 2019-08-04 03:27:53 +02:00
Petr Mrázek
7d13e31198 NOISSUE refactor Mod a bunch, get rid of dead code 2019-08-03 05:30:46 +02:00
Petr Mrázek
40c9af1a8b NOISSUE remove dependency of legacy mod list on the Mod class 2019-08-03 03:12:48 +02:00
Petr Mrázek
f5f3149dcf NOISSUE update changelog and version 2019-08-03 00:48:34 +02:00
Petr Mrázek
7b00d47fe0 NOISSUE tweak UI geometry and remove old language selection 2019-08-02 23:52:19 +02:00
Petr Mrázek
930d39b5f2 GH-2550 soring of mods by enabled status, cascade sorting to name and version 2019-07-31 01:28:55 +02:00
Petr Mrázek
bafcf93eb1 NOISSUE fix bug with drag & drop not working with empty mod list 2019-07-31 01:27:35 +02:00
Petr Mrázek
bd93c3b4e0 NOISSUE fix build 2019-07-30 01:25:37 +02:00
Petr Mrázek
09f7a426ab GH-2722 GH-2762 Improve mod list sorting
Sorting by version understands version numbers
Sorting by name removes 'The' prefixes before sorting
2019-07-30 01:16:56 +02:00
Petr Mrázek
e4fd50e210 NOISSUE make the language translation prompt translateable 2019-07-30 01:16:02 +02:00
Petr Mrázek
3ee5a63c5c NOISSUE make notes page focusable with tab key 2019-07-25 01:13:47 +02:00
Petr Mrázek
7dfe73df0c NOISSUE add context menus to pages with toolbars 2019-07-25 01:02:30 +02:00
Petr Mrázek
c3e61536a3 NOISSUE automatically open the log page when starting the instance 2019-07-24 00:24:02 +02:00
Petr Mrázek
a0e45c5d1d NOISSUE fix build 2019-07-23 01:05:23 +02:00
Petr Mrázek
bf38021937 NOISSUE improve toolbars 2019-07-23 00:48:14 +02:00
Petr Mrázek
1e5b595923 NOISSUE fix build failures 2019-07-22 01:44:19 +02:00
Petr Mrázek
d6c6653872 NOISSUE Add basic accessibility support to GroupView 2019-07-22 01:40:52 +02:00
Petr Mrázek
3b32730526 Merge pull request #2758 from telans/patch-1
Fix translation repository link
2019-07-21 01:33:31 +02:00
telans
1703dbeb57 Fix translation repository link 2019-07-21 10:50:30 +12:00
Petr Mrázek
81fdde6fdd NOISSUE convert accounts page to use a toolbar for the side menu 2019-07-19 08:29:31 +02:00
Petr Mrázek
3d5869e1cf NOISSUE fix overly large margins in the instance settings page 2019-07-17 02:17:09 +02:00
Petr Mrázek
edc5378333 NOISSUE add spacer to the screenshot page toolbar 2019-07-17 02:09:54 +02:00
Petr Mrázek
95febe5436 NOISSUE convert rest of the instance pages to use toolbars for side menus 2019-07-17 02:01:29 +02:00
Petr Mrázek
5b153a5165 GH-2748 disable the version page toolbar labels 2019-07-16 08:58:00 +02:00
Petr Mrázek
decd4ae7ab NOISSUE Make mod folder pages use toolbars instead of button layouts 2019-07-16 01:30:53 +02:00
Petr Mrázek
2eec1df1a0 NOISSUE hide main toolbar toggle action instead of working around it 2019-07-16 01:30:09 +02:00
Petr Mrázek
6fde775b90 NOISSUE Show Version page while the instancer is running.
All controls are disabled.
2019-07-15 23:16:34 +02:00
Petr Mrázek
80b3efff11 NOISSUE Do not hide mods list pages when the instance is running.
Instead, disable (most of) the controls.
2019-07-15 01:07:21 +02:00
Petr Mrázek
e4273d6a17 GH-358 Make version page use a toolbar for all the actions
This should make it possible to make it fit on small screens again.
2019-07-14 05:37:10 +02:00
1338 changed files with 32394 additions and 23979 deletions

View File

@@ -1,5 +0,0 @@
{
"project_id": "MultiMC5",
"conduit_uri": "http://ph.multimc.org"
}

View File

@@ -1,25 +0,0 @@
UseTab: false
IndentWidth: 4
TabWidth: 4
ConstructorInitializerIndentWidth: 4
AccessModifierOffset: -4
IndentCaseLabels: false
IndentFunctionDeclarationAfterType: false
NamespaceIndentation: None
BreakBeforeBraces: Allman
AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
ColumnLimit: 160
MaxEmptyLinesToKeep: 1
Standard: Cpp11
Cpp11BracedListStyle: true
SpacesInParentheses: false
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
SpaceAfterControlStatementKeyword: true
AlignTrailingComments: true
SpacesBeforeTrailingComments: 1

View File

@@ -1,51 +0,0 @@
<!--
Before submitting this issue, please make sure you have:
1. Filled out this form completely, the only optional field is "additional info".
- Use as many details as possible and state the problem clearly.
2. Proof-read your ENTIRE issue report.
- Grammar and spelling mistakes make issue reports harder to understand.
3. Made sure your problem is not caused by an issue in your own modpack.
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
4. Given the issue a descriptive title.
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
5. Place all information below the ---- of lines.
- It makes the issue look pretty
If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
-->
System Information
-----------------------------
MultiMC version:
Operating System:
Summary of the issue or suggestion:
----------------------------------------------
What should happen:
------------------------------
Steps to reproduce the issue (Add more if needed):
-------------------------------------------------------------
1.
2.
3.
Suspected cause:
---------------------------
Logs/Screenshots:
----------------------------
[//]: # (Please refer to https://github.com/MultiMC/MultiMC5/wiki/Log-Upload for instructions on how to attach your logs.)
Additional Info:
---------------------------

51
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Bug Report
description: File a bug report
labels: [bug, needs-triage]
body:
- type: markdown
attributes:
value: |
If you need help with running Minecraft, please visit us [on our Discord](https://discord.gg/multimc) before making a bug report.
Before submitting a bug report, please make sure you have read this *entire* form, and that:
* You have read the [FAQ](https://github.com/MultiMC/Launcher/wiki/FAQ) and it has not answered your question
* Your bug is not caused by Minecraft or any mods you have installed.
* Your issue has not been reported before, [make sure to use the search function!](https://github.com/MultiMC/Launcher/issues)
**Do not forget to give your issue a descriptive title.** "Bug in the instance screen" makes it hard to distinguish issues at a glance.
- type: dropdown
attributes:
label: Operating System
description: If you know this bug occurs on multiple operating systems, select all you have tested.
multiple: true
options:
- Windows
- macOS
- Linux
- Other
- type: textarea
attributes:
label: Description of bug
description: What did you expect to happen, what happened, and why is it incorrect?
placeholder: The cat button should show a cat, but it showed a dog instead!
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: A bulleted list, or an exported instance if relevant.
placeholder: "* Press the cat button"
validations:
required: true
- type: textarea
attributes:
label: Suspected cause
description: If you know what could be causing this bug, describe it here.
validations:
required: false
- type: checkboxes
attributes:
label: This issue is unique
options:
- label: I have searched the issue tracker and did not find an issue describing my bug.
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: MultiMC Discord
url: https://discord.gg/multimc
about: Please ask for support here before opening an issue.

38
.github/ISSUE_TEMPLATE/suggestion.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Suggestion
description: Make a suggestion
labels: [idea, needs-triage]
body:
- type: markdown
attributes:
value: |
### Use this form to suggest a feature for MultiMC.
- type: input
attributes:
label: Role
description: In what way do you use MultiMC that needs this feature?
placeholder: I play modded Minecraft.
validations:
required: true
- type: input
attributes:
label: Suggestion
description: What do you want MultiMC to do?
placeholder: I want the cat button to meow.
validations:
required: true
- type: input
attributes:
label: Benefit
description: Why do you need MultiMC to do this?
placeholder: so that I can always hear a cat when I need to.
validations:
required: true
- type: checkboxes
attributes:
label: This suggestion is unique
options:
- label: I have searched the issue tracker and did not find an issue describing my suggestion, especially not one that has been rejected.
required: true
- type: textarea
attributes:
label: You may use the editor below to elaborate further.

11
.gitignore vendored
View File

@@ -1,22 +1,21 @@
Thumbs.db
.kdev4
*.kdev4
.user
.directory
resources/CMakeFiles
resources/MultiMCLauncher.jar
*~
*.swp
html/
# Project Files
MultiMC5.kdev4
MultiMC.pro.user
*.pro.user
CMakeLists.txt.user
CMakeLists.txt.user.*
/.project
/.settings
/.idea
cmake-build-*/
Debug
# Build dirs
build
@@ -30,3 +29,7 @@ tags
#OSX Stuff
.DS_Store
branding/
secrets/
run/

2
.gitmodules vendored
View File

@@ -1,6 +1,8 @@
[submodule "depends/libnbtplusplus"]
path = libraries/libnbtplusplus
url = https://github.com/MultiMC/libnbtplusplus.git
pushurl = git@github.com:MultiMC/libnbtplusplus.git
[submodule "libraries/quazip"]
path = libraries/quazip
url = https://github.com/MultiMC/quazip.git
pushurl = git@github.com:MultiMC/quazip.git

View File

@@ -1,38 +0,0 @@
# General set up
language: cpp
cache: apt
matrix:
include:
- os: linux
dist: precise
sudo: required
compiler: gcc
env: TRAVIS_DIST=precise QT_VERSION=5.4.2
- os: linux
dist: precise
sudo: required
compiler: gcc
env: TRAVIS_DIST=precise QT_VERSION=5.6.2
- os: linux
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
- os: linux
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
# Install dependencies
install:
- source travis/prepare.sh # installs qt and cmake. need to source because some env vars are set from there
# Actual work
before_script:
- mkdir build
- cd build
- cmake -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH ..
script:
- make -j4 && make test ARGS="-V"

View File

@@ -7,14 +7,15 @@ Build Instructions
* [Getting the source](#source)
* [Linux](#linux)
* [Windows](#windows)
* [OS X](#os-x)
* [macOS](#macos)
# Note
MultiMC is a portable application and is not supposed to be installed into any system folders.
That would be anything outside your home folder. Before runing `make install`, make sure
That would be anything outside your home folder. Before running `make install`, make sure
you set the install path to something you have write access to. Never build this under
an administrator/root level account. Don't use `sudo`. It won't work and it's not supposed to work.
Also note that this guide is for development purposes only. No support is given for building your own fork or special build for any reason whatsoever.
# Getting the source
@@ -22,7 +23,7 @@ an administrator/root level account. Don't use `sudo`. It won't work and it's no
Clone the source code using git and grab all the submodules:
```
git clone git@github.com:MultiMC/MultiMC5.git
git clone https://github.com/MultiMC/Launcher.git
git submodule init
git submodule update
```
@@ -50,7 +51,7 @@ mkdir ~/MultiMC && cd ~/MultiMC
mkdir build
mkdir install
# clone the complete source
git clone --recursive git@github.com:MultiMC/MultiMC5.git src
git clone --recursive https://github.com/MultiMC/Launcher.git src
# configure the project
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install ../src
@@ -74,7 +75,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
### Loading the project in Qt Creator (optional)
1. Open Qt Creator.
2. Choose `File->Open File or Project`.
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
3. Navigate to the Launcher source folder you cloned and choose CMakeLists.txt.
4. Read the instructions that just popped up about a build location and choose one.
5. You should see "Run CMake" in the window.
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
@@ -82,7 +83,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
6. Cross your fingers and press the Run button (bottom left of Qt Creator).
- If the project builds successfully it will run and the MultiMC5 window will pop up.
- If the project builds successfully it will run and the Launcher window will pop up.
**If this doesn't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
@@ -92,14 +93,19 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
## Dependencies
* [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
- http://download.qt.io/new_archive/qt/5.6/5.6.0/qt-opensource-windows-x86-mingw492-5.6.0.exe
- Download the MinGW version (MSVC version does not work).
* [OpenSSL](https://github.com/IndySockets/OpenSSL-Binaries/tree/master/Archive/) -- Win32 OpenSSL, version 1.0.2g (from 2016)
- https://github.com/IndySockets/OpenSSL-Binaries/raw/master/Archive/openssl-1.0.2g-i386-win32.zip
- the usual OpenSSL for Windows (http://slproweb.com/products/Win32OpenSSL.html) only provides the newest version of OpenSSL, and we need the 1.0.2g version
- **Download the 32-bit version, not 64-bit.**
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
Put it somewhere on the `PATH`, so that it is accessible from the console.
Ensure that OpenSSL, zlib, Java and CMake are on `PATH`.
## Getting set up
@@ -115,9 +121,8 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- Installation can take a very long time, go grab a cup of tea or something and let it work.
### Installing OpenSSL
1. Run the OpenSSL installer,
2. It's best to choose the option to copy OpenSSL DLLs to the `/bin` directory
- If you do this you'll need to add that directory (the default being `C:\OpenSSL-Win32\bin`) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
1. Download .zip file from the link above.
2. Unzip and add the directory to PATH, so CMake can find it.
### Installing CMake
1. Run the CMake installer,
@@ -127,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
### Loading the project
1. Open Qt Creator,
2. Choose File->Open File or Project,
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt,
3. Navigate to the Launcher source folder you cloned and choose CMakeLists.txt,
4. Read the instructions that just popped up about a build location and choose one,
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- Otherwise you can skip this step.
@@ -137,49 +142,66 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
7. Cross your fingers and press the Run button (bottom left of Qt Creator)!
- If the project builds successfully it will run and the MultiMC5 window will pop up,
- If the project builds successfully it will run and the Launcher window will pop up,
- Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error.
The following .dlls are needed for the app to run (copy them to build directory if you want to be able to move the build to another pc):
```
platforms/qwindows.dll
libeay32.dll
libgcc_s_dw2-1.dll
libssp-0.dll
libstdc++-6.dll
libwinpthread-1.dll
Qt5Core.dll
Qt5Gui.dll
Qt5Network.dll
Qt5Svg.dll
Qt5Widgets.dll
Qt5Xml.dll
ssleay32.dll
zlib1.dll
```
**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
### Compile from command line on Windows
1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/MultiMC/MultiMC5.git`, and change directory to the folder you cloned to.
2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/MultiMC/Launcher.git`, and change directory to the folder you cloned to.
3. Make a build directory, and change directory to the directory and do `cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\Path\that\makes\sense\for\you`. By default, it will install to C:\Program Files (x86), which you might not want, if you want a local installation. If you want to install it to that directory, make sure to run the command window as administrator.
3. Do `mingw32-make -jX`, where X is the number of cores your CPU has plus one.
4. Now to wait for it to compile. This could take some time. Hopefully it compiles properly.
5. Run the command `mingw32-make install`, and it should install MultiMC, to whatever the `-DCMAKE_INSTALL_PREFIX` was.
6. In most cases, whenever compiling, the OpenSSL dll's aren't put into the directory to where MultiMC installs, meaning you cannot log in. The best way to fix this is just to do `copy C:\OpenSSL-Win32\*.dll C:\Where\you\installed\MultiMC\to`. This should copy the required OpenSSL dll's to log in.
# OS X
# macOS
### Install prerequisites:
* install homebrew
* then:
```
brew install qt5
brew tap homebrew/versions
brew install gcc48
brew install cmake
```
- Install XCode and set it up to the point where you can build things from a terminal
- Install the official build of CMake (https://cmake.org/download/)
- Install JDK 8 (https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html)
- Get Qt 5.6 and install it (https://download.qt.io/new_archive/qt/5.6/5.6.3/)
### Build
Pick an installation path - this is where the final `.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration.
```
git clone https://github.com/MultiMC/MultiMC5.git
git submodule init
git submodule update
cd MultiMC5
git clone --recursive https://github.com/MultiMC/Launcher.git
cd Launcher
mkdir build
cd build
export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
export CC=/usr/local/bin/gcc-4.8
export CXX=/usr/local/bin/g++-4.8
cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/Users/YOU/some/path/that/makes/sense/
make
cmake \
-DCMAKE_C_COMPILER=/usr/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX:PATH="$(dirname $PWD)/dist/" \
-DCMAKE_PREFIX_PATH="/path/to/Qt5.6/" \
-DQt5_DIR="/path/to/Qt5.6/" \
-DLauncher_LAYOUT=mac-bundle \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 \
..
make install
```
**These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
**Note:** The final app bundle may not run due to code signing issues, which
need to be fixed with `codesign -fs -`.

View File

@@ -1,18 +1,27 @@
cmake_minimum_required(VERSION 3.1)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD)
message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!")
endif()
if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
cmake_policy(SET CMP0020 OLD)
endif()
project(MultiMC)
project(Launcher)
enable_testing()
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD)
message(FATAL_ERROR "You are building the Launcher in-source. Please separate the build tree from the source tree.")
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
if(CMAKE_HOST_SYSTEM_VERSION MATCHES ".*[Mm]icrosoft.*" OR
CMAKE_HOST_SYSTEM_VERSION MATCHES ".*WSL.*"
)
message(FATAL_ERROR "Building the Launcher is not supported in Linux-on-Windows distributions.")
endif()
endif()
##################################### Set CMake options #####################################
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -41,42 +50,61 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
##################################### Set Application options #####################################
######## Set URLs ########
set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch Launcher's news RSS feed from.")
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
set(MultiMC_VERSION_HOTFIX 6)
set(Launcher_VERSION_MAJOR 0)
set(Launcher_VERSION_MINOR 6)
set(Launcher_VERSION_HOTFIX 14)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
# Build platform.
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
# Channel list URL
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
# Notification URL
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
# The metadata server
set(Launcher_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
# paste.ee API key
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
set(Launcher_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
# Google analytics ID
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
set(Launcher_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "" CACHE STRING "URL for the bug tracker.")
# Discord URL
set(Launcher_DISCORD_URL "" CACHE STRING "URL for the Discord guild.")
# Subreddit URL
set(Launcher_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.")
# Use the secrets library or a public stub?
option(Launcher_EMBED_SECRETS "Determines whether to embed secrets. Secrets are separate and non-public." OFF)
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
get_git_head_revision(Launcher_GIT_REFSPEC Launcher_GIT_COMMIT)
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
#### Custom target to just print the version.
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
add_custom_target(version echo "Version: ${Launcher_RELEASE_VERSION_NAME}")
add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.LAUNCHER_VERSION\\' value=\\'${Launcher_RELEASE_VERSION_NAME}\\']")
################################ 3rd Party Libs ################################
@@ -101,47 +129,55 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
####################################### Secrets #######################################
if(Launcher_EMBED_SECRETS)
add_subdirectory(secrets)
else()
add_subdirectory(notsecrets)
endif()
####################################### Install layout #######################################
# How to install the build results
set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
set(Launcher_LAYOUT "auto" CACHE STRING "The layout for the launcher installation (auto, win-bundle, lin-nodeps, mac-bundle)")
set_property(CACHE Launcher_LAYOUT PROPERTY STRINGS auto win-bundle lin-nodeps mac-bundle)
if(MultiMC_LAYOUT STREQUAL "auto")
if(Launcher_LAYOUT STREQUAL "auto")
if(UNIX AND APPLE)
set(MultiMC_LAYOUT_REAL "mac-bundle")
set(Launcher_LAYOUT_REAL "mac-bundle")
elseif(UNIX)
set(MultiMC_LAYOUT_REAL "lin-nodeps")
set(Launcher_LAYOUT_REAL "lin-nodeps")
elseif(WIN32)
set(MultiMC_LAYOUT_REAL "win-bundle")
set(Launcher_LAYOUT_REAL "win-bundle")
else()
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
endif()
else()
set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
set(Launcher_LAYOUT_REAL ${Launcher_LAYOUT})
endif()
if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources")
set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars")
set(BUNDLE_DEST_DIR ".")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
# Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors")
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}")
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: Minecraft launcher and management utility.")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.${Launcher_Name}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2021 ${Launcher_Copyright}")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
@@ -150,32 +186,9 @@ if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
set(INSTALL_BUNDLE "full")
# Add the icon
install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "bin/jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
elseif(Launcher_LAYOUT_REAL STREQUAL "lin-nodeps")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
@@ -187,28 +200,13 @@ elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
set(INSTALL_BUNDLE "nodeps")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
SET(Launcher_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY)
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
set(BINARY_DEST_DIR ".")
set(LIBRARY_DEST_DIR ".")
set(PLUGIN_DEST_DIR ".")
@@ -217,7 +215,7 @@ elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
set(JARS_DEST_DIR "jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.exe")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
@@ -236,7 +234,7 @@ set_directory_properties(PROPERTIES EP_BASE External)
option(NBT_BUILD_SHARED "Build NBT shared library" ON)
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++)
set(NBT_NAME Launcher_nbt++)
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus)
@@ -247,16 +245,17 @@ add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/quazip) # zip manipulation library
add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
add_subdirectory(libraries/optional-bare)
add_subdirectory(libraries/tomlc99) # toml parser
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
############################### Built Artifacts ###############################
add_subdirectory(api/logic)
add_subdirectory(api/gui)
add_subdirectory(buildconfig)
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
add_subdirectory(application)
add_subdirectory(launcher)

View File

@@ -1,6 +1,6 @@
# MultiMC
Copyright 2012-2019 MultiMC Contributors
Copyright 2012-2021 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -128,35 +128,6 @@
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
# Pack200
The GNU General Public License (GPL)
Version 2, June 1991
+ "CLASSPATH" EXCEPTION TO THE GPL
Certain source files distributed by Oracle America and/or its affiliates are
subject to the following clarification and special exception to the GPL, but
only where Oracle has expressly included in the particular source file's header
the words "Oracle designates this particular file as subject to the "Classpath"
exception as provided by Oracle in the LICENSE file that accompanied this code."
Linking this library statically or dynamically with other modules is making
a combined work based on this library. Thus, the terms and conditions of
the GNU General Public License cover the whole combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,
and to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent module,
the terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library. If
you modify this library, you may extend this exception to your version of
the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.
# Quazip
Copyright (C) 2005-2011 Sergey A. Tachenov
@@ -252,3 +223,82 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# optional-bare
Code from https://github.com/martinmoene/optional-bare/
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
# tomlc99
MIT License
Copyright (c) 2017 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# O2 (Katabasis fork)
Copyright (c) 2012, Akos Polster
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -5,41 +5,76 @@
MultiMC 5
=========
MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping. Here are the current [features](https://github.com/MultiMC/MultiMC5/wiki#features) of MultiMC.
MultiMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
## Development
The project uses C++ and Qt5 as the language and base framework. This might seem odd in the Minecraft community, but allows using 25MB of RAM, where other tools use an excessive amount of resources for no reason.
If you want to contribute, talk to us on [Discord](https://discord.gg/multimc) first.
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
While blindly submitting PRs is definitely possible, they're not necessarily going to get accepted.
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
We aren't looking for flashy features, but expanding upon the existing feature set without distruption or endangering future viability of the project is OK.
### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
The ci server is running at [ci.multimc.org](http://ci.multimc.org), where you can watch the builds happen in (or very close to) real time. It can also serve as a nice reference on how to set up the build environment on your end.
According to travis.ci, the builds are currently [![Build Status](https://travis-ci.org/MultiMC/MultiMC5.svg?branch=develop)](https://travis-ci.org/MultiMC/MultiMC5)
### Code formatting
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted.
Just follow the existing formatting.
In general, in order of importance:
* Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
* Prefer readability over dogma.
* Keep to the existing formatting.
* Indent with 4 space unless it's in a submodule.
* Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
## Translations
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
Translations can be done [on crowdin](https://translate.multimc.org). Please avoid making direct pull requests to the translations repository.
## Forking/Redistributing
## Forking/Redistributing/Custom builds policy
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
Part of the reason for using the Apache license is we don't want people using the "MultiMC" name when redistributing the project. This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).
Part of the reason for using the Apache license is that we don't want people using the "MultiMC" name when redistributing the project. This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).
Apache covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork *without* implying that you have our blessing.
## License
Copyright &copy; 2013-2019 MultiMC Contributors
Copyright &copy; 2013-2021 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
## Build status
### Linux (Intel32)
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Linux32_Build&guest=1">
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Linux32_Build)/statusIcon"/>
</a>
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Linux32_Deploy&guest=1">
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Linux32_Deploy)/statusIcon"/>
</a>
### Linux (AMD64)
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Linux64_Build&guest=1">
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Linux64_Build)/statusIcon"/>
</a>
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Linux64_Deploy&guest=1">
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Linux64_Deploy)/statusIcon"/>
</a>
### macOS (AMD64)
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_MacOS_Build&guest=1">
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_MacOS_Build)/statusIcon"/>
</a>
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_MacOS_Deploy&guest=1">
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_MacOS_Deploy)/statusIcon"/>
</a>
### Windows (Intel32)
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Windows_Build&guest=1">
Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Windows_Build)/statusIcon"/>
</a>
<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=Launcher_Launcher_Windows_Deploy&guest=1">
Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:Launcher_Launcher_Windows_Deploy)/statusIcon"/>
</a>

View File

@@ -1,34 +0,0 @@
project(MultiMC_gui LANGUAGES CXX)
set(GUI_SOURCES
DesktopServices.h
DesktopServices.cpp
# Icons
icons/MMCIcon.h
icons/MMCIcon.cpp
icons/IconList.h
icons/IconList.cpp
SkinUtils.cpp
SkinUtils.h
)
################################ COMPILE ################################
add_library(MultiMC_gui SHARED ${GUI_SOURCES})
set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_gui)
# Link
target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui)
# Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
# Install it
install(
TARGETS MultiMC_gui
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -1,37 +0,0 @@
#pragma once
#include <QUrl>
#include <QString>
#include "multimc_gui_export.h"
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices
{
/**
* Open a file in whatever application is applicable
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
/**
* Open a file in the specified application
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Run an application
*/
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Open a directory
*/
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
/**
* Open the URL, most likely in a browser. Maybe.
*/
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
}

View File

@@ -1,501 +0,0 @@
project(MultiMC_logic)
include (UnitTest)
set(CORE_SOURCES
# LOGIC - Base classes and infrastructure
BaseInstaller.h
BaseInstaller.cpp
BaseVersionList.h
BaseVersionList.cpp
InstanceList.h
InstanceList.cpp
InstanceTask.h
InstanceTask.cpp
LoggedProcess.h
LoggedProcess.cpp
MessageLevel.cpp
MessageLevel.h
BaseVersion.h
BaseInstance.h
BaseInstance.cpp
NullInstance.h
MMCZip.h
MMCZip.cpp
MMCStrings.h
MMCStrings.cpp
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
# Use tracking separate from memory management
Usable.h
# Prefix tree where node names are strings between separators
SeparatorPrefixTree.h
# WARNING: globals live here
Env.h
Env.cpp
# String filters
Filter.h
Filter.cpp
# JSON parsing helpers
Json.h
Json.cpp
FileSystem.h
FileSystem.cpp
Exception.h
# RW lock protected map
RWStorage.h
# A variable that has an implicit default value and keeps track of changes
DefaultVariable.h
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
QObjectPtr.h
# Compression support
GZip.h
GZip.cpp
# Command line parameter parsing
Commandline.h
Commandline.cpp
# Version number string support
Version.h
Version.cpp
# A Recursive file system watcher
RecursiveFileSystemWatcher.h
RecursiveFileSystemWatcher.cpp
)
add_unit_test(FileSystem
SOURCES FileSystem_test.cpp
LIBS MultiMC_logic
DATA testdata
)
add_unit_test(GZip
SOURCES GZip_test.cpp
LIBS MultiMC_logic
)
set(PATHMATCHER_SOURCES
# Path matchers
pathmatcher/FSTreeMatcher.h
pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h
)
set(NET_SOURCES
# network stuffs
net/ByteArraySink.h
net/ChecksumValidator.h
net/Download.cpp
net/Download.h
net/FileSink.cpp
net/FileSink.h
net/HttpMetaCache.cpp
net/HttpMetaCache.h
net/MetaCacheSink.cpp
net/MetaCacheSink.h
net/NetAction.h
net/NetJob.cpp
net/NetJob.h
net/PasteUpload.cpp
net/PasteUpload.h
net/Sink.h
net/URLConstants.cpp
net/URLConstants.h
net/Validator.h
)
# Game launch logic
set(LAUNCH_SOURCES
launch/steps/PostLaunchCommand.cpp
launch/steps/PostLaunchCommand.h
launch/steps/PreLaunchCommand.cpp
launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp
launch/steps/TextPrint.h
launch/steps/Update.cpp
launch/steps/Update.h
launch/LaunchStep.cpp
launch/LaunchStep.h
launch/LaunchTask.cpp
launch/LaunchTask.h
launch/LogModel.cpp
launch/LogModel.h
)
# Old update system
set(UPDATE_SOURCES
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
)
add_unit_test(UpdateChecker
SOURCES updater/UpdateChecker_test.cpp
LIBS MultiMC_logic
DATA updater/testdata
)
add_unit_test(DownloadTask
SOURCES updater/DownloadTask_test.cpp
LIBS MultiMC_logic
DATA updater/testdata
)
# Rarely used notifications
set(NOTIFICATIONS_SOURCES
# Notifications - short warning messages
notifications/NotificationChecker.h
notifications/NotificationChecker.cpp
)
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
# News System
news/NewsChecker.h
news/NewsChecker.cpp
news/NewsEntry.h
news/NewsEntry.cpp
)
# Icon interface
set(ICONS_SOURCES
# Icons System and related code
icons/IIconList.h
icons/IIconList.cpp
icons/IconUtils.h
icons/IconUtils.cpp
)
# Minecraft services status checker
set(STATUS_SOURCES
# Status system
status/StatusChecker.h
status/StatusChecker.cpp
)
# Support for Minecraft instances and launch
set(MINECRAFT_SOURCES
# Minecraft support
minecraft/auth/AuthSession.h
minecraft/auth/AuthSession.cpp
minecraft/auth/MojangAccountList.h
minecraft/auth/MojangAccountList.cpp
minecraft/auth/MojangAccount.h
minecraft/auth/MojangAccount.cpp
minecraft/auth/YggdrasilTask.h
minecraft/auth/YggdrasilTask.cpp
minecraft/auth/flows/AuthenticateTask.h
minecraft/auth/flows/AuthenticateTask.cpp
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp
minecraft/gameoptions/GameOptions.h
minecraft/gameoptions/GameOptions.cpp
minecraft/update/AssetUpdateTask.h
minecraft/update/AssetUpdateTask.cpp
minecraft/update/FMLLibrariesTask.cpp
minecraft/update/FMLLibrariesTask.h
minecraft/update/FoldersTask.cpp
minecraft/update/FoldersTask.h
minecraft/update/LibrariesTask.cpp
minecraft/update/LibrariesTask.h
minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h
minecraft/launch/CreateServerResourcePacksFolder.cpp
minecraft/launch/CreateServerResourcePacksFolder.h
minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h
minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
minecraft/launch/ReconstructAssets.h
minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyUpgradeTask.h
minecraft/legacy/LegacyUpgradeTask.cpp
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
minecraft/LaunchProfile.cpp
minecraft/LaunchProfile.h
minecraft/Component.cpp
minecraft/Component.h
minecraft/ComponentList.cpp
minecraft/ComponentList.h
minecraft/ComponentUpdateTask.cpp
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h
minecraft/Rule.cpp
minecraft/Rule.h
minecraft/OneSixVersionFormat.cpp
minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp
minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp
minecraft/ProfileUtils.h
minecraft/Library.cpp
minecraft/Library.h
minecraft/MojangDownloadInfo.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h
minecraft/Mod.cpp
minecraft/SimpleModList.h
minecraft/SimpleModList.cpp
minecraft/World.h
minecraft/World.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
# Forge and all things forge related
minecraft/forge/ForgeXzDownload.h
minecraft/forge/ForgeXzDownload.cpp
# Skin upload utilities
minecraft/SkinUpload.cpp
minecraft/SkinUpload.h
)
add_unit_test(GradleSpecifier
SOURCES minecraft/GradleSpecifier_test.cpp
LIBS MultiMC_logic
)
add_unit_test(MojangVersionFormat
SOURCES minecraft/MojangVersionFormat_test.cpp
LIBS MultiMC_logic
DATA minecraft/testdata
)
add_unit_test(Library
SOURCES minecraft/Library_test.cpp
LIBS MultiMC_logic
)
# FIXME: shares data with FileSystem test
add_unit_test(SimpleModList
SOURCES minecraft/SimpleModList_test.cpp
DATA testdata
LIBS MultiMC_logic
)
add_unit_test(ParseUtils
SOURCES minecraft/ParseUtils_test.cpp
LIBS MultiMC_logic
)
# the screenshots feature
set(SCREENSHOTS_SOURCES
screenshots/Screenshot.h
screenshots/ImgurUpload.h
screenshots/ImgurUpload.cpp
screenshots/ImgurAlbumCreation.h
screenshots/ImgurAlbumCreation.cpp
)
set(TASKS_SOURCES
# Tasks
tasks/Task.h
tasks/Task.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
)
set(SETTINGS_SOURCES
# Settings
settings/INIFile.cpp
settings/INIFile.h
settings/INISettingsObject.cpp
settings/INISettingsObject.h
settings/OverrideSetting.cpp
settings/OverrideSetting.h
settings/PassthroughSetting.cpp
settings/PassthroughSetting.h
settings/Setting.cpp
settings/Setting.h
settings/SettingsObject.cpp
settings/SettingsObject.h
)
add_unit_test(INIFile
SOURCES settings/INIFile_test.cpp
LIBS MultiMC_logic
)
set(JAVA_SOURCES
# Java related code
java/launch/CheckJava.cpp
java/launch/CheckJava.h
java/JavaChecker.h
java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h
java/JavaInstall.cpp
java/JavaInstallList.h
java/JavaInstallList.cpp
java/JavaUtils.h
java/JavaUtils.cpp
java/JavaVersion.h
java/JavaVersion.cpp
)
add_unit_test(JavaVersion
SOURCES java/JavaVersion_test.cpp
LIBS MultiMC_logic
)
set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h
translations/TranslationsModel.cpp
translations/POTranslator.h
translations/POTranslator.cpp
)
set(TOOLS_SOURCES
# Tools
tools/BaseExternalTool.cpp
tools/BaseExternalTool.h
tools/BaseProfiler.cpp
tools/BaseProfiler.h
tools/JProfiler.cpp
tools/JProfiler.h
tools/JVisualVM.cpp
tools/JVisualVM.h
tools/MCEditTool.cpp
tools/MCEditTool.h
)
set(META_SOURCES
# Metadata sources
meta/JsonFormat.cpp
meta/JsonFormat.h
meta/BaseEntity.cpp
meta/BaseEntity.h
meta/VersionList.cpp
meta/VersionList.h
meta/Version.cpp
meta/Version.h
meta/Index.cpp
meta/Index.h
)
set(FTB_SOURCES
modplatform/ftb/FtbPackFetchTask.h
modplatform/ftb/FtbPackFetchTask.cpp
modplatform/ftb/FtbPackInstallTask.h
modplatform/ftb/FtbPackInstallTask.cpp
modplatform/ftb/FtbPrivatePackManager.h
modplatform/ftb/FtbPrivatePackManager.cpp
modplatform/ftb/PackHelpers.h
)
set(FLAME_SOURCES
# Flame
modplatform/flame/PackManifest.h
modplatform/flame/PackManifest.cpp
modplatform/flame/FileResolvingTask.h
modplatform/flame/FileResolvingTask.cpp
modplatform/flame/UrlResolvingTask.h
modplatform/flame/UrlResolvingTask.cpp
)
add_unit_test(Index
SOURCES meta/Index_test.cpp
LIBS MultiMC_logic
)
################################ COMPILE ################################
# we need zlib
find_package(ZLIB REQUIRED)
set(LOGIC_SOURCES
${CORE_SOURCES}
${PATHMATCHER_SOURCES}
${NET_SOURCES}
${LAUNCH_SOURCES}
${UPDATE_SOURCES}
${NOTIFICATIONS_SOURCES}
${NEWS_SOURCES}
${STATUS_SOURCES}
${MINECRAFT_SOURCES}
${SCREENSHOTS_SOURCES}
${TASKS_SOURCES}
${SETTINGS_SOURCES}
${JAVA_SOURCES}
${TRANSLATIONS_SOURCES}
${TOOLS_SOURCES}
${META_SOURCES}
${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
)
message(STATUS "FOO! ${LOGIC_SOURCES}")
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_logic)
# Link
target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
# Mark and export headers
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
# Install it
install(
TARGETS MultiMC_logic
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -1,209 +0,0 @@
#include "Env.h"
#include "net/HttpMetaCache.h"
#include "BaseVersion.h"
#include "BaseVersionList.h"
#include <QDir>
#include <QCoreApplication>
#include <QNetworkProxy>
#include <QNetworkAccessManager>
#include <QDebug>
#include "tasks/Task.h"
#include "meta/Index.h"
#include "FileSystem.h"
#include <QDebug>
struct Env::Private
{
QNetworkAccessManager m_qnam;
shared_qobject_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IIconList> m_iconlist;
shared_qobject_ptr<Meta::Index> m_metadataIndex;
QString m_jarsPath;
QSet<QString> m_features;
};
static Env * instance;
/*
* The *NEW* global rat nest of an object. Handle with care.
*/
Env::Env()
{
d = new Private();
}
Env::~Env()
{
delete d;
}
Env& Env::Env::getInstance()
{
if(!instance)
{
instance = new Env();
}
return *instance;
}
void Env::dispose()
{
delete instance;
instance = nullptr;
}
shared_qobject_ptr< HttpMetaCache > Env::metacache()
{
return d->m_metacache;
}
QNetworkAccessManager& Env::qnam() const
{
return d->m_qnam;
}
std::shared_ptr<IIconList> Env::icons()
{
return d->m_iconlist;
}
void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
{
d->m_iconlist = iconlist;
}
shared_qobject_ptr<Meta::Index> Env::metadataIndex()
{
if (!d->m_metadataIndex)
{
d->m_metadataIndex.reset(new Meta::Index());
}
return d->m_metadataIndex;
}
void Env::initHttpMetaCache()
{
auto &m_metacache = d->m_metacache;
m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->Load();
}
void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password)
{
// Set the application proxy settings.
if (proxyTypeStr == "SOCKS5")
{
QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
}
else if (proxyTypeStr == "HTTP")
{
QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
}
else if (proxyTypeStr == "None")
{
// If we have no proxy set, set no proxy and return.
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
}
else
{
// If we have "Default" selected, set Qt to use the system proxy settings.
QNetworkProxyFactory::setUseSystemConfiguration(true);
}
qDebug() << "Detecting proxy settings...";
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
d->m_qnam.setProxy(proxy);
QString proxyDesc;
if (proxy.type() == QNetworkProxy::NoProxy)
{
qDebug() << "Using no proxy is an option!";
return;
}
switch (proxy.type())
{
case QNetworkProxy::DefaultProxy:
proxyDesc = "Default proxy: ";
break;
case QNetworkProxy::Socks5Proxy:
proxyDesc = "Socks5 proxy: ";
break;
case QNetworkProxy::HttpProxy:
proxyDesc = "HTTP proxy: ";
break;
case QNetworkProxy::HttpCachingProxy:
proxyDesc = "HTTP caching: ";
break;
case QNetworkProxy::FtpCachingProxy:
proxyDesc = "FTP caching: ";
break;
default:
proxyDesc = "DERP proxy: ";
break;
}
proxyDesc += QString("%3@%1:%2 pass %4")
.arg(proxy.hostName())
.arg(proxy.port())
.arg(proxy.user())
.arg(proxy.password());
qDebug() << proxyDesc;
}
QString Env::getJarsPath()
{
if(d->m_jarsPath.isEmpty())
{
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
}
return d->m_jarsPath;
}
void Env::setJarsPath(const QString& path)
{
d->m_jarsPath = path;
}
void Env::enableFeature(const QString& featureName, bool state)
{
if(state)
{
d->m_features.insert(featureName);
}
else
{
d->m_features.remove(featureName);
}
}
bool Env::isFeatureEnabled(const QString& featureName) const
{
return d->m_features.contains(featureName);
}
void Env::getEnabledFeatures(QSet<QString>& features) const
{
features = d->m_features;
}
void Env::setEnabledFeatures(const QSet<QString>& features) const
{
d->m_features = features;
}

View File

@@ -1,65 +0,0 @@
#pragma once
#include <memory>
#include "icons/IIconList.h"
#include <QString>
#include <QMap>
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
class QNetworkAccessManager;
class HttpMetaCache;
class BaseVersionList;
class BaseVersion;
namespace Meta
{
class Index;
}
#if defined(ENV)
#undef ENV
#endif
#define ENV (Env::getInstance())
class MULTIMC_LOGIC_EXPORT Env
{
friend class MultiMC;
private:
struct Private;
Env();
~Env();
static void dispose();
public:
static Env& getInstance();
QNetworkAccessManager &qnam() const;
shared_qobject_ptr<HttpMetaCache> metacache();
std::shared_ptr<IIconList> icons();
/// init the cache. FIXME: possible future hook point
void initHttpMetaCache();
/// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
void registerIconList(std::shared_ptr<IIconList> iconlist);
shared_qobject_ptr<Meta::Index> metadataIndex();
QString getJarsPath();
void setJarsPath(const QString & path);
bool isFeatureEnabled(const QString & featureName) const;
void enableFeature(const QString & featureName, bool state = true);
void getEnabledFeatures(QSet<QString> & features) const;
void setEnabledFeatures(const QSet<QString> & features) const;
protected:
Private * d;
};

View File

@@ -1,10 +0,0 @@
#pragma once
#include <QString>
#include "multimc_logic_export.h"
namespace Strings
{
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
}

View File

@@ -1,71 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/Mod.h"
#include <functional>
#include "multimc_logic_export.h"
#include <JlCompress.h>
namespace MMCZip
{
/**
* Merge two zip files, using a filter function
*/
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
const JlCompress::FilterFunction filter = nullptr);
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
/**
* Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a subdirectory from an archive
*/
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
}

View File

@@ -1,7 +0,0 @@
#include "IIconList.h"
// blargh
IIconList::~IIconList()
{
}

View File

@@ -1,26 +0,0 @@
#pragma once
#include <QString>
#include <QStringList>
#include "multimc_logic_export.h"
enum IconType : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
class MULTIMC_LOGIC_EXPORT IIconList
{
public:
virtual ~IIconList();
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
virtual bool deleteIcon(const QString &key) = 0;
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0;
virtual void installIcon(const QString &file, const QString &name) = 0;
};

View File

@@ -1,433 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QDir>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <quazip.h>
#include <quazipfile.h>
#include "Mod.h"
#include "settings/INIFile.h"
#include <FileSystem.h>
#include <QDebug>
Mod::Mod(const QFileInfo &file)
{
repath(file);
m_changedDateTime = file.lastModified();
}
void Mod::repath(const QFileInfo &file)
{
m_file = file;
QString name_base = file.fileName();
m_type = Mod::MOD_UNKNOWN;
if (m_file.isDir())
{
m_type = MOD_FOLDER;
m_name = name_base;
m_mmc_id = name_base;
}
else if (m_file.isFile())
{
if (name_base.endsWith(".disabled"))
{
m_enabled = false;
name_base.chop(9);
}
else
{
m_enabled = true;
}
m_mmc_id = name_base;
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
{
m_type = MOD_ZIPFILE;
name_base.chop(4);
}
else if (name_base.endsWith(".litemod"))
{
m_type = MOD_LITEMOD;
name_base.chop(8);
}
else
{
m_type = MOD_SINGLEFILE;
}
m_name = name_base;
}
if (m_type == MOD_ZIPFILE)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
if (zip.setCurrentFile("mcmod.info"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadMCModInfo(file.readAll());
file.close();
zip.close();
return;
}
else if (zip.setCurrentFile("fabric.mod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadFabricModInfo(file.readAll());
file.close();
zip.close();
return;
}
else if (zip.setCurrentFile("forgeversion.properties"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadForgeInfo(file.readAll());
file.close();
zip.close();
return;
}
zip.close();
}
else if (m_type == MOD_FOLDER)
{
QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
if (mcmod_info.isFile())
{
QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly))
return;
auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull())
return;
ReadMCModInfo(data);
}
}
else if (m_type == MOD_LITEMOD)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadLiteModInfo(file.readAll());
file.close();
}
zip.close();
}
}
// NEW format
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
// OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
void Mod::ReadMCModInfo(QByteArray contents)
{
auto getInfoFromArray = [&](QJsonArray arr)->void
{
if (!arr.at(0).isObject())
return;
auto firstObj = arr.at(0).toObject();
m_mod_id = firstObj.value("modid").toString();
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
m_updateurl = firstObj.value("updateUrl").toString();
m_homeurl = m_homeurl.trimmed();
if(!m_homeurl.isEmpty())
{
// fix up url.
if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
!m_homeurl.startsWith("ftp://"))
{
m_homeurl.prepend("http://");
}
}
m_description = firstObj.value("description").toString();
QJsonArray authors = firstObj.value("authorList").toArray();
if (authors.size() == 0)
authors = firstObj.value("authors").toArray();
if (authors.size() == 0)
m_authors = "";
else if (authors.size() >= 1)
{
m_authors = authors.at(0).toString();
for (int i = 1; i < authors.size(); i++)
{
m_authors += ", " + authors.at(i).toString();
}
}
m_credits = firstObj.value("credits").toString();
return;
}
;
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array
if (jsonDoc.isArray())
{
getInfoFromArray(jsonDoc.array());
}
else if (jsonDoc.isObject())
{
auto val = jsonDoc.object().value("modinfoversion");
if(val.isUndefined())
val = jsonDoc.object().value("modListVersion");
int version = val.toDouble();
if (version != 2)
{
qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents;
return;
}
auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined())
arrVal = jsonDoc.object().value("modList");
if (arrVal.isArray())
{
getInfoFromArray(arrVal.toArray());
}
}
}
// https://fabricmc.net/wiki/documentation:fabric_mod_json
void Mod::ReadFabricModInfo(QByteArray contents)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
m_mod_id = object.value("id").toString();
m_version = object.value("version").toString();
m_name = object.contains("name") ? object.value("name").toString() : m_mod_id;
m_description = object.value("description").toString();
if (schemaVersion >= 1)
{
QJsonArray authors = object.value("authors").toArray();
m_authors = "";
for (int i = 0; i < authors.size(); i++)
{
QString author_name = authors.at(i).isObject()
? authors.at(i).toObject().value("name").toString()
: authors.at(i).toString();
if (i > 0)
m_authors += ", " + author_name;
else {
m_authors += author_name;
}
}
if (object.contains("contact"))
{
QJsonObject contact = object.value("contact").toObject();
if (contact.contains("homepage"))
m_homeurl = contact.value("homepage").toString();
}
}
}
void Mod::ReadForgeInfo(QByteArray contents)
{
// Read the data
m_name = "Minecraft Forge";
m_mod_id = "Forge";
m_homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(contents))
return;
QString major = ini.get("forge.major.number", "0").toString();
QString minor = ini.get("forge.minor.number", "0").toString();
QString revision = ini.get("forge.revision.number", "0").toString();
QString build = ini.get("forge.build.number", "0").toString();
m_version = major + "." + minor + "." + revision + "." + build;
}
void Mod::ReadLiteModInfo(QByteArray contents)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
if (object.contains("name"))
{
m_mod_id = m_name = object.value("name").toString();
}
if (object.contains("version"))
{
m_version = object.value("version").toString("");
}
else
{
m_version = object.value("revision").toString("");
}
m_mcversion = object.value("mcversion").toString();
m_authors = object.value("author").toString();
m_description = object.value("description").toString();
m_homeurl = object.value("url").toString();
}
bool Mod::replace(Mod &with)
{
if (!destroy())
return false;
bool success = false;
auto t = with.type();
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
{
qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
}
if (t == MOD_FOLDER)
{
success = FS::copy(with.m_file.filePath(), m_file.path())();
}
if (success)
{
m_name = with.m_name;
m_mmc_id = with.m_mmc_id;
m_mod_id = with.m_mod_id;
m_version = with.m_version;
m_mcversion = with.m_mcversion;
m_description = with.m_description;
m_authors = with.m_authors;
m_credits = with.m_credits;
m_homeurl = with.m_homeurl;
m_type = with.m_type;
m_file.refresh();
}
return success;
}
bool Mod::destroy()
{
if (m_type == MOD_FOLDER)
{
QDir d(m_file.filePath());
if (d.removeRecursively())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
{
QFile f(m_file.filePath());
if (f.remove())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
return true;
}
QString Mod::version() const
{
switch (type())
{
case MOD_ZIPFILE:
case MOD_LITEMOD:
return m_version;
case MOD_FOLDER:
return "Folder";
case MOD_SINGLEFILE:
return "File";
default:
return "VOID";
}
}
bool Mod::enable(bool value)
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
if (m_enabled == value)
return false;
QString path = m_file.absoluteFilePath();
if (value)
{
QFile foo(path);
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!foo.rename(path))
return false;
}
else
{
QFile foo(path);
path += ".disabled";
if (!foo.rename(path))
return false;
}
m_file = QFileInfo(path);
m_enabled = value;
return true;
}
bool Mod::operator==(const Mod &other) const
{
return mmc_id() == other.mmc_id();
}
bool Mod::strongCompare(const Mod &other) const
{
return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
}

View File

@@ -1,315 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MojangAccount.h"
#include "flows/RefreshTask.h"
#include "flows/AuthenticateTask.h"
#include <QUuid>
#include <QJsonObject>
#include <QJsonArray>
#include <QRegExp>
#include <QStringList>
#include <QJsonDocument>
#include <QDebug>
MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
{
// The JSON object must at least have a username for it to be valid.
if (!object.value("username").isString())
{
qCritical() << "Can't load Mojang account info from JSON object. Username field is "
"missing or of the wrong type.";
return nullptr;
}
QString username = object.value("username").toString("");
QString clientToken = object.value("clientToken").toString("");
QString accessToken = object.value("accessToken").toString("");
QJsonArray profileArray = object.value("profiles").toArray();
if (profileArray.size() < 1)
{
qCritical() << "Can't load Mojang account with username \"" << username
<< "\". No profiles found.";
return nullptr;
}
QList<AccountProfile> profiles;
for (QJsonValue profileVal : profileArray)
{
QJsonObject profileObject = profileVal.toObject();
QString id = profileObject.value("id").toString("");
QString name = profileObject.value("name").toString("");
bool legacy = profileObject.value("legacy").toBool(false);
if (id.isEmpty() || name.isEmpty())
{
qWarning() << "Unable to load a profile because it was missing an ID or a name.";
continue;
}
profiles.append({id, name, legacy});
}
MojangAccountPtr account(new MojangAccount());
if (object.value("user").isObject())
{
User u;
QJsonObject userStructure = object.value("user").toObject();
u.id = userStructure.value("id").toString();
/*
QJsonObject propMap = userStructure.value("properties").toObject();
for(auto key: propMap.keys())
{
auto values = propMap.operator[](key).toArray();
for(auto value: values)
u.properties.insert(key, value.toString());
}
*/
account->m_user = u;
}
account->m_username = username;
account->m_clientToken = clientToken;
account->m_accessToken = accessToken;
account->m_profiles = profiles;
// Get the currently selected profile.
QString currentProfile = object.value("activeProfile").toString("");
if (!currentProfile.isEmpty())
account->setCurrentProfile(currentProfile);
return account;
}
MojangAccountPtr MojangAccount::createFromUsername(const QString &username)
{
MojangAccountPtr account(new MojangAccount());
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
account->m_username = username;
return account;
}
QJsonObject MojangAccount::saveToJson() const
{
QJsonObject json;
json.insert("username", m_username);
json.insert("clientToken", m_clientToken);
json.insert("accessToken", m_accessToken);
QJsonArray profileArray;
for (AccountProfile profile : m_profiles)
{
QJsonObject profileObj;
profileObj.insert("id", profile.id);
profileObj.insert("name", profile.name);
profileObj.insert("legacy", profile.legacy);
profileArray.append(profileObj);
}
json.insert("profiles", profileArray);
QJsonObject userStructure;
{
userStructure.insert("id", m_user.id);
/*
QJsonObject userAttrs;
for(auto key: m_user.properties.keys())
{
auto array = QJsonArray::fromStringList(m_user.properties.values(key));
userAttrs.insert(key, array);
}
userStructure.insert("properties", userAttrs);
*/
}
json.insert("user", userStructure);
if (m_currentProfile != -1)
json.insert("activeProfile", currentProfile()->id);
return json;
}
bool MojangAccount::setCurrentProfile(const QString &profileId)
{
for (int i = 0; i < m_profiles.length(); i++)
{
if (m_profiles[i].id == profileId)
{
m_currentProfile = i;
return true;
}
}
return false;
}
const AccountProfile *MojangAccount::currentProfile() const
{
if (m_currentProfile == -1)
return nullptr;
return &m_profiles[m_currentProfile];
}
AccountStatus MojangAccount::accountStatus() const
{
if (m_accessToken.isEmpty())
return NotVerified;
else
return Verified;
}
std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password)
{
Q_ASSERT(m_currentTask.get() == nullptr);
// take care of the true offline status
if (accountStatus() == NotVerified && password.isEmpty())
{
if (session)
{
session->status = AuthSession::RequiresPassword;
fillSession(session);
}
return nullptr;
}
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
}
else
{
if (password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this));
}
else
{
m_currentTask.reset(new AuthenticateTask(this, password));
}
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
}
return m_currentTask;
}
void MojangAccount::authSucceeded()
{
auto session = m_currentTask->getAssignedSession();
if (session)
{
session->status =
session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline;
fillSession(session);
session->auth_server_online = true;
}
m_currentTask.reset();
emit changed();
}
void MojangAccount::authFailed(QString reason)
{
auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT)
{
if (session)
{
session->status = accountStatus() == Verified ? AuthSession::PlayableOffline
: AuthSession::RequiresPassword;
session->auth_server_online = false;
fillSession(session);
}
}
else
{
m_accessToken = QString();
emit changed();
if (session)
{
session->status = AuthSession::RequiresPassword;
session->auth_server_online = true;
fillSession(session);
}
}
m_currentTask.reset();
}
void MojangAccount::fillSession(AuthSessionPtr session)
{
// the user name. you have to have an user name
session->username = m_username;
// volatile auth token
session->access_token = m_accessToken;
// the semi-permanent client token
session->client_token = m_clientToken;
if (currentProfile())
{
// profile name
session->player_name = currentProfile()->name;
// profile ID
session->uuid = currentProfile()->id;
// 'legacy' or 'mojang', depending on account type
session->user_type = currentProfile()->legacy ? "legacy" : "mojang";
if (!session->access_token.isEmpty())
{
session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
}
else
{
session->session = "-";
}
}
else
{
session->player_name = "Player";
session->session = "-";
}
session->u = user();
session->m_accountPtr = shared_from_this();
}
void MojangAccount::decrementUses()
{
Usable::decrementUses();
if(!isInUse())
{
emit changed();
qWarning() << "Account" << m_username << "is no longer in use.";
}
}
void MojangAccount::incrementUses()
{
bool wasInUse = isInUse();
Usable::incrementUses();
if(!wasInUse)
{
emit changed();
qWarning() << "Account" << m_username << "is now in use.";
}
}
void MojangAccount::invalidateClientToken()
{
m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
emit changed();
}

View File

@@ -1,182 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <QJsonObject>
#include <QPair>
#include <QMap>
#include <memory>
#include "AuthSession.h"
#include "Usable.h"
#include "multimc_logic_export.h"
class Task;
class YggdrasilTask;
class MojangAccount;
typedef std::shared_ptr<MojangAccount> MojangAccountPtr;
Q_DECLARE_METATYPE(MojangAccountPtr)
/**
* A profile within someone's Mojang account.
*
* Currently, the profile system has not been implemented by Mojang yet,
* but we might as well add some things for it in MultiMC right now so
* we don't have to rip the code to pieces to add it later.
*/
struct AccountProfile
{
QString id;
QString name;
bool legacy;
};
enum AccountStatus
{
NotVerified,
Verified
};
/**
* Object that stores information about a certain Mojang account.
*
* Said information may include things such as that account's username, client token, and access
* token if the user chose to stay logged in.
*/
class MULTIMC_LOGIC_EXPORT MojangAccount :
public QObject,
public Usable,
public std::enable_shared_from_this<MojangAccount>
{
Q_OBJECT
public: /* construction */
//! Do not copy accounts. ever.
explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;
//! Default constructor
explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};
//! Creates an empty account for the specified user name.
static MojangAccountPtr createFromUsername(const QString &username);
//! Loads a MojangAccount from the given JSON object.
static MojangAccountPtr loadFromJson(const QJsonObject &json);
//! Saves a MojangAccount to a JSON object and returns it.
QJsonObject saveToJson() const;
public: /* manipulation */
/**
* Sets the currently selected profile to the profile with the given ID string.
* If profileId is not in the list of available profiles, the function will simply return
* false.
*/
bool setCurrentProfile(const QString &profileId);
/**
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/
std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString());
void invalidateClientToken();
public: /* queries */
const QString &username() const
{
return m_username;
}
const QString &clientToken() const
{
return m_clientToken;
}
const QString &accessToken() const
{
return m_accessToken;
}
const QList<AccountProfile> &profiles() const
{
return m_profiles;
}
const User &user()
{
return m_user;
}
//! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const;
//! Returns whether the account is NotVerified, Verified or Online
AccountStatus accountStatus() const;
signals:
/**
* This signal is emitted when the account changes
*/
void changed();
// TODO: better signalling for the various possible state changes - especially errors
protected: /* variables */
QString m_username;
// Used to identify the client - the user can have multiple clients for the same account
// Think: different launchers, all connecting to the same account/profile
QString m_clientToken;
// Blank if not logged in.
QString m_accessToken;
// Index of the selected profile within the list of available
// profiles. -1 if nothing is selected.
int m_currentProfile = -1;
// List of available profiles.
QList<AccountProfile> m_profiles;
// the user structure, whatever it is.
User m_user;
// current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask;
protected: /* methods */
void incrementUses() override;
void decrementUses() override;
private
slots:
void authSucceeded();
void authFailed(QString reason);
private:
void fillSession(AuthSessionPtr session);
public:
friend class YggdrasilTask;
friend class AuthenticateTask;
friend class ValidateTask;
friend class RefreshTask;
};

View File

@@ -1,468 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MojangAccountList.h"
#include "MojangAccount.h"
#include <QIODevice>
#include <QFile>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonParseError>
#include <QDir>
#include <QDebug>
#include <FileSystem.h>
#define ACCOUNT_LIST_FORMAT_VERSION 2
MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent)
{
}
MojangAccountPtr MojangAccountList::findAccount(const QString &username) const
{
for (int i = 0; i < count(); i++)
{
MojangAccountPtr account = at(i);
if (account->username() == username)
return account;
}
return nullptr;
}
const MojangAccountPtr MojangAccountList::at(int i) const
{
return MojangAccountPtr(m_accounts.at(i));
}
void MojangAccountList::addAccount(const MojangAccountPtr account)
{
int row = m_accounts.count();
beginInsertRows(QModelIndex(), row, row);
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
endInsertRows();
onListChanged();
}
void MojangAccountList::removeAccount(const QString &username)
{
int idx = 0;
for (auto account : m_accounts)
{
if (account->username() == username)
{
beginRemoveRows(QModelIndex(), idx, idx);
m_accounts.removeOne(account);
endRemoveRows();
return;
}
idx++;
}
onListChanged();
}
void MojangAccountList::removeAccount(QModelIndex index)
{
int row = index.row();
if(index.isValid() && row >= 0 && row < m_accounts.size())
{
auto & account = m_accounts[row];
if(account == m_activeAccount)
{
m_activeAccount = nullptr;
onActiveChanged();
}
beginRemoveRows(QModelIndex(), row, row);
m_accounts.removeAt(index.row());
endRemoveRows();
onListChanged();
}
}
MojangAccountPtr MojangAccountList::activeAccount() const
{
return m_activeAccount;
}
void MojangAccountList::setActiveAccount(const QString &username)
{
if (username.isEmpty() && m_activeAccount)
{
int idx = 0;
auto prevActiveAcc = m_activeAccount;
m_activeAccount = nullptr;
for (MojangAccountPtr account : m_accounts)
{
if (account == prevActiveAcc)
{
emit dataChanged(index(idx), index(idx));
}
idx ++;
}
onActiveChanged();
}
else
{
auto currentActiveAccount = m_activeAccount;
int currentActiveAccountIdx = -1;
auto newActiveAccount = m_activeAccount;
int newActiveAccountIdx = -1;
int idx = 0;
for (MojangAccountPtr account : m_accounts)
{
if (account->username() == username)
{
newActiveAccount = account;
newActiveAccountIdx = idx;
}
if(currentActiveAccount == account)
{
currentActiveAccountIdx = idx;
}
idx++;
}
if(currentActiveAccount != newActiveAccount)
{
emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx));
emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx));
m_activeAccount = newActiveAccount;
onActiveChanged();
}
}
}
void MojangAccountList::accountChanged()
{
// the list changed. there is no doubt.
onListChanged();
}
void MojangAccountList::onListChanged()
{
if (m_autosave)
// TODO: Alert the user if this fails.
saveList();
emit listChanged();
}
void MojangAccountList::onActiveChanged()
{
if (m_autosave)
saveList();
emit activeAccountChanged();
}
int MojangAccountList::count() const
{
return m_accounts.count();
}
QVariant MojangAccountList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
MojangAccountPtr account = at(index.row());
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return account->username();
default:
return QVariant();
}
case Qt::ToolTipRole:
return account->username();
case PointerRole:
return qVariantFromValue(account);
case Qt::CheckStateRole:
switch (index.column())
{
case ActiveColumn:
return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
}
default:
return QVariant();
}
}
QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return tr("Active?");
case NameColumn:
return tr("Name");
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return tr("The name of the version.");
default:
return QVariant();
}
default:
return QVariant();
}
}
int MojangAccountList::rowCount(const QModelIndex &) const
{
// Return count
return count();
}
int MojangAccountList::columnCount(const QModelIndex &) const
{
return 2;
}
Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return Qt::NoItemFlags;
}
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
{
MojangAccountPtr account = this->at(index.row());
this->setActiveAccount(account->username());
}
}
emit dataChanged(index, index);
return true;
}
void MojangAccountList::updateListData(QList<MojangAccountPtr> versions)
{
beginResetModel();
m_accounts = versions;
endResetModel();
}
bool MojangAccountList::loadList(const QString &filePath)
{
QString path = filePath;
if (path.isEmpty())
path = m_listFilePath;
if (path.isEmpty())
{
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
return false;
}
QFile file(path);
// Try to open the file and fail if we can't.
// TODO: We should probably report this error to the user.
if (!file.open(QIODevice::ReadOnly))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
// Fail if the JSON is invalid.
if (parseError.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse account list file: %1 at offset %2")
.arg(parseError.errorString(), QString::number(parseError.offset))
.toUtf8();
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
qCritical() << "Invalid account list JSON: Root should be an array.";
return false;
}
QJsonObject root = jsonDoc.object();
// Make sure the format version matches.
if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION)
{
QString newName = "accounts-old.json";
qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to"
<< newName;
// Attempt to rename the old version.
file.rename(newName);
return false;
}
// Now, load the accounts array.
beginResetModel();
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{
QJsonObject accountObj = accountVal.toObject();
MojangAccountPtr account = MojangAccount::loadFromJson(accountObj);
if (account.get() != nullptr)
{
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
}
else
{
qWarning() << "Failed to load an account.";
}
}
// Load the active account.
m_activeAccount = findAccount(root.value("activeAccount").toString(""));
endResetModel();
return true;
}
bool MojangAccountList::saveList(const QString &filePath)
{
QString path(filePath);
if (path.isEmpty())
path = m_listFilePath;
if (path.isEmpty())
{
qCritical() << "Can't save Mojang account list. No file path given and no default set.";
return false;
}
// make sure the parent folder exists
if(!FS::ensureFilePathExists(path))
return false;
// make sure the file wasn't overwritten with a folder before (fixes a bug)
QFileInfo finfo(path);
if(finfo.isDir())
{
QDir badDir(path);
badDir.removeRecursively();
}
qDebug() << "Writing account list to" << path;
qDebug() << "Building JSON data structure.";
// Build the JSON document to write to the list file.
QJsonObject root;
root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION);
// Build a list of accounts.
qDebug() << "Building account array.";
QJsonArray accounts;
for (MojangAccountPtr account : m_accounts)
{
QJsonObject accountObj = account->saveToJson();
accounts.append(accountObj);
}
// Insert the account list into the root object.
root.insert("accounts", accounts);
if(m_activeAccount)
{
// Save the active account.
root.insert("activeAccount", m_activeAccount->username());
}
// Create a JSON document object to convert our JSON to bytes.
QJsonDocument doc(root);
// Now that we're done building the JSON object, we can write it to the file.
qDebug() << "Writing account list to file.";
QFile file(path);
// Try to open the file and fail if we can't.
// TODO: We should probably report this error to the user.
if (!file.open(QIODevice::WriteOnly))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// Write the JSON to the file.
file.write(doc.toJson());
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
file.close();
qDebug() << "Saved account list to" << path;
return true;
}
void MojangAccountList::setListFilePath(QString path, bool autosave)
{
m_listFilePath = path;
m_autosave = autosave;
}
bool MojangAccountList::anyAccountIsValid()
{
for(auto account:m_accounts)
{
if(account->accountStatus() != NotVerified)
return true;
}
return false;
}

View File

@@ -1,201 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "MojangAccount.h"
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include <QSharedPointer>
#include "multimc_logic_export.h"
/*!
* \brief List of available Mojang accounts.
* This should be loaded in the background by MultiMC on startup.
*
* This class also inherits from QAbstractListModel. Methods from that
* class determine how this list shows up in a list view. Said methods
* all have a default implementation, but they can be overridden by subclasses to
* change the behavior of the list.
*/
class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
PointerRole = 0x34B1CB48
};
enum VListColumns
{
// TODO: Add icon column.
// First column - Active?
ActiveColumn = 0,
// Second column - Name
NameColumn,
};
explicit MojangAccountList(QObject *parent = 0);
//! Gets the account at the given index.
virtual const MojangAccountPtr at(int i) const;
//! Returns the number of accounts in the list.
virtual int count() const;
//////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
/*!
* Adds a the given Mojang account to the account list.
*/
virtual void addAccount(const MojangAccountPtr account);
/*!
* Removes the mojang account with the given username from the account list.
*/
virtual void removeAccount(const QString &username);
/*!
* Removes the account at the given QModelIndex.
*/
virtual void removeAccount(QModelIndex index);
/*!
* \brief Finds an account by its username.
* \param The username of the account to find.
* \return A const pointer to the account with the given username. NULL if
* one doesn't exist.
*/
virtual MojangAccountPtr findAccount(const QString &username) const;
/*!
* Sets the default path to save the list file to.
* If autosave is true, this list will automatically save to the given path whenever it changes.
* THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately
* after calling this function to ensure an autosaved change doesn't overwrite the list you intended
* to load.
*/
virtual void setListFilePath(QString path, bool autosave = false);
/*!
* \brief Loads the account list from the given file path.
* If the given file is an empty string (default), will load from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool loadList(const QString &file = "");
/*!
* \brief Saves the account list to the given file.
* If the given file is an empty string (default), will save from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool saveList(const QString &file = "");
/*!
* \brief Gets a pointer to the account that the user has selected as their "active" account.
* Which account is active can be overridden on a per-instance basis, but this will return the one that
* is set as active globally.
* \return The currently active MojangAccount. If there isn't an active account, returns a null pointer.
*/
virtual MojangAccountPtr activeAccount() const;
/*!
* Sets the given account as the current active account.
* If the username given is an empty string, sets the active account to nothing.
*/
virtual void setActiveAccount(const QString &username);
/*!
* Returns true if any of the account is at least Validated
*/
bool anyAccountIsValid();
signals:
/*!
* Signal emitted to indicate that the account list has changed.
* This will also fire if the value of an element in the list changes (will be implemented
* later).
*/
void listChanged();
/*!
* Signal emitted to indicate that the active account has changed.
*/
void activeAccountChanged();
public
slots:
/**
* This is called when one of the accounts changes and the list needs to be updated
*/
void accountChanged();
protected:
/*!
* Called whenever the list changes.
* This emits the listChanged() signal and autosaves the list (if autosave is enabled).
*/
void onListChanged();
/*!
* Called whenever the active account changes.
* Emits the activeAccountChanged() signal and autosaves the list if enabled.
*/
void onActiveChanged();
QList<MojangAccountPtr> m_accounts;
/*!
* Account that is currently active.
*/
MojangAccountPtr m_activeAccount;
//! Path to the account list file. Empty string if there isn't one.
QString m_listFilePath;
/*!
* If true, the account list will automatically save to the account list path when it changes.
* Ignored if m_listFilePath is blank.
*/
bool m_autosave = false;
protected
slots:
/*!
* Updates this list with the given list of accounts.
* This is done by copying each account in the given list and inserting it
* into this one.
* We need to do this so that we can set the parents of the accounts are set to this
* account list. This can't be done in the load task, because the accounts the load
* task creates are on the load task's thread and Qt won't allow their parents
* to be set to something created on another thread.
* To get around that problem, we invoke this method on the GUI thread, which
* then copies the accounts and sets their parents correctly.
* \param accounts List of accounts whose parents should be set.
*/
virtual void updateListData(QList<MojangAccountPtr> versions);
};

View File

@@ -1,255 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "YggdrasilTask.h"
#include "MojangAccount.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QByteArray>
#include <Env.h>
#include <net/URLConstants.h>
#include <QDebug>
YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
: Task(parent), m_account(account)
{
changeState(STATE_CREATED);
}
void YggdrasilTask::executeTask()
{
changeState(STATE_SENDING_REQUEST);
// Get the content of the request we're going to send to the server.
QJsonDocument doc(getRequestContent());
QUrl reqUrl(URLConstants::AUTH_BASE + getEndpoint());
QNetworkRequest netRequest(reqUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray requestData = doc.toJson();
m_netReply = ENV.qnam().post(netRequest, requestData);
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
timeout_keeper.setSingleShot(true);
timeout_keeper.start(timeout_max);
counter.setSingleShot(false);
counter.start(time_step);
progress(0, timeout_max);
connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout);
connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
}
void YggdrasilTask::refreshTimers(qint64, qint64)
{
timeout_keeper.stop();
timeout_keeper.start(timeout_max);
progress(count = 0, timeout_max);
}
void YggdrasilTask::heartbeat()
{
count += time_step;
progress(count, timeout_max);
}
bool YggdrasilTask::abort()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_USER;
m_netReply->abort();
return true;
}
void YggdrasilTask::abortByTimeout()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_TIMEOUT;
m_netReply->abort();
}
void YggdrasilTask::sslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors)
{
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
void YggdrasilTask::processReply()
{
changeState(STATE_PROCESSING_RESPONSE);
switch (m_netReply->error())
{
case QNetworkReply::NoError:
break;
case QNetworkReply::TimeoutError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(
STATE_FAILED_SOFT,
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
"href=\"https://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
"your root certificates</a></li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
"<li>Possibly something else. Check the MultiMC log file for details</li>"
"</ul>"));
return;
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
default:
changeState(STATE_FAILED_SOFT,
tr("Authentication operation failed due to a network error: %1 (%2)")
.arg(m_netReply->errorString()).arg(m_netReply->error()));
return;
}
// Try to parse the response regardless of the response code.
// Sometimes the auth server will give more information and an error code.
QJsonParseError jsonError;
QByteArray replyData = m_netReply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
// Check the response code.
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (responseCode == 200)
{
// If the response code was 200, then there shouldn't be an error. Make sure
// anyways.
// Also, sometimes an empty reply indicates success. If there was no data received,
// pass an empty json object to the processResponse function.
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
else
{
changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response "
"JSON response: %1 at offset %2.")
.arg(jsonError.errorString())
.arg(jsonError.offset));
qCritical() << replyData;
}
return;
}
// If the response code was not 200, then Yggdrasil may have given us information
// about the error.
// If we can parse the response, then get information from it. Otherwise just say
// there was an unknown error.
if (jsonError.error == QJsonParseError::NoError)
{
// We were able to parse the server's response. Woo!
// Call processError. If a subclass has overridden it then they'll handle their
// stuff there.
qDebug() << "The request failed, but the server gave us an error message. "
"Processing error.";
processError(doc.object());
}
else
{
// The server didn't say anything regarding the error. Give the user an unknown
// error.
qDebug()
<< "The request failed and the server gave no error message. Unknown error.";
changeState(STATE_FAILED_SOFT,
tr("An unknown error occurred when trying to communicate with the "
"authentication server: %1").arg(m_netReply->errorString()));
}
}
void YggdrasilTask::processError(QJsonObject responseData)
{
QJsonValue errorVal = responseData.value("error");
QJsonValue errorMessageValue = responseData.value("errorMessage");
QJsonValue causeVal = responseData.value("cause");
if (errorVal.isString() && errorMessageValue.isString())
{
m_error = std::shared_ptr<Error>(new Error{
errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
else
{
// Error is not in standard format. Don't set m_error and return unknown error.
changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}
}
QString YggdrasilTask::getStateMessage() const
{
switch (m_state)
{
case STATE_CREATED:
return "Waiting...";
case STATE_SENDING_REQUEST:
return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE:
return tr("Processing response from servers...");
case STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case STATE_FAILED_SOFT:
return tr("Failed to contact the authentication server.");
case STATE_FAILED_HARD:
return tr("Failed to authenticate.");
default:
return tr("...");
}
}
void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason)
{
m_state = newState;
setStatus(getStateMessage());
if (newState == STATE_SUCCEEDED)
{
emitSucceeded();
}
else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
{
emitFailed(reason);
}
}
YggdrasilTask::State YggdrasilTask::state()
{
return m_state;
}

View File

@@ -1,202 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AuthenticateTask.h"
#include "../MojangAccount.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
#include <QDebug>
#include <QUuid>
AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password,
QObject *parent)
: YggdrasilTask(account, parent), m_password(password)
{
}
QJsonObject AuthenticateTask::getRequestContent() const
{
/*
* {
* "agent": { // optional
* "name": "Minecraft", // So far this is the only encountered value
* "version": 1 // This number might be increased
* // by the vanilla client in the future
* },
* "username": "mojang account name", // Can be an email address or player name for
// unmigrated accounts
* "password": "mojang account password",
* "clientToken": "client identifier" // optional
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
{
QJsonObject agent;
// C++ makes string literals void* for some stupid reason, so we have to tell it
// QString... Thanks Obama.
agent.insert("name", QString("Minecraft"));
agent.insert("version", 1);
req.insert("agent", agent);
}
req.insert("username", m_account->username());
req.insert("password", m_password);
req.insert("requestUser", true);
// If we already have a client token, give it to the server.
// Otherwise, let the server give us one.
if(m_account->m_clientToken.isEmpty())
{
auto uuid = QUuid::createUuid();
auto uuidString = uuid.toString().remove('{').remove('-').remove('}');
m_account->m_clientToken = uuidString;
}
req.insert("clientToken", m_account->m_clientToken);
return req;
}
void AuthenticateTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
qDebug() << "Getting client token.";
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Set the client token.
m_account->m_clientToken = clientToken;
// Now, we set the access token.
qDebug() << "Getting access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// Set the access token.
m_account->m_accessToken = accessToken;
// Now we load the list of available profiles.
// Mojang hasn't yet implemented the profile system,
// but we might as well support what's there so we
// don't have trouble implementing it later.
qDebug() << "Loading profile list.";
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
QList<AccountProfile> loadedProfiles;
for (auto iter : availableProfiles)
{
QJsonObject profile = iter.toObject();
// Profiles are easy, we just need their ID and name.
QString id = profile.value("id").toString("");
QString name = profile.value("name").toString("");
bool legacy = profile.value("legacy").toBool(false);
if (id.isEmpty() || name.isEmpty())
{
// This should never happen, but we might as well
// warn about it if it does so we can debug it easily.
// You never know when Mojang might do something truly derpy.
qWarning() << "Found entry in available profiles list with missing ID or name "
"field. Ignoring it.";
}
// Now, add a new AccountProfile entry to the list.
loadedProfiles.append({id, name, legacy});
}
// Put the list of profiles we loaded into the MojangAccount object.
m_account->m_profiles = loadedProfiles;
// Finally, we set the current profile to the correct value. This is pretty simple.
// We do need to make sure that the current profile that the server gave us
// is actually in the available profiles list.
// If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
qDebug() << "Setting current profile.";
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (currentProfileId.isEmpty())
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium."));
return;
}
if (!m_account->setCurrentProfile(currentProfileId))
{
changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading authentication response.";
changeState(STATE_SUCCEEDED);
}
QString AuthenticateTask::getEndpoint() const
{
return "authenticate";
}
QString AuthenticateTask::getStateMessage() const
{
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE:
return tr("Authenticating: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -1,46 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "../YggdrasilTask.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
/**
* The authenticate task takes a MojangAccount with no access token and password and attempts to
* authenticate with Mojang's servers.
* If successful, it will set the MojangAccount's access token.
*/
class AuthenticateTask : public YggdrasilTask
{
Q_OBJECT
public:
AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
private:
QString m_password;
};

View File

@@ -1,144 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "RefreshTask.h"
#include "../MojangAccount.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
#include <QDebug>
RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account)
{
}
QJsonObject RefreshTask::getRequestContent() const
{
/*
* {
* "clientToken": "client identifier"
* "accessToken": "current access token to be refreshed"
* "selectedProfile": // specifying this causes errors
* {
* "id": "profile ID"
* "name": "profile name"
* }
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
req.insert("clientToken", m_account->m_clientToken);
req.insert("accessToken", m_account->m_accessToken);
/*
{
auto currentProfile = m_account->currentProfile();
QJsonObject profile;
profile.insert("id", currentProfile->id());
profile.insert("name", currentProfile->name());
req.insert("selectedProfile", profile);
}
*/
req.insert("requestUser", true);
return req;
}
void RefreshTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Now, we set the access token.
qDebug() << "Getting new access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// we validate that the server responded right. (our current profile = returned current
// profile)
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (m_account->currentProfile()->id != currentProfileId)
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading refresh response.";
// Reset the access token.
m_account->m_accessToken = accessToken;
changeState(STATE_SUCCEEDED);
}
QString RefreshTask::getEndpoint() const
{
return "refresh";
}
QString RefreshTask::getStateMessage() const
{
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE:
return tr("Refreshing login token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -1,44 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "../YggdrasilTask.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
/**
* The authenticate task takes a MojangAccount with a possibly timed-out access token
* and attempts to authenticate with Mojang's servers.
* If successful, it will set the new access token. The token is considered validated.
*/
class RefreshTask : public YggdrasilTask
{
Q_OBJECT
public:
RefreshTask(MojangAccount * account);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
};

View File

@@ -1,61 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ValidateTask.h"
#include "../MojangAccount.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
#include <QDebug>
ValidateTask::ValidateTask(MojangAccount * account, QObject *parent)
: YggdrasilTask(account, parent)
{
}
QJsonObject ValidateTask::getRequestContent() const
{
QJsonObject req;
req.insert("accessToken", m_account->m_accessToken);
return req;
}
void ValidateTask::processResponse(QJsonObject responseData)
{
// Assume that if processError wasn't called, then the request was successful.
changeState(YggdrasilTask::STATE_SUCCEEDED);
}
QString ValidateTask::getEndpoint() const
{
return "validate";
}
QString ValidateTask::getStateMessage() const
{
switch (m_state)
{
case YggdrasilTask::STATE_SENDING_REQUEST:
return tr("Validating access token: Sending request...");
case YggdrasilTask::STATE_PROCESSING_RESPONSE:
return tr("Validating access token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -1,47 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME:
*/
#pragma once
#include "../YggdrasilTask.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
/**
* The validate task takes a MojangAccount and checks to make sure its access token is valid.
*/
class ValidateTask : public YggdrasilTask
{
Q_OBJECT
public:
ValidateTask(MojangAccount *account, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
private:
};

View File

@@ -1,393 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Env.h"
#include "ForgeXzDownload.h"
#include <FileSystem.h>
#include <QCryptographicHash>
#include <QFileInfo>
#include <QDateTime>
#include <QDir>
#include <QDebug>
ForgeXzDownload::ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry) : NetAction()
{
m_entry = entry;
m_target_path = entry->getFullPath();
m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX");
m_status = Job_NotStarted;
m_url_path = relative_path;
m_url = url + ".pack.xz";
}
void ForgeXzDownload::start()
{
if(m_status == Job_Aborted)
{
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
emit aborted(m_index_within_job);
return;
}
m_status = Job_InProgress;
if (!m_entry->isStale())
{
m_status = Job_Finished;
emit succeeded(m_index_within_job);
return;
}
// can we actually create the real, final file?
if (!FS::ensureFilePathExists(m_target_path))
{
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
qDebug() << "Downloading " << m_url.toString();
QNetworkRequest request(m_url);
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
QNetworkReply *rep = ENV.qnam().get(request);
m_reply.reset(rep);
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
}
void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
m_total_progress = bytesTotal;
m_progress = bytesReceived;
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
}
void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error)
{
if(error == QNetworkReply::OperationCanceledError)
{
qCritical() << "Aborted " << m_url.toString();
m_status = Job_Aborted;
}
else
{
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
m_status = Job_Failed;
}
}
void ForgeXzDownload::failAndTryNextMirror()
{
m_status = Job_Failed;
emit failed(m_index_within_job);
}
void ForgeXzDownload::downloadFinished()
{
// if the download succeeded
if (m_status != Job_Failed && m_status != Job_Aborted)
{
// nothing went wrong...
m_status = Job_Finished;
if (m_pack200_xz_file.isOpen())
{
// we actually downloaded something! process and isntall it
decompressAndInstall();
return;
}
else
{
// something bad happened -- on the local machine!
m_status = Job_Failed;
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
return;
}
}
else if(m_status == Job_Aborted)
{
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
emit aborted(m_index_within_job);
return;
}
// else the download failed
else
{
m_status = Job_Failed;
m_pack200_xz_file.close();
m_pack200_xz_file.remove();
m_reply.reset();
failAndTryNextMirror();
return;
}
}
void ForgeXzDownload::downloadReadyRead()
{
if (!m_pack200_xz_file.isOpen())
{
if (!m_pack200_xz_file.open())
{
/*
* Can't open the file... the job failed
*/
m_reply->abort();
emit failed(m_index_within_job);
return;
}
}
m_pack200_xz_file.write(m_reply->readAll());
}
#include "xz.h"
#include "unpack200.h"
#include <stdexcept>
#include <unistd.h>
const size_t buffer_size = 8196;
// NOTE: once this gets here, it can't be aborted anymore. we don't care.
void ForgeXzDownload::decompressAndInstall()
{
// rewind the downloaded temp file
m_pack200_xz_file.seek(0);
// de-xz'd file
QTemporaryFile pack200_file("./dl_temp.XXXXXX");
pack200_file.open();
bool xz_success = false;
// first, de-xz
{
uint8_t in[buffer_size];
uint8_t out[buffer_size];
struct xz_buf b;
struct xz_dec *s;
enum xz_ret ret;
xz_crc32_init();
xz_crc64_init();
s = xz_dec_init(XZ_DYNALLOC, 1 << 26);
if (s == nullptr)
{
xz_dec_end(s);
failAndTryNextMirror();
return;
}
b.in = in;
b.in_pos = 0;
b.in_size = 0;
b.out = out;
b.out_pos = 0;
b.out_size = buffer_size;
while (!xz_success)
{
if (b.in_pos == b.in_size)
{
b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in));
b.in_pos = 0;
}
ret = xz_dec_run(s, &b);
if (b.out_pos == sizeof(out))
{
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// msg = "Write error\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
b.out_pos = 0;
}
if (ret == XZ_OK)
continue;
if (ret == XZ_UNSUPPORTED_CHECK)
{
// unsupported check. this is OK, but we should log this
continue;
}
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// write error
pack200_file.close();
xz_dec_end(s);
return;
}
switch (ret)
{
case XZ_STREAM_END:
xz_dec_end(s);
xz_success = true;
break;
case XZ_MEM_ERROR:
qCritical() << "Memory allocation failed\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_MEMLIMIT_ERROR:
qCritical() << "Memory usage limit reached\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_FORMAT_ERROR:
qCritical() << "Not a .xz file\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_OPTIONS_ERROR:
qCritical() << "Unsupported options in the .xz headers\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_DATA_ERROR:
case XZ_BUF_ERROR:
qCritical() << "File is corrupt\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
default:
qCritical() << "Bug!\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
}
}
m_pack200_xz_file.remove();
// revert pack200
pack200_file.seek(0);
int handle_in = pack200_file.handle();
// FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
if(handle_in == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
int handle_in_dup = dup (handle_in);
if(handle_in_dup == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
FILE *file_in = fdopen (handle_in_dup, "rb");
if(!file_in)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
QFile qfile_out(m_target_path);
if(!qfile_out.open(QIODevice::WriteOnly))
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out = qfile_out.handle();
if(handle_out == -1)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out_dup = dup (handle_out);
if(handle_out_dup == -1)
{
qCritical() << "Error reopening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
FILE *file_out = fdopen (handle_out_dup, "wb");
if(!file_out)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
try
{
// NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above.
unpack_200(file_in, file_out);
}
catch (const std::runtime_error &err)
{
m_status = Job_Failed;
qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
QFile f(m_target_path);
if (f.exists())
f.remove();
failAndTryNextMirror();
return;
}
pack200_file.remove();
QFile jar_file(m_target_path);
if (!jar_file.open(QIODevice::ReadOnly))
{
jar_file.remove();
failAndTryNextMirror();
return;
}
auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5);
m_entry->setMD5Sum(hash.toHex().constData());
jar_file.close();
QFileInfo output_file_info(m_target_path);
m_entry->setETag(m_reply->rawHeader("ETag").constData());
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
m_entry->setStale(false);
ENV.metacache()->updateEntry(m_entry);
m_reply.reset();
emit succeeded(m_index_within_job);
}
bool ForgeXzDownload::abort()
{
if(m_reply)
m_reply->abort();
m_status = Job_Aborted;
return true;
}
bool ForgeXzDownload::canAbort()
{
return true;
}

View File

@@ -1,61 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "net/NetAction.h"
#include "net/HttpMetaCache.h"
#include <QFile>
#include <QTemporaryFile>
typedef std::shared_ptr<class ForgeXzDownload> ForgeXzDownloadPtr;
class ForgeXzDownload : public NetAction
{
Q_OBJECT
public:
MetaEntryPtr m_entry;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
QTemporaryFile m_pack200_xz_file;
/// path relative to the mirror base
QString m_url_path;
public:
explicit ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry);
static ForgeXzDownloadPtr make(QString url, QString relative_path, MetaEntryPtr entry)
{
return ForgeXzDownloadPtr(new ForgeXzDownload(url, relative_path, entry));
}
virtual ~ForgeXzDownload(){};
bool canAbort() override;
protected
slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void downloadFinished() override;
void downloadReadyRead() override;
public
slots:
void start() override;
bool abort() override;
private:
void decompressAndInstall();
void failAndTryNextMirror();
};

View File

@@ -1,19 +0,0 @@
#include "CreateServerResourcePacksFolder.h"
#include "minecraft/MinecraftInstance.h"
#include "launch/LaunchTask.h"
#include "FileSystem.h"
CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* parent): LaunchStep(parent)
{
}
void CreateServerResourcePacksFolder::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs")))
{
emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error);
}
emitSucceeded();
}

View File

@@ -1,175 +0,0 @@
#include "UrlResolvingTask.h"
#include <QtXml>
#include <Json.h>
namespace {
const char * metabase = "https://cursemeta.dries007.net";
}
Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess)
: m_url(toProcess)
{
}
void Flame::UrlResolvingTask::executeTask()
{
resolveUrl();
}
void Flame::UrlResolvingTask::resolveUrl()
{
setStatus(tr("Resolving URL..."));
setProgress(0, 1);
QUrl actualUrl(m_url);
if(actualUrl.host() != "www.curseforge.com") {
emitFailed(tr("Not a Twitch URL."));
return;
}
m_dljob.reset(new NetJob("URL resolver"));
bool weAreDigging = false;
needle = QString();
if(m_url.startsWith("https://")) {
if(m_url.endsWith("?client=y")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y
m_url.chop(9);
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
}
if(m_url.endsWith("/download")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html...
weAreDigging = true;
needle = m_url;
needle.replace("https://", "twitch://");
needle.replace("/download", "/download-client/");
m_url.append("?client=y");
} else if (m_url.contains("/download/")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
m_url.replace("/download/", "/download-client/");
}
}
else if(m_url.startsWith("twitch://")) {
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url.replace(0, 9, "https://");
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
}
auto dl = Net::Download::makeByteArray(QUrl(m_url), &results);
m_dljob->addNetAction(dl);
if(weAreDigging) {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML);
} else {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP);
}
m_dljob->start();
}
void Flame::UrlResolvingTask::processHTML()
{
QString htmlDoc = QString::fromUtf8(results);
auto index = htmlDoc.indexOf(needle);
if(index < 0) {
emitFailed(tr("Couldn't find the needle in the haystack..."));
return;
}
auto indexStart = index;
int indexEnd = -1;
while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') {
index ++;
if(htmlDoc[index] == '"') {
indexEnd = index;
break;
}
}
if(indexEnd > 0) {
QString found = htmlDoc.mid(indexStart, indexEnd - indexStart);
qDebug() << "Found needle: " << found;
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url = found;
resolveUrl();
return;
}
emitFailed(tr("Couldn't find the end of the needle in the haystack..."));
return;
}
void Flame::UrlResolvingTask::processCCIP()
{
QDomDocument doc;
if (!doc.setContent(results)) {
qDebug() << results;
emitFailed(tr("Resolving failed."));
return;
}
auto packageNode = doc.namedItem("package");
if(!packageNode.isElement()) {
emitFailed(tr("Resolving failed: missing package root element."));
return;
}
auto projectNode = packageNode.namedItem("project");
if(!projectNode.isElement()) {
emitFailed(tr("Resolving failed: missing project element."));
return;
}
auto attribs = projectNode.attributes();
auto projectIdNode = attribs.namedItem("id");
if(!projectIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing id attribute."));
return;
}
auto fileIdNode = attribs.namedItem("file");
if(!fileIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing file attribute."));
return;
}
auto projectId = projectIdNode.nodeValue();
auto fileId = fileIdNode.nodeValue();
bool success = true;
m_result.projectId = projectId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resove projectId as a number."));
return;
}
m_result.fileId = fileId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resove fileId as a number."));
return;
}
qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId;
resolveIDs();
}
void Flame::UrlResolvingTask::resolveIDs()
{
setStatus(tr("Resolving mod IDs..."));
m_dljob.reset(new NetJob("Mod id resolver"));
auto projectIdStr = QString::number(m_result.projectId);
auto fileIdStr = QString::number(m_result.fileId);
QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results);
m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta);
m_dljob->start();
}
void Flame::UrlResolvingTask::processCursemeta()
{
try {
if(m_result.parseFromBytes(results)) {
emitSucceeded();
qDebug() << results;
return;
}
} catch (const JSONValidationError &e) {
qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
qCritical() << results;
}
emitFailed(tr("Failed to resolve the modpack file."));
}

View File

@@ -1,43 +0,0 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include "PackManifest.h"
#include "multimc_logic_export.h"
namespace Flame
{
class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task
{
Q_OBJECT
public:
explicit UrlResolvingTask(const QString &toProcess);
virtual ~UrlResolvingTask() {};
const Flame::File &getResults() const
{
return m_result;
}
protected:
virtual void executeTask() override;
protected slots:
void processCCIP();
void processHTML();
void processCursemeta();
private:
void resolveUrl();
void resolveIDs();
private: /* data */
QString m_url;
QString needle;
Flame::File m_result;
QByteArray results;
NetJobPtr m_dljob;
};
}

View File

@@ -1,40 +0,0 @@
#pragma once
#include "net/NetJob.h"
#include <QTemporaryDir>
#include <QByteArray>
#include <QObject>
#include "PackHelpers.h"
class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject {
Q_OBJECT
public:
FtbPackFetchTask() = default;
virtual ~FtbPackFetchTask() = default;
void fetch();
void fetchPrivate(const QStringList &toFetch);
private:
NetJobPtr jobPtr;
QByteArray publicModpacksXmlFileData;
QByteArray thirdPartyModpacksXmlFileData;
bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list);
FtbModpackList publicPacks;
FtbModpackList thirdPartyPacks;
protected slots:
void fileDownloadFinished();
void fileDownloadFailed(QString reason);
signals:
void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks);
void failed(QString reason);
void privateFileDownloadFinished(FtbModpack modpack);
void privateFileDownloadFailed(QString reason, QString packCode);
};

View File

@@ -1,16 +0,0 @@
#include "URLConstants.h"
namespace URLConstants {
QString getLegacyJarUrl(QString version)
{
return AWS_DOWNLOAD_VERSIONS + getJarPath(version);
}
QString getJarPath(QString version)
{
return version + "/" + version + ".jar";
}
}

View File

@@ -1,36 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
namespace URLConstants
{
const QString AWS_DOWNLOAD_VERSIONS("https://s3.amazonaws.com/Minecraft.Download/versions/");
const QString RESOURCE_BASE("https://resources.download.minecraft.net/");
const QString LIBRARY_BASE("https://libraries.minecraft.net/");
const QString SKINS_BASE("https://crafatar.com/skins/");
const QString AUTH_BASE("https://authserver.mojang.com/");
const QString MOJANG_STATUS_URL("https://status.mojang.com/check");
const QString IMGUR_BASE_URL("https://api.imgur.com/3/");
const QString FMLLIBS_OUR_BASE_URL("https://files.multimc.org/fmllibs/");
const QString FMLLIBS_FORGE_BASE_URL("https://files.minecraftforge.net/fmllibs/");
const QString TRANSLATIONS_BASE_URL("https://files.multimc.org/translations/");
const QString FTB_CDN_BASE_URL("https://ftb.forgecdn.net/FTB2/");
QString getJarPath(QString version);
QString getLegacyJarUrl(QString version);
}

View File

@@ -1,33 +0,0 @@
#pragma once
#include "net/NetAction.h"
#include "Screenshot.h"
#include "multimc_logic_export.h"
typedef std::shared_ptr<class ImgurUpload> ImgurUploadPtr;
class MULTIMC_LOGIC_EXPORT ImgurUpload : public NetAction
{
public:
explicit ImgurUpload(ScreenshotPtr shot);
static ImgurUploadPtr make(ScreenshotPtr shot)
{
return ImgurUploadPtr(new ImgurUpload(shot));
}
protected
slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
virtual void downloadError(QNetworkReply::NetworkError error);
virtual void downloadFinished();
virtual void downloadReadyRead()
{
}
public
slots:
virtual void start();
private:
ScreenshotPtr m_shot;
bool finished = true;
};

View File

@@ -1,148 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "StatusChecker.h"
#include <net/URLConstants.h>
#include <QByteArray>
#include <QDebug>
StatusChecker::StatusChecker()
{
}
void StatusChecker::timerEvent(QTimerEvent *e)
{
QObject::timerEvent(e);
reloadStatus();
}
void StatusChecker::reloadStatus()
{
if (isLoadingStatus())
{
// qDebug() << "Ignored request to reload status. Currently reloading already.";
return;
}
// qDebug() << "Reloading status.";
NetJob* job = new NetJob("Status JSON");
job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink));
QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed);
m_statusNetJob.reset(job);
emit statusLoading(true);
job->start();
}
void StatusChecker::statusDownloadFinished()
{
qDebug() << "Finished loading status JSON.";
m_statusEntries.clear();
m_statusNetJob.reset();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
fail("Error parsing status JSON:" + jsonError.errorString());
return;
}
if (!jsonDoc.isArray())
{
fail("Error parsing status JSON: JSON root is not an array");
return;
}
QJsonArray root = jsonDoc.array();
for(auto status = root.begin(); status != root.end(); ++status)
{
QVariantMap map = (*status).toObject().toVariantMap();
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
QString key = iter.key();
QVariant value = iter.value();
if(value.type() == QVariant::Type::String)
{
m_statusEntries.insert(key, value.toString());
//qDebug() << "Status JSON object: " << key << m_statusEntries[key];
}
else
{
fail("Malformed status JSON: expected status type to be a string.");
return;
}
}
}
succeed();
}
void StatusChecker::statusDownloadFailed(QString reason)
{
fail(tr("Failed to load status JSON:\n%1").arg(reason));
}
QMap<QString, QString> StatusChecker::getStatusEntries() const
{
return m_statusEntries;
}
bool StatusChecker::isLoadingStatus() const
{
return m_statusNetJob.get() != nullptr;
}
QString StatusChecker::getLastLoadErrorMsg() const
{
return m_lastLoadError;
}
void StatusChecker::succeed()
{
if(m_prevEntries != m_statusEntries)
{
emit statusChanged(m_statusEntries);
m_prevEntries = m_statusEntries;
}
m_lastLoadError = "";
qDebug() << "Status loading succeeded.";
m_statusNetJob.reset();
emit statusLoading(false);
}
void StatusChecker::fail(const QString& errorMsg)
{
if(m_prevEntries != m_statusEntries)
{
emit statusChanged(m_statusEntries);
m_prevEntries = m_statusEntries;
}
m_lastLoadError = errorMsg;
qDebug() << "Failed to load status:" << errorMsg;
m_statusNetJob.reset();
emit statusLoading(false);
}

View File

@@ -1,60 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <net/NetJob.h>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT StatusChecker : public QObject
{
Q_OBJECT
public: /* con/des */
StatusChecker();
public: /* methods */
QString getLastLoadErrorMsg() const;
bool isLoadingStatus() const;
QMap<QString, QString> getStatusEntries() const;
signals:
void statusLoading(bool loading);
void statusChanged(QMap<QString, QString> newStatus);
public slots:
void reloadStatus();
protected: /* methods */
virtual void timerEvent(QTimerEvent *);
protected slots:
void statusDownloadFinished();
void statusDownloadFailed(QString reason);
void succeed();
void fail(const QString& errorMsg);
protected: /* data */
QMap<QString, QString> m_prevEntries;
QMap<QString, QString> m_statusEntries;
NetJobPtr m_statusNetJob;
QString m_lastLoadError;
QByteArray dataSink;
};

View File

@@ -1,54 +0,0 @@
#include "BuildConfig.h"
#include <QObject>
Config BuildConfig;
Config::Config()
{
// Version information
VERSION_MAJOR = @MultiMC_VERSION_MAJOR@;
VERSION_MINOR = @MultiMC_VERSION_MINOR@;
VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@;
VERSION_BUILD = @MultiMC_VERSION_BUILD@;
BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@";
CHANLIST_URL = "@MultiMC_CHANLIST_URL@";
ANALYTICS_ID = "@MultiMC_ANALYTICS_ID@";
NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@";
FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@";
GIT_COMMIT = "@MultiMC_GIT_COMMIT@";
GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@";
if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0)
{
VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/");
UPDATER_ENABLED = true;
}
else
{
VERSION_CHANNEL = QObject::tr("custom");
}
VERSION_STR = "@MultiMC_VERSION_STRING@";
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@";
}
QString Config::printableVersionString() const
{
QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX));
// If the build is not a main release, append the channel
if(VERSION_CHANNEL != "stable")
{
vstr += "-" + VERSION_CHANNEL;
}
// if a build number is set, also add it to the end
if(VERSION_BUILD >= 0)
{
vstr += "-" + QString::number(VERSION_BUILD);
}
return vstr;
}

View File

@@ -1,392 +0,0 @@
project(application)
######## Configure the file with build properties ########
configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR}/BuildConfig.cpp")
################################ FILES ################################
######## Sources and headers ########
SET(MULTIMC_SOURCES
# Application base
main.cpp
MultiMC.h
MultiMC.cpp
BuildConfig.h
${PROJECT_BINARY_DIR}/BuildConfig.cpp
UpdateController.cpp
UpdateController.h
# GUI - general utilities
GuiUtil.h
GuiUtil.cpp
ColumnResizer.h
ColumnResizer.cpp
InstanceProxyModel.h
InstanceProxyModel.cpp
VersionProxyModel.h
VersionProxyModel.cpp
ColorCache.h
ColorCache.cpp
HoeDown.h
# Super secret!
KonamiCode.h
KonamiCode.cpp
# GUI - windows
MainWindow.h
MainWindow.cpp
InstanceWindow.h
InstanceWindow.cpp
# GUI - setup wizard
setupwizard/SetupWizard.h
setupwizard/SetupWizard.cpp
setupwizard/AnalyticsWizardPage.cpp
setupwizard/AnalyticsWizardPage.h
setupwizard/BaseWizardPage.h
setupwizard/JavaWizardPage.cpp
setupwizard/JavaWizardPage.h
setupwizard/LanguageWizardPage.cpp
setupwizard/LanguageWizardPage.h
# GUI - themes
themes/FusionTheme.cpp
themes/FusionTheme.h
themes/BrightTheme.cpp
themes/BrightTheme.h
themes/CustomTheme.cpp
themes/CustomTheme.h
themes/DarkTheme.cpp
themes/DarkTheme.h
themes/ITheme.cpp
themes/ITheme.h
themes/SystemTheme.cpp
themes/SystemTheme.h
# Processes
LaunchController.h
LaunchController.cpp
# page provider for instances
InstancePageProvider.h
# Common java checking UI
JavaCommon.h
JavaCommon.cpp
# GUI - paged dialog base
pages/BasePage.h
pages/BasePageContainer.h
pages/BasePageProvider.h
# GUI - instance pages
pages/instance/GameOptionsPage.cpp
pages/instance/GameOptionsPage.h
pages/instance/VersionPage.cpp
pages/instance/VersionPage.h
pages/instance/TexturePackPage.h
pages/instance/ResourcePackPage.h
pages/instance/ModFolderPage.cpp
pages/instance/ModFolderPage.h
pages/instance/NotesPage.cpp
pages/instance/NotesPage.h
pages/instance/LogPage.cpp
pages/instance/LogPage.h
pages/instance/InstanceSettingsPage.cpp
pages/instance/InstanceSettingsPage.h
pages/instance/ScreenshotsPage.cpp
pages/instance/ScreenshotsPage.h
pages/instance/OtherLogsPage.cpp
pages/instance/OtherLogsPage.h
pages/instance/ServersPage.cpp
pages/instance/ServersPage.h
pages/instance/LegacyUpgradePage.cpp
pages/instance/LegacyUpgradePage.h
pages/instance/WorldListPage.cpp
pages/instance/WorldListPage.h
# GUI - global settings pages
pages/global/AccountListPage.cpp
pages/global/AccountListPage.h
pages/global/CustomCommandsPage.cpp
pages/global/CustomCommandsPage.h
pages/global/ExternalToolsPage.cpp
pages/global/ExternalToolsPage.h
pages/global/JavaPage.cpp
pages/global/JavaPage.h
pages/global/LanguagePage.cpp
pages/global/LanguagePage.h
pages/global/MinecraftPage.cpp
pages/global/MinecraftPage.h
pages/global/MultiMCPage.cpp
pages/global/MultiMCPage.h
pages/global/ProxyPage.cpp
pages/global/ProxyPage.h
pages/global/PasteEEPage.cpp
pages/global/PasteEEPage.h
pages/global/PackagesPage.cpp
pages/global/PackagesPage.h
# GUI - platform pages
pages/modplatform/VanillaPage.cpp
pages/modplatform/VanillaPage.h
pages/modplatform/FTBPage.cpp
pages/modplatform/FTBPage.h
pages/modplatform/FtbListModel.h
pages/modplatform/FtbListModel.cpp
pages/modplatform/TwitchPage.cpp
pages/modplatform/TwitchPage.h
pages/modplatform/TechnicPage.cpp
pages/modplatform/TechnicPage.h
pages/modplatform/ImportPage.cpp
pages/modplatform/ImportPage.h
# GUI - dialogs
dialogs/AboutDialog.cpp
dialogs/AboutDialog.h
dialogs/ProfileSelectDialog.cpp
dialogs/ProfileSelectDialog.h
dialogs/CopyInstanceDialog.cpp
dialogs/CopyInstanceDialog.h
dialogs/CustomMessageBox.cpp
dialogs/CustomMessageBox.h
dialogs/EditAccountDialog.cpp
dialogs/EditAccountDialog.h
dialogs/ExportInstanceDialog.cpp
dialogs/ExportInstanceDialog.h
dialogs/IconPickerDialog.cpp
dialogs/IconPickerDialog.h
dialogs/LoginDialog.cpp
dialogs/LoginDialog.h
dialogs/ModEditDialogCommon.cpp
dialogs/ModEditDialogCommon.h
dialogs/NewComponentDialog.cpp
dialogs/NewComponentDialog.h
dialogs/NewInstanceDialog.cpp
dialogs/NewInstanceDialog.h
dialogs/NotificationDialog.cpp
dialogs/NotificationDialog.h
pagedialog/PageDialog.cpp
pagedialog/PageDialog.h
dialogs/ProgressDialog.cpp
dialogs/ProgressDialog.h
dialogs/UpdateDialog.cpp
dialogs/UpdateDialog.h
dialogs/VersionSelectDialog.cpp
dialogs/VersionSelectDialog.h
dialogs/SkinUploadDialog.cpp
dialogs/SkinUploadDialog.h
# GUI - widgets
widgets/Common.cpp
widgets/Common.h
widgets/CustomCommands.cpp
widgets/CustomCommands.h
widgets/FocusLineEdit.cpp
widgets/FocusLineEdit.h
widgets/IconLabel.cpp
widgets/IconLabel.h
widgets/JavaSettingsWidget.cpp
widgets/JavaSettingsWidget.h
widgets/LabeledToolButton.cpp
widgets/LabeledToolButton.h
widgets/LanguageSelectionWidget.cpp
widgets/LanguageSelectionWidget.h
widgets/LineSeparator.cpp
widgets/LineSeparator.h
widgets/LogView.cpp
widgets/LogView.h
widgets/MCModInfoFrame.cpp
widgets/MCModInfoFrame.h
widgets/ModListView.cpp
widgets/ModListView.h
widgets/PageContainer.cpp
widgets/PageContainer.h
widgets/PageContainer_p.h
widgets/ServerStatus.cpp
widgets/ServerStatus.h
widgets/VersionListView.cpp
widgets/VersionListView.h
widgets/VersionSelectWidget.cpp
widgets/VersionSelectWidget.h
widgets/ProgressWidget.h
widgets/ProgressWidget.cpp
# GUI - instance group view
groupview/GroupedProxyModel.cpp
groupview/GroupedProxyModel.h
groupview/GroupView.cpp
groupview/GroupView.h
groupview/InstanceDelegate.cpp
groupview/InstanceDelegate.h
groupview/VisualGroup.cpp
groupview/VisualGroup.h
)
######## UIs ########
SET(MULTIMC_UIS
# Instance pages
pages/instance/GameOptionsPage.ui
pages/instance/VersionPage.ui
pages/instance/ModFolderPage.ui
pages/instance/LogPage.ui
pages/instance/InstanceSettingsPage.ui
pages/instance/NotesPage.ui
pages/instance/ScreenshotsPage.ui
pages/instance/OtherLogsPage.ui
pages/instance/LegacyUpgradePage.ui
pages/instance/ServersPage.ui
pages/instance/WorldListPage.ui
# Global settings pages
pages/global/AccountListPage.ui
pages/global/ExternalToolsPage.ui
pages/global/JavaPage.ui
pages/global/MinecraftPage.ui
pages/global/MultiMCPage.ui
pages/global/ProxyPage.ui
pages/global/PasteEEPage.ui
pages/global/PackagesPage.ui
# Platform pages
pages/modplatform/VanillaPage.ui
pages/modplatform/FTBPage.ui
pages/modplatform/TwitchPage.ui
pages/modplatform/TechnicPage.ui
pages/modplatform/ImportPage.ui
# Dialogs
dialogs/CopyInstanceDialog.ui
dialogs/NewComponentDialog.ui
dialogs/NewInstanceDialog.ui
dialogs/AboutDialog.ui
dialogs/ProgressDialog.ui
dialogs/IconPickerDialog.ui
dialogs/ProfileSelectDialog.ui
dialogs/EditAccountDialog.ui
dialogs/ExportInstanceDialog.ui
dialogs/LoginDialog.ui
dialogs/UpdateDialog.ui
dialogs/NotificationDialog.ui
dialogs/SkinUploadDialog.ui
# Widgets/other
widgets/CustomCommands.ui
widgets/MCModInfoFrame.ui
)
set(MULTIMC_QRCS
resources/assets/assets.qrc
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
resources/pe_dark/pe_dark.qrc
resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
resources/flat/flat.qrc
resources/documents/documents.qrc
)
######## Windows resource files ########
if(WIN32)
set(MULTIMC_RCS resources/multimc.rc)
endif()
# Qt 5 stuff
qt5_wrap_ui(MULTIMC_UI ${MULTIMC_UIS})
qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
# Add executable
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics)
if(DEFINED MultiMC_APP_BINARY_NAME)
set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}")
endif()
if(DEFINED MultiMC_BINARY_RPATH)
SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}")
endif()
if(DEFINED MultiMC_APP_BINARY_DEFS)
target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS})
endif()
install(TARGETS MultiMC
BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
)
#### The MultiMC bundle mess! ####
# Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system.
# NOTE: it seems that this absolutely has to be here, and nowhere else.
if(INSTALL_BUNDLE STREQUAL "full")
# Add qt.conf - this makes Qt stop looking for things outside the bundle
install(
CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
COMPONENT Runtime
)
# Bundle plugins
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng|webp" EXCLUDE
)
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
)
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
)
else()
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng|webp" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
@ONLY
)
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime)
endif()

View File

@@ -1,199 +0,0 @@
/*
* Copyright 2011 Aurélien Gâteau <agateau@kde.org>
* License: BSD-3-Clause
*/
#include <ColumnResizer.h>
#include <QDebug>
#include <QEvent>
#include <QFormLayout>
#include <QGridLayout>
#include <QTimer>
#include <QWidget>
class FormLayoutWidgetItem : public QWidgetItem
{
public:
FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole)
: QWidgetItem(widget)
, m_width(-1)
, m_formLayout(formLayout)
, m_itemRole(itemRole)
{}
QSize sizeHint() const
{
QSize size = QWidgetItem::sizeHint();
if (m_width != -1) {
size.setWidth(m_width);
}
return size;
}
QSize minimumSize() const
{
QSize size = QWidgetItem::minimumSize();
if (m_width != -1) {
size.setWidth(m_width);
}
return size;
}
QSize maximumSize() const
{
QSize size = QWidgetItem::maximumSize();
if (m_width != -1) {
size.setWidth(m_width);
}
return size;
}
void setWidth(int width)
{
if (width != m_width) {
m_width = width;
invalidate();
}
}
void setGeometry(const QRect& _rect)
{
QRect rect = _rect;
int width = widget()->sizeHint().width();
if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) {
rect.setLeft(rect.right() - width);
}
QWidgetItem::setGeometry(rect);
}
QFormLayout* formLayout() const
{
return m_formLayout;
}
private:
int m_width;
QFormLayout* m_formLayout;
QFormLayout::ItemRole m_itemRole;
};
typedef QPair<QGridLayout*, int> GridColumnInfo;
class ColumnResizerPrivate
{
public:
ColumnResizerPrivate(ColumnResizer* q_ptr)
: q(q_ptr)
, m_updateTimer(new QTimer(q))
{
m_updateTimer->setSingleShot(true);
m_updateTimer->setInterval(0);
QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth()));
}
void scheduleWidthUpdate()
{
m_updateTimer->start();
}
ColumnResizer* q;
QTimer* m_updateTimer;
QList<QWidget*> m_widgets;
QList<FormLayoutWidgetItem*> m_wrWidgetItemList;
QList<GridColumnInfo> m_gridColumnInfoList;
};
ColumnResizer::ColumnResizer(QObject* parent)
: QObject(parent)
, d(new ColumnResizerPrivate(this))
{}
ColumnResizer::~ColumnResizer()
{
delete d;
}
void ColumnResizer::addWidget(QWidget* widget)
{
d->m_widgets.append(widget);
widget->installEventFilter(this);
d->scheduleWidthUpdate();
}
void ColumnResizer::updateWidth()
{
int width = 0;
Q_FOREACH(QWidget* widget, d->m_widgets) {
width = qMax(widget->sizeHint().width(), width);
}
Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) {
item->setWidth(width);
item->formLayout()->update();
}
Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) {
info.first->setColumnMinimumWidth(info.second, width);
}
}
bool ColumnResizer::eventFilter(QObject*, QEvent* event)
{
if (event->type() == QEvent::Resize) {
d->scheduleWidthUpdate();
}
return false;
}
void ColumnResizer::addWidgetsFromLayout(QLayout* layout, int column)
{
Q_ASSERT(column >= 0);
QGridLayout* gridLayout = qobject_cast<QGridLayout*>(layout);
QFormLayout* formLayout = qobject_cast<QFormLayout*>(layout);
if (gridLayout) {
addWidgetsFromGridLayout(gridLayout, column);
} else if (formLayout) {
if (column > QFormLayout::SpanningRole) {
qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout";
return;
}
QFormLayout::ItemRole role = static_cast<QFormLayout::ItemRole>(column);
addWidgetsFromFormLayout(formLayout, role);
} else {
qCritical() << "Don't know how to handle layout" << layout;
}
}
void ColumnResizer::addWidgetsFromGridLayout(QGridLayout* layout, int column)
{
for (int row = 0; row < layout->rowCount(); ++row) {
QLayoutItem* item = layout->itemAtPosition(row, column);
if (!item) {
continue;
}
QWidget* widget = item->widget();
if (!widget) {
continue;
}
addWidget(widget);
}
d->m_gridColumnInfoList << GridColumnInfo(layout, column);
}
void ColumnResizer::addWidgetsFromFormLayout(QFormLayout* layout, QFormLayout::ItemRole role)
{
for (int row = 0; row < layout->rowCount(); ++row) {
QLayoutItem* item = layout->itemAt(row, role);
if (!item) {
continue;
}
QWidget* widget = item->widget();
if (!widget) {
continue;
}
layout->removeItem(item);
delete item;
FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role);
layout->setItem(row, role, newItem);
addWidget(widget);
d->m_wrWidgetItemList << newItem;
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2011 Aurélien Gâteau <agateau@kde.org>
* License: BSD-3-Clause
*/
#ifndef COLUMNRESIZER_H
#define COLUMNRESIZER_H
#include <QFormLayout>
#include <QtCore/QObject>
#include <QtCore/QList>
class QEvent;
class QGridLayout;
class QLayout;
class QWidget;
class ColumnResizerPrivate;
class ColumnResizer : public QObject
{
Q_OBJECT
public:
ColumnResizer(QObject* parent = 0);
~ColumnResizer();
void addWidget(QWidget* widget);
void addWidgetsFromLayout(QLayout*, int column);
void addWidgetsFromGridLayout(QGridLayout*, int column);
void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role);
private Q_SLOTS:
void updateWidth();
protected:
bool eventFilter(QObject*, QEvent* event);
private:
ColumnResizerPrivate* const d;
};
#endif /* COLUMNRESIZER_H */

View File

@@ -1,34 +0,0 @@
#include "InstanceProxyModel.h"
#include "MultiMC.h"
#include <BaseInstance.h>
#include <icons/IconList.h>
InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(parent)
{
}
QVariant InstanceProxyModel::data(const QModelIndex & index, int role) const
{
QVariant data = QSortFilterProxyModel::data(index, role);
if(role == Qt::DecorationRole)
{
return QVariant(MMC->icons()->getIcon(data.toString()));
}
return data;
}
bool InstanceProxyModel::subSortLessThan(const QModelIndex &left,
const QModelIndex &right) const
{
BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer());
BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer());
QString sortMode = MMC->settings()->get("InstSortMode").toString();
if (sortMode == "LastLaunch")
{
return pdataLeft->lastLaunch() > pdataRight->lastLaunch();
}
else
{
return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0;
}
}

View File

@@ -1,16 +0,0 @@
#pragma once
#include "groupview/GroupedProxyModel.h"
/**
* A proxy model that is responsible for sorting instances into groups
*/
class InstanceProxyModel : public GroupedProxyModel
{
public:
explicit InstanceProxyModel(QObject *parent = 0);
QVariant data(const QModelIndex & index, int role) const override;
protected:
virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override;
};

View File

@@ -1,312 +0,0 @@
#include "LaunchController.h"
#include "MainWindow.h"
#include <minecraft/auth/MojangAccountList.h>
#include "MultiMC.h"
#include "dialogs/CustomMessageBox.h"
#include "dialogs/ProfileSelectDialog.h"
#include "dialogs/ProgressDialog.h"
#include "dialogs/EditAccountDialog.h"
#include "InstanceWindow.h"
#include "BuildConfig.h"
#include "JavaCommon.h"
#include <QLineEdit>
#include <QInputDialog>
#include <tasks/Task.h>
#include <minecraft/auth/YggdrasilTask.h>
#include <launch/steps/TextPrint.h>
#include <QStringList>
LaunchController::LaunchController(QObject *parent) : Task(parent)
{
}
void LaunchController::executeTask()
{
if (!m_instance)
{
emitFailed(tr("No instance specified"));
return;
}
login();
}
// FIXME: minecraft specific
void LaunchController::login()
{
JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget);
// Find an account to use.
std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
MojangAccountPtr account = accounts->activeAccount();
if (accounts->count() <= 0)
{
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(
m_parentWidget, tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Mojang or Minecraft "
"account logged in to MultiMC."
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
if (reply == QMessageBox::Yes)
{
// Open the account manager.
MMC->ShowGlobalSettings(m_parentWidget, "accounts");
}
}
else if (account.get() == nullptr)
{
// If no default account is set, ask the user which one to use.
ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"),
ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget);
selectDialog.exec();
// Launch the instance with the selected account.
account = selectDialog.selectedAccount();
// If the user said to use the account as default, do that.
if (selectDialog.useAsGlobalDefault() && account.get() != nullptr)
accounts->setActiveAccount(account->username());
}
// if no account is selected, we bail
if (!account.get())
{
emitFailed(tr("No account selected for launch"));
return;
}
// we try empty password first :)
QString password;
// we loop until the user succeeds in logging in or gives up
bool tryagain = true;
// the failure. the default failure.
const QString needLoginAgain = tr("Your account is currently not logged in. Please enter "
"your password to log in again.");
QString failReason = needLoginAgain;
while (tryagain)
{
m_session = std::make_shared<AuthSession>();
m_session->wants_online = m_online;
auto task = account->login(m_session, password);
if (task)
{
// We'll need to validate the access token to make sure the account
// is still logged in.
ProgressDialog progDialog(m_parentWidget);
if (m_online)
{
progDialog.setSkipButton(true, tr("Play Offline"));
}
progDialog.execWithTask(task.get());
if (!task->wasSuccessful())
{
auto failReasonNew = task->failReason();
if(failReasonNew == "Invalid token.")
{
account->invalidateClientToken();
failReason = needLoginAgain;
}
else failReason = failReasonNew;
}
}
switch (m_session->status)
{
case AuthSession::Undetermined:
{
qCritical() << "Received undetermined session status during login. Bye.";
tryagain = false;
emitFailed(tr("Received undetermined session status during login."));
break;
}
case AuthSession::RequiresPassword:
{
EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField);
auto username = m_session->username;
auto chopN = [](QString toChop, int N) -> QString
{
if(toChop.size() > N)
{
auto left = toChop.left(N);
left += QString("\u25CF").repeated(toChop.size() - N);
return left;
}
return toChop;
};
if(username.contains('@'))
{
auto parts = username.split('@');
auto mailbox = chopN(parts[0],3);
QString domain = chopN(parts[1], 3);
username = mailbox + '@' + domain;
}
passDialog.setUsername(username);
if (passDialog.exec() == QDialog::Accepted)
{
password = passDialog.password();
}
else
{
tryagain = false;
}
break;
}
case AuthSession::PlayableOffline:
{
// we ask the user for a player name
bool ok = false;
QString usedname = m_session->player_name;
QString name = QInputDialog::getText(m_parentWidget, tr("Player name"),
tr("Choose your offline mode player name."),
QLineEdit::Normal, m_session->player_name, &ok);
if (!ok)
{
tryagain = false;
break;
}
if (name.length())
{
usedname = name;
}
m_session->MakeOffline(usedname);
// offline flavored game from here :3
}
case AuthSession::PlayableOnline:
{
launchInstance();
tryagain = false;
return;
}
}
}
emitFailed(tr("Failed to launch."));
}
void LaunchController::launchInstance()
{
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
return;
}
m_launcher = m_instance->createLaunchTask(m_session);
if (!m_launcher)
{
emitFailed(tr("Couldn't instantiate a launcher."));
return;
}
auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
auto showConsole = m_instance->settings()->get("ShowConsole").toBool();
if(!console && showConsole)
{
MMC->showInstanceWindow(m_instance);
}
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded);
connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
m_launcher->start();
}
void LaunchController::readyForLaunch()
{
if (!m_profiler)
{
m_launcher->proceed();
return;
}
QString error;
if (!m_profiler->check(&error))
{
m_launcher->abort();
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed");
return;
}
BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1").arg(message));
msg.setWindowTitle(tr("Waiting"));
msg.setIcon(QMessageBox::Information);
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
msg.setModal(true);
msg.exec();
m_launcher->proceed();
});
connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("Couldn't start the profiler: %1").arg(message));
msg.setWindowTitle(tr("Error"));
msg.setIcon(QMessageBox::Critical);
msg.addButton(QMessageBox::Ok);
msg.setModal(true);
msg.exec();
m_launcher->abort();
emitFailed("Profiler startup failed");
});
profilerInstance->beginProfiling(m_launcher);
}
void LaunchController::onSucceeded()
{
emitSucceeded();
}
void LaunchController::onFailed(QString reason)
{
if(m_instance->settings()->get("ShowConsoleOnError").toBool())
{
MMC->showInstanceWindow(m_instance, "console");
}
emitFailed(reason);
}
void LaunchController::onProgressRequested(Task* task)
{
ProgressDialog progDialog(m_parentWidget);
progDialog.setSkipButton(true, tr("Abort"));
m_launcher->proceed();
progDialog.execWithTask(task);
}
bool LaunchController::abort()
{
if(!m_launcher)
{
return true;
}
if(!m_launcher->canAbort())
{
return false;
}
auto response = CustomMessageBox::selectable(
m_parentWidget, tr("Kill Minecraft?"),
tr("This can cause the instance to get corrupted and should only be used if Minecraft "
"is frozen for some reason"),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
if (response == QMessageBox::Yes)
{
return m_launcher->abort();
}
return false;
}

View File

@@ -1,139 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AboutDialog.h"
#include "ui_AboutDialog.h"
#include <QIcon>
#include "MultiMC.h"
#include "BuildConfig.h"
#include <net/NetJob.h>
#include "HoeDown.h"
// Credits
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
static QString getCreditsHtml(QStringList patrons)
{
QString creditsHtml = QObject::tr(
"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>"
"<html>"
""
"<head>"
"<meta name='qrichtext' content='1' />"
"<style type='text/css'>"
"p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }"
"</style>"
"</head>"
""
"<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>"
""
"<h3>MultiMC Developers</h3>"
"<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>"
"<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>"
"<p>Sky Welch &lt;<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>&gt;</p>"
"<p>Jan (02JanDal) &lt;<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>&gt;</p>"
"<p>RoboSky &lt;<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>&gt;</p>"
""
"<h3>With thanks to</h3>"
"<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>"
"<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>"
"<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>"
"<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>"
""
"<h3>Patrons</h3>"
"%1"
""
"</body>"
"</html>");
if (patrons.isEmpty())
return creditsHtml.arg(QObject::tr("<p>Loading...</p>"));
else
{
QString patronsStr;
for (QString patron : patrons)
{
patronsStr.append(QString("<p>%1</p>").arg(patron));
}
return creditsHtml.arg(patronsStr);
}
}
static QString getLicenseHtml()
{
HoeDown hoedown;
QFile dataFile(":/documents/COPYING.md");
dataFile.open(QIODevice::ReadOnly);
QString output = hoedown.process(dataFile.readAll());
return output;
}
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
{
ui->setupUi(this);
QString chtml = getCreditsHtml(QStringList());
ui->creditsText->setHtml(chtml);
QString lhtml = getLicenseHtml();
ui->licenseText->setHtml(lhtml);
ui->urlLabel->setOpenExternalLinks(true);
ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64));
ui->title->setText("MultiMC 5");
ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString());
ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM);
if (BuildConfig.VERSION_BUILD >= 0)
ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
else
ui->buildNumLabel->setVisible(false);
if (!BuildConfig.VERSION_CHANNEL.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL);
else
ui->channelLabel->setVisible(false);
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt);
loadPatronList();
}
AboutDialog::~AboutDialog()
{
delete ui;
}
void AboutDialog::loadPatronList()
{
netJob.reset(new NetJob("Patreon Patron List"));
netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink));
connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded);
netJob->start();
}
void AboutDialog::patronListLoaded()
{
QString patronListStr(dataSink);
dataSink.clear();
QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts));
ui->creditsText->setHtml(html);
}

View File

@@ -1,40 +0,0 @@
#include "ModEditDialogCommon.h"
#include "CustomMessageBox.h"
#include <QUrl>
bool lastfirst(QModelIndexList &list, int &first, int &last)
{
if (list.isEmpty())
return false;
first = last = list[0].row();
for (auto item : list)
{
int row = item.row();
if (row < first)
first = row;
if (row > last)
last = row;
}
return true;
}
void showWebsiteForMod(QWidget *parentDlg, Mod &m)
{
QString url = m.homeurl();
if (url.size())
{
// catch the cases where the protocol is missing
if (!url.startsWith("http"))
{
url = "http://" + url;
}
DesktopServices::openUrl(url);
}
else
{
CustomMessageBox::selectable(
parentDlg, QObject::tr("How sad!"),
QObject::tr("The mod author didn't provide a website link for this mod."),
QMessageBox::Warning);
}
}

View File

@@ -1,9 +0,0 @@
#pragma once
#include <QModelIndex>
#include <DesktopServices.h>
#include <QWidget>
#include <minecraft/Mod.h>
bool lastfirst(QModelIndexList &list, int &first, int &last);
void showWebsiteForMod(QWidget *parentDlg, Mod &m);

View File

@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SkinUploadDialog</class>
<widget class="QDialog" name="SkinUploadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>413</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Skin Upload</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="fileBox">
<property name="title">
<string>Skin File</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="skinPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="skinBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="modelBox">
<property name="title">
<string>Player Model</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<widget class="QRadioButton" name="steveBtn">
<property name="text">
<string>Steve Model</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="alexBtn">
<property name="text">
<string>Alex Model</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,48 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "GroupedProxyModel.h"
#include "GroupView.h"
#include <QDebug>
GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString();
const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString();
if (leftCategory == rightCategory)
{
return subSortLessThan(left, right);
}
else
{
// FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString
auto result = leftCategory.localeAwareCompare(rightCategory);
if(result == 0)
{
return subSortLessThan(left, right);
}
return result < 0;
}
}
bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
{
return left.row() < right.row();
}

View File

@@ -1,11 +0,0 @@
[Desktop Entry]
Version=1.0
Name=MultiMC
GenericName=MultiMC
Comment=Free, open source launcher and instance manager for Minecraft.
Type=Application
Terminal=false
Exec=multimc
Icon=multimc
Categories=Game
Keywords=game;minecraft;

View File

@@ -1,12 +0,0 @@
# What is this?
A simple ubuntu package for MultiMC that wraps the contains a script that downloads and installs real MultiMC on ubuntu based systems.
It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
# How to build this?
You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run:
```
fakeroot dpkg-deb --build multimc_1.3-1
```
Replace the version with whatever is appropriate.

View File

@@ -1,233 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AccountListPage.h"
#include "ui_AccountListPage.h"
#include <QItemSelectionModel>
#include <QDebug>
#include "net/NetJob.h"
#include "net/URLConstants.h"
#include "Env.h"
#include "dialogs/ProgressDialog.h"
#include "dialogs/LoginDialog.h"
#include "dialogs/CustomMessageBox.h"
#include "dialogs/SkinUploadDialog.h"
#include "tasks/Task.h"
#include "minecraft/auth/YggdrasilTask.h"
#include "MultiMC.h"
enum class OfflineModeNameMode
{
UseAccountName = 1,
RememberPerAccount = 2,
RememberPerInstance = 3,
UseFixedName = 4
};
AccountListPage::AccountListPage(QWidget *parent)
: QWidget(parent), ui(new Ui::AccountListPage)
{
ui->setupUi(this);
m_accounts = MMC->accounts();
ui->listView->setModel(m_accounts.get());
ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
// Expand the account column
ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
QItemSelectionModel *selectionModel = ui->listView->selectionModel();
connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this](const QItemSelection &sel, const QItemSelection &dsel)
{ updateButtonStates(); });
connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged()));
connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged()));
ui->offlineButtonGroup->setId(ui->useSelectedNameBtn, int(OfflineModeNameMode::UseAccountName));
ui->offlineButtonGroup->setId(ui->rememberNamesForAccountsBtn, int(OfflineModeNameMode::RememberPerAccount));
ui->offlineButtonGroup->setId(ui->rememberNamesForInstancesBtn, int(OfflineModeNameMode::RememberPerInstance));
ui->offlineButtonGroup->setId(ui->useFixedNameBtn, int(OfflineModeNameMode::UseFixedName));
connect(ui->offlineButtonGroup, SIGNAL(buttonToggled(int,bool)), this, SLOT(groupSelectionChanged(int,bool)));
updateButtonStates();
loadSettings();
}
AccountListPage::~AccountListPage()
{
delete ui;
}
void AccountListPage::listChanged()
{
updateButtonStates();
}
void AccountListPage::on_addAccountBtn_clicked()
{
addAccount(tr("Please enter your Mojang or Minecraft account username and password to add "
"your account."));
}
void AccountListPage::on_rmAccountBtn_clicked()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
m_accounts->removeAccount(selected);
}
}
void AccountListPage::on_setDefaultBtn_clicked()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
MojangAccountPtr account =
selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
m_accounts->setActiveAccount(account->username());
}
}
void AccountListPage::on_noDefaultBtn_clicked()
{
m_accounts->setActiveAccount("");
}
void AccountListPage::updateButtonStates()
{
// If there is no selection, disable buttons that require something selected.
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
ui->rmAccountBtn->setEnabled(selection.size() > 0);
ui->setDefaultBtn->setEnabled(selection.size() > 0);
ui->uploadSkinBtn->setEnabled(selection.size() > 0);
ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
}
void AccountListPage::addAccount(const QString &errMsg)
{
// TODO: The login dialog isn't quite done yet
MojangAccountPtr account = LoginDialog::newAccount(this, errMsg);
if (account != nullptr)
{
m_accounts->addAccount(account);
if (m_accounts->count() == 1)
m_accounts->setActiveAccount(account->username());
// Grab associated player skins
auto job = new NetJob("Player skins: " + account->username());
for (AccountProfile profile : account->profiles())
{
auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png");
auto action = Net::Download::makeCached(QUrl(URLConstants::SKINS_BASE + profile.id + ".png"), meta);
job->addNetAction(action);
meta->setStale(true);
}
job->start();
}
}
void AccountListPage::on_uploadSkinBtn_clicked()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
SkinUploadDialog dialog(account, this);
dialog.exec();
}
}
bool AccountListPage::apply()
{
applySettings();
return true;
}
void AccountListPage::applySettings()
{
auto s = MMC->settings();
auto sortMode = (OfflineModeNameMode)ui->offlineButtonGroup->checkedId();
switch (sortMode)
{
default:
case OfflineModeNameMode::UseAccountName:
s->set("OfflineModeNameMode", "UseAccountName");
break;
case OfflineModeNameMode::RememberPerAccount:
s->set("OfflineModeNameMode", "RememberPerAccount");
break;
case OfflineModeNameMode::RememberPerInstance:
s->set("OfflineModeNameMode", "RememberPerInstance");
break;
case OfflineModeNameMode::UseFixedName:
s->set("OfflineModeNameMode", "UseFixedName");
break;
}
s->set("OfflineModeName", ui->mainOfflineNameEdit->text());
}
void AccountListPage::loadSettings()
{
auto s = MMC->settings();
auto value = s->get("OfflineModeNameMode").toString();
if(value == "UseAccountName")
{
ui->useSelectedNameBtn->setChecked(true);
}
else if(value == "RememberPerAccount")
{
ui->rememberNamesForAccountsBtn->setChecked(true);
}
else if(value == "RememberPerInstance")
{
ui->rememberNamesForInstancesBtn->setChecked(true);
}
else if(value == "UseFixedName")
{
ui->useFixedNameBtn->setChecked(true);
}
ui->mainOfflineNameEdit->setText(s->get("OfflineModeName").toString());
}
void AccountListPage::groupSelectionChanged(int, bool)
{
auto sortMode = (OfflineModeNameMode)ui->offlineButtonGroup->checkedId();
if(sortMode == OfflineModeNameMode::UseFixedName)
{
ui->mainOfflineNameEdit->setEnabled(true);
}
else
{
ui->mainOfflineNameEdit->setEnabled(false);
}
}

View File

@@ -1,201 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AccountListPage</class>
<widget class="QWidget" name="AccountListPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>302</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="onlineTab">
<attribute name="title">
<string>Online</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="welcomeLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Welcome! If you're new here, you can click the &amp;quot;Add&amp;quot; button to add your Mojang or Minecraft account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeView" name="listView"/>
</item>
<item>
<layout class="QVBoxLayout" name="manageAcctsBtnBox">
<item>
<widget class="QPushButton" name="addAccountBtn">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rmAccountBtn">
<property name="text">
<string>&amp;Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="setDefaultBtn">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>&amp;Set Default</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="noDefaultBtn">
<property name="toolTip">
<string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string>
</property>
<property name="text">
<string>&amp;No Default</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uploadSkinBtn">
<property name="toolTip">
<string>Opens a dialog to select and upload a skin image to the selected account.</string>
</property>
<property name="text">
<string>&amp;Upload Skin</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="offlineTab">
<attribute name="title">
<string>Offline</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout22">
<item>
<widget class="QGroupBox" name="offlineBox">
<property name="title">
<string>Offline mode name</string>
</property>
<layout class="QGridLayout" name="foldersBoxLayout_2">
<item row="0" column="0">
<widget class="QRadioButton" name="useSelectedNameBtn">
<property name="text">
<string>&amp;Use selected account name</string>
</property>
<attribute name="buttonGroup">
<string notr="true">offlineButtonGroup</string>
</attribute>
</widget>
</item>
<item row="3" column="0">
<widget class="QRadioButton" name="useFixedNameBtn">
<property name="text">
<string>Always use &amp;this offline name:</string>
</property>
<attribute name="buttonGroup">
<string notr="true">offlineButtonGroup</string>
</attribute>
</widget>
</item>
<item row="4" column="0">
<widget class="QLineEdit" name="mainOfflineNameEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="rememberNamesForAccountsBtn">
<property name="text">
<string>Remember offline &amp;names per account</string>
</property>
<attribute name="buttonGroup">
<string notr="true">offlineButtonGroup</string>
</attribute>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="rememberNamesForInstancesBtn">
<property name="text">
<string>Remember offline &amp;names per instance</string>
</property>
<attribute name="buttonGroup">
<string notr="true">offlineButtonGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="offlineButtonGroup"/>
</buttongroups>
</ui>

View File

@@ -1,224 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PackagesPage.h"
#include "ui_PackagesPage.h"
#include <QDateTime>
#include <QSortFilterProxyModel>
#include <QRegularExpression>
#include "dialogs/ProgressDialog.h"
#include "VersionProxyModel.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "meta/Version.h"
#include "Env.h"
#include "MultiMC.h"
using namespace Meta;
static QString formatRequires(const VersionPtr &version)
{
QStringList lines;
auto & reqs = version->requires();
auto iter = reqs.begin();
while (iter != reqs.end())
{
auto &uid = iter->uid;
auto &version = iter->equalsVersion;
const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
if(!version.isEmpty())
{
lines.append(QString("%1 (%2)").arg(readable, version));
}
else
{
lines.append(QString("%1").arg(readable));
}
iter++;
}
return lines.join('\n');
}
PackagesPage::PackagesPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::PackagesPage)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
m_fileProxy = new QSortFilterProxyModel(this);
m_fileProxy->setSortRole(Qt::DisplayRole);
m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_fileProxy->setFilterRole(Qt::DisplayRole);
m_fileProxy->setFilterKeyColumn(0);
m_fileProxy->sort(0);
m_fileProxy->setSourceModel(ENV.metadataIndex().get());
ui->indexView->setModel(m_fileProxy);
m_filterProxy = new QSortFilterProxyModel(this);
m_filterProxy->setSortRole(VersionList::SortRole);
m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_filterProxy->setFilterRole(Qt::DisplayRole);
m_filterProxy->setFilterKeyColumn(0);
m_filterProxy->sort(0, Qt::DescendingOrder);
ui->versionsView->setModel(m_filterProxy);
m_versionProxy = new VersionProxyModel(this);
m_filterProxy->setSourceModel(m_versionProxy);
connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList);
connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion);
connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged);
updateCurrentVersionList(QModelIndex());
updateVersion();
}
PackagesPage::~PackagesPage()
{
delete ui;
}
QIcon PackagesPage::icon() const
{
return MMC->getThemedIcon("packages");
}
void PackagesPage::on_refreshIndexBtn_clicked()
{
ENV.metadataIndex()->load(Net::Mode::Online);
}
void PackagesPage::on_refreshFileBtn_clicked()
{
VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>();
if (!list)
{
return;
}
list->load(Net::Mode::Online);
}
void PackagesPage::on_refreshVersionBtn_clicked()
{
VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>();
if (!version)
{
return;
}
version->load(Net::Mode::Online);
}
void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
{
if (search.isEmpty())
{
m_fileProxy->setFilterFixedString(QString());
}
else
{
QStringList parts = search.split(' ');
std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
}
}
void PackagesPage::on_versionSearchEdit_textChanged(const QString &search)
{
if (search.isEmpty())
{
m_filterProxy->setFilterFixedString(QString());
}
else
{
QStringList parts = search.split(' ');
std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
}
}
void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
{
if (index.isValid())
{
VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>();
ui->versionsBox->setEnabled(true);
ui->refreshFileBtn->setEnabled(true);
ui->fileUidLabel->setEnabled(true);
ui->fileUid->setText(list->uid());
ui->fileNameLabel->setEnabled(true);
ui->fileName->setText(list->name());
m_versionProxy->setSourceModel(list.get());
ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
list->load(Net::Mode::Offline);
}
else
{
ui->versionsBox->setEnabled(false);
ui->refreshFileBtn->setEnabled(false);
ui->fileUidLabel->setEnabled(false);
ui->fileUid->clear();
ui->fileNameLabel->setEnabled(false);
ui->fileName->clear();
m_versionProxy->setSourceModel(nullptr);
ui->refreshFileBtn->setText(tr("Refresh"));
}
}
void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br)
{
if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex()))
{
updateVersion();
}
}
void PackagesPage::updateVersion()
{
VersionPtr version = std::dynamic_pointer_cast<Version>(
ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>());
if (version)
{
ui->refreshVersionBtn->setEnabled(true);
ui->versionVersionLabel->setEnabled(true);
ui->versionVersion->setText(version->version());
ui->versionTimeLabel->setEnabled(true);
ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm"));
ui->versionTypeLabel->setEnabled(true);
ui->versionType->setText(version->type());
ui->versionRequiresLabel->setEnabled(true);
ui->versionRequires->setText(formatRequires(version));
ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version()));
}
else
{
ui->refreshVersionBtn->setEnabled(false);
ui->versionVersionLabel->setEnabled(false);
ui->versionVersion->clear();
ui->versionTimeLabel->setEnabled(false);
ui->versionTime->clear();
ui->versionTypeLabel->setEnabled(false);
ui->versionType->clear();
ui->versionRequiresLabel->setEnabled(false);
ui->versionRequires->clear();
ui->refreshVersionBtn->setText(tr("Refresh"));
}
}
void PackagesPage::openedImpl()
{
ENV.metadataIndex()->load(Net::Mode::Offline);
}

View File

@@ -1,57 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QWidget>
#include "pages/BasePage.h"
namespace Ui {
class PackagesPage;
}
class QSortFilterProxyModel;
class VersionProxyModel;
class PackagesPage : public QWidget, public BasePage
{
Q_OBJECT
public:
explicit PackagesPage(QWidget *parent = 0);
~PackagesPage();
QString id() const override { return "packages-global"; }
QString displayName() const override { return tr("Packages"); }
QIcon icon() const override;
void openedImpl() override;
private slots:
void on_refreshIndexBtn_clicked();
void on_refreshFileBtn_clicked();
void on_refreshVersionBtn_clicked();
void on_fileSearchEdit_textChanged(const QString &search);
void on_versionSearchEdit_textChanged(const QString &search);
void updateCurrentVersionList(const QModelIndex &index);
void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br);
private:
Ui::PackagesPage *ui;
QSortFilterProxyModel *m_fileProxy;
QSortFilterProxyModel *m_filterProxy;
VersionProxyModel *m_versionProxy;
void updateVersion();
};

View File

@@ -1,252 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PackagesPage</class>
<widget class="QWidget" name="PackagesPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>636</width>
<height>621</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QGroupBox" name="versionsBox">
<property name="title">
<string>Versions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLineEdit" name="versionSearchEdit">
<property name="placeholderText">
<string>Search...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="versionsView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="refreshVersionBtn">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="versionVersionLabel">
<property name="text">
<string>Version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="versionVersion">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="versionTimeLabel">
<property name="text">
<string>Time:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="versionTime">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="versionTypeLabel">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="versionType">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="versionRequiresLabel">
<property name="text">
<string>Dependencies:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="versionRequires">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="versionListsBox">
<property name="title">
<string>Resources</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="fileSearchEdit">
<property name="placeholderText">
<string>Search...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="indexView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="refreshFileBtn">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="fileUidLabel">
<property name="text">
<string>UID:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="fileUid">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="fileNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="fileName">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QPushButton" name="refreshIndexBtn">
<property name="text">
<string>Refresh Index</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,210 +0,0 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModFolderPage.h"
#include "ui_ModFolderPage.h"
#include <QMessageBox>
#include <QEvent>
#include <QKeyEvent>
#include <QAbstractItemModel>
#include "MultiMC.h"
#include "dialogs/CustomMessageBox.h"
#include "dialogs/ModEditDialogCommon.h"
#include <GuiUtil.h>
#include "minecraft/SimpleModList.h"
#include "minecraft/Mod.h"
#include "minecraft/VersionFilterData.h"
#include "minecraft/ComponentList.h"
#include <DesktopServices.h>
ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id,
QString iconName, QString displayName, QString helpPage,
QWidget *parent)
: QWidget(parent), ui(new Ui::ModFolderPage)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
m_inst = inst;
m_mods = mods;
m_id = id;
m_displayName = displayName;
m_iconName = iconName;
m_helpName = helpPage;
m_fileSelectionFilter = "%1 (*.zip *.jar)";
m_filterModel = new QSortFilterProxyModel(this);
m_filterModel->setDynamicSortFilter(true);
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_filterModel->setSourceModel(m_mods.get());
m_filterModel->setFilterKeyColumn(-1);
ui->modTreeView->setModel(m_filterModel);
ui->modTreeView->installEventFilter(this);
ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
auto smodel = ui->modTreeView->selectionModel();
connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged );
}
void ModFolderPage::openedImpl()
{
m_mods->startWatching();
}
void ModFolderPage::closedImpl()
{
m_mods->stopWatching();
}
void ModFolderPage::on_filterTextChanged(const QString& newContents)
{
m_viewFilter = newContents;
m_filterModel->setFilterFixedString(m_viewFilter);
}
CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods,
QString id, QString iconName, QString displayName,
QString helpPage, QWidget *parent)
: ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
{
}
ModFolderPage::~ModFolderPage()
{
m_mods->stopWatching();
delete ui;
}
bool ModFolderPage::shouldDisplay() const
{
if (m_inst)
return !m_inst->isRunning();
return true;
}
bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay())
{
auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
if (!inst)
return true;
auto version = inst->getComponentList();
if (!version)
return true;
if(!version->getComponent("net.minecraftforge"))
{
return false;
}
if(!version->getComponent("net.minecraft"))
{
return false;
}
if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
{
return true;
}
}
return false;
}
bool ModFolderPage::modListFilter(QKeyEvent *keyEvent)
{
switch (keyEvent->key())
{
case Qt::Key_Delete:
on_rmModBtn_clicked();
return true;
case Qt::Key_Plus:
on_addModBtn_clicked();
return true;
default:
break;
}
return QWidget::eventFilter(ui->modTreeView, keyEvent);
}
bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() != QEvent::KeyPress)
{
return QWidget::eventFilter(obj, ev);
}
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
if (obj == ui->modTreeView)
return modListFilter(keyEvent);
return QWidget::eventFilter(obj, ev);
}
void ModFolderPage::on_addModBtn_clicked()
{
auto list = GuiUtil::BrowseForFiles(
m_helpName,
tr("Select %1",
"Select whatever type of files the page contains. Example: 'Loader Mods'")
.arg(m_displayName),
m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(),
this->parentWidget());
if (!list.empty())
{
for (auto filename : list)
{
m_mods->installMod(filename);
}
}
}
void ModFolderPage::on_enableModBtn_clicked()
{
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
m_mods->enableMods(selection.indexes(), true);
}
void ModFolderPage::on_disableModBtn_clicked()
{
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
m_mods->enableMods(selection.indexes(), false);
}
void ModFolderPage::on_rmModBtn_clicked()
{
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
m_mods->deleteMods(selection.indexes());
}
void ModFolderPage::on_configFolderBtn_clicked()
{
DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
}
void ModFolderPage::on_viewModBtn_clicked()
{
DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
}
void ModFolderPage::modCurrent(const QModelIndex &current, const QModelIndex &previous)
{
if (!current.isValid())
{
ui->frame->clear();
return;
}
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
Mod &m = m_mods->operator[](row);
ui->frame->updateWithMod(m);
}

View File

@@ -1,180 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ModFolderPage</class>
<widget class="QWidget" name="ModFolderPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>723</width>
<height>532</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="addModBtn">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rmModBtn">
<property name="text">
<string>&amp;Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="enableModBtn">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="disableModBtn">
<property name="text">
<string>Disable</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="configFolderBtn">
<property name="toolTip">
<string>Open the 'config' folder in the system file manager.</string>
</property>
<property name="text">
<string>View configs</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="viewModBtn">
<property name="text">
<string>&amp;View Folder</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="3">
<widget class="MCModInfoFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QLineEdit" name="filterEdit">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="filterLabel">
<property name="text">
<string>Filter:</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="ModListView" name="modTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ModListView</class>
<extends>QTreeView</extends>
<header>widgets/ModListView.h</header>
</customwidget>
<customwidget>
<class>MCModInfoFrame</class>
<extends>QFrame</extends>
<header>widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>modTreeView</tabstop>
<tabstop>filterEdit</tabstop>
<tabstop>addModBtn</tabstop>
<tabstop>rmModBtn</tabstop>
<tabstop>enableModBtn</tabstop>
<tabstop>disableModBtn</tabstop>
<tabstop>configFolderBtn</tabstop>
<tabstop>viewModBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NotesPage</class>
<widget class="QWidget" name="NotesPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>731</width>
<height>538</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="noteEditor">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScreenshotsPage</class>
<widget class="QWidget" name="ScreenshotsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>723</width>
<height>532</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListView" name="listView">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="uploadBtn">
<property name="text">
<string>&amp;Upload</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteBtn">
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="renameBtn">
<property name="text">
<string>&amp;Rename</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="viewFolderBtn">
<property name="text">
<string>&amp;View Folder</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>listView</tabstop>
<tabstop>uploadBtn</tabstop>
<tabstop>deleteBtn</tabstop>
<tabstop>renameBtn</tabstop>
<tabstop>viewFolderBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,199 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ServersPage</class>
<widget class="QWidget" name="ServersPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>706</width>
<height>575</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="1">
<widget class="QComboBox" name="resourceComboBox">
<item>
<property name="text">
<string>Ask to download</string>
</property>
</item>
<item>
<property name="text">
<string>Always download</string>
</property>
</item>
<item>
<property name="text">
<string>Never download</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="resourcesLabel">
<property name="text">
<string>Reso&amp;urces</string>
</property>
<property name="buddy">
<cstring>resourceComboBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="addressLine"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>&amp;Name</string>
</property>
<property name="buddy">
<cstring>nameLine</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="nameLine"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="addressLabel">
<property name="text">
<string>Address</string>
</property>
<property name="buddy">
<cstring>addressLine</cstring>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QTreeView" name="serversView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="addBtn">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeBtn">
<property name="text">
<string>&amp;Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveUpBtn">
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveDownBtn">
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>serversView</tabstop>
<tabstop>nameLine</tabstop>
<tabstop>addressLine</tabstop>
<tabstop>resourceComboBox</tabstop>
<tabstop>addBtn</tabstop>
<tabstop>removeBtn</tabstop>
<tabstop>moveUpBtn</tabstop>
<tabstop>moveDownBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,332 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VersionPage</class>
<widget class="QWidget" name="VersionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>870</width>
<height>1008</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="ModListView" name="packageView">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="headerHidden">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Selection</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeVersionBtn">
<property name="toolTip">
<string>Change version of the selected package.</string>
</property>
<property name="text">
<string>Change version</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveUpBtn">
<property name="toolTip">
<string>Make the selected package apply sooner.</string>
</property>
<property name="text">
<string>Move up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveDownBtn">
<property name="toolTip">
<string>Make the selected package apply later.</string>
</property>
<property name="text">
<string>Move down</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeBtn">
<property name="toolTip">
<string>Remove selected package from the instance.</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="LineSeparator" name="separator_4" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Edit</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="customizeBtn">
<property name="toolTip">
<string>Customize selected package.</string>
</property>
<property name="text">
<string>Customize</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editBtn">
<property name="toolTip">
<string>Edit selected package.</string>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="revertBtn">
<property name="toolTip">
<string>Revert the selected package to default.</string>
</property>
<property name="text">
<string>Revert</string>
</property>
</widget>
</item>
<item>
<widget class="LineSeparator" name="separator" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Install</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="forgeBtn">
<property name="toolTip">
<string>Install the Minecraft Forge package.</string>
</property>
<property name="text">
<string>Install Forge</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fabricBtn">
<property name="toolTip">
<string>Install the Fabric Loader package.</string>
</property>
<property name="text">
<string>Install Fabric</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="liteloaderBtn">
<property name="toolTip">
<string>Install the LiteLoader package.</string>
</property>
<property name="text">
<string>Install LiteLoader</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="modBtn">
<property name="toolTip">
<string>Install normal mods.</string>
</property>
<property name="text">
<string>Install mods</string>
</property>
</widget>
</item>
<item>
<widget class="LineSeparator" name="widget" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Advanced</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="jarmodBtn">
<property name="toolTip">
<string>Add a mod into the Minecraft jar file.</string>
</property>
<property name="text">
<string>Add to Minecraft.jar</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="jarBtn">
<property name="text">
<string>Replace Minecraft.jar</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addEmptyBtn">
<property name="text">
<string>Add Empty</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reloadBtn">
<property name="toolTip">
<string>Reload all packages.</string>
</property>
<property name="text">
<string>Reload</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downloadBtn">
<property name="toolTip">
<string>Download the files needed to launch the instance now.</string>
</property>
<property name="text">
<string>Download All</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>111</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="MCModInfoFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ModListView</class>
<extends>QTreeView</extends>
<header>widgets/ModListView.h</header>
</customwidget>
<customwidget>
<class>LineSeparator</class>
<extends>QWidget</extends>
<header>widgets/LineSeparator.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MCModInfoFrame</class>
<extends>QFrame</extends>
<header>widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>packageView</tabstop>
<tabstop>changeVersionBtn</tabstop>
<tabstop>moveUpBtn</tabstop>
<tabstop>moveDownBtn</tabstop>
<tabstop>removeBtn</tabstop>
<tabstop>customizeBtn</tabstop>
<tabstop>editBtn</tabstop>
<tabstop>revertBtn</tabstop>
<tabstop>forgeBtn</tabstop>
<tabstop>fabricBtn</tabstop>
<tabstop>liteloaderBtn</tabstop>
<tabstop>modBtn</tabstop>
<tabstop>jarmodBtn</tabstop>
<tabstop>jarBtn</tabstop>
<tabstop>addEmptyBtn</tabstop>
<tabstop>reloadBtn</tabstop>
<tabstop>downloadBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WorldListPage</class>
<widget class="QWidget" name="WorldListPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>723</width>
<height>532</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="addBtn">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="LineSeparator" name="separator" native="true"/>
</item>
<item>
<widget class="QPushButton" name="renameBtn">
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copyBtn">
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rmWorldBtn">
<property name="text">
<string>&amp;Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mcEditBtn">
<property name="text">
<string notr="true">MCEdit</string>
</property>
</widget>
</item>
<item>
<widget class="LineSeparator" name="separator_2" native="true"/>
</item>
<item>
<widget class="QPushButton" name="copySeedBtn">
<property name="text">
<string>Copy Seed</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshBtn">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="viewFolderBtn">
<property name="text">
<string>&amp;View Folder</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QTreeView" name="worldTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineSeparator</class>
<extends>QWidget</extends>
<header>widgets/LineSeparator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>worldTreeView</tabstop>
<tabstop>addBtn</tabstop>
<tabstop>renameBtn</tabstop>
<tabstop>copyBtn</tabstop>
<tabstop>rmWorldBtn</tabstop>
<tabstop>mcEditBtn</tabstop>
<tabstop>copySeedBtn</tabstop>
<tabstop>refreshBtn</tabstop>
<tabstop>viewFolderBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -1,26 +0,0 @@
#include "TechnicPage.h"
#include "ui_TechnicPage.h"
#include "MultiMC.h"
#include "dialogs/NewInstanceDialog.h"
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
{
ui->setupUi(this);
}
TechnicPage::~TechnicPage()
{
delete ui;
}
bool TechnicPage::shouldDisplay() const
{
return true;
}
void TechnicPage::openedImpl()
{
dialog->setSuggestedPack();
}

View File

@@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TechnicPage</class>
<widget class="QWidget" name="TechnicPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>546</width>
<height>405</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>40</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color:#ffc000</string>
</property>
<property name="text">
<string notr="true">UNDER</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string notr="true"/>
</property>
<property name="pixmap">
<pixmap resource="../../resources/assets/assets.qrc">:/assets/underconstruction</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>40</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color:#7ca32b</string>
</property>
<property name="text">
<string notr="true">CONSTRUCTION</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>
<include location="../../resources/assets/assets.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -1,60 +0,0 @@
#include "TwitchPage.h"
#include "ui_TwitchPage.h"
#include "MultiMC.h"
#include "dialogs/NewInstanceDialog.h"
#include <InstanceImportTask.h>
TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
{
ui->setupUi(this);
connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck);
}
TwitchPage::~TwitchPage()
{
delete ui;
}
bool TwitchPage::shouldDisplay() const
{
return true;
}
void TwitchPage::openedImpl()
{
dialog->setSuggestedPack();
}
void TwitchPage::triggerCheck(bool)
{
if(m_modIdResolver) {
return;
}
auto task = new Flame::UrlResolvingTask(ui->lineEdit->text());
connect(task, &Task::finished, this, &TwitchPage::checkDone);
m_modIdResolver.reset(task);
task->start();
}
void TwitchPage::setUrl(const QString& url)
{
ui->lineEdit->setText(url);
triggerCheck(true);
}
void TwitchPage::checkDone()
{
auto result = m_modIdResolver->getResults();
auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId);
if(result.resolved && result.type == Flame::File::Type::Modpack) {
ui->twitchLabel->setText(formatted);
QFileInfo fi(result.fileName);
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url));
} else {
ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch")));
dialog->setSuggestedPack();
}
m_modIdResolver.reset();
}

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TwitchPage</class>
<widget class="QWidget" name="TwitchPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>546</width>
<height>405</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Twitch URL:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="twitchLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>40</pointsize>
</font>
</property>
<property name="pixmap">
<pixmap resource="../../resources/assets/assets.qrc">:/assets/deadglitch</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="checkButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>lineEdit</tabstop>
<tabstop>checkButton</tabstop>
</tabstops>
<resources>
<include location="../../resources/assets/assets.qrc"/>
</resources>
<connections/>
</ui>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<path fill="#585858" d="M6.9,4.4v16h16v-16H6.9z M21.9,5.4l0,14h-14v-14H21.9z"/>
<rect fill="none" width="24" height="24"/>
<rect x="7.9" y="5.4" fill="#F2F2F2" width="14" height="14"/>
<polygon fill="#999999" points="7.9,7.4 7.9,6.9 8.9,6.9 8.9,7.9 9.9,7.9 9.9,7.4 10.9,7.4 10.9,8.9 11.9,8.9 11.9,6.4 12.9,6.4
12.9,7.4 13.9,7.4 13.9,6.9 14.9,6.9 14.9,8.9 15.9,8.9 15.9,7.4 16.9,7.4 16.9,8.4 17.9,8.4 17.9,7.4 18.9,7.4 18.9,6.9 19.9,6.9
19.9,7.4 21.4,7.4 21.4,6.9 21.9,6.9 21.9,7.4 22.9,7.4 22.9,4.4 6.9,4.4 6.9,7.4 "/>
<g>
<path fill="#585858" d="M14.9,13.6c-0.9,0.9-1.9,2-3.4,2c-1.5,0-2.6-1.1-2.6-2.7c0-1.4,1.2-2.7,2.8-2.7c1.3,0,2.4,1.1,3.2,2
c0.9-0.9,1.9-2,3.4-2c1.6,0,2.6,1.1,2.6,2.7c0,1.4-1.2,2.7-2.8,2.7C16.7,15.6,15.7,14.5,14.9,13.6z M14.1,12.9
c-0.7-0.7-1.5-1.6-2.5-1.6c-0.9,0-1.6,0.7-1.6,1.6c0,0.9,0.7,1.6,1.6,1.6C12.6,14.5,13.4,13.6,14.1,12.9z M19.7,12.9
c0-0.9-0.7-1.6-1.5-1.6c-1,0-1.9,0.9-2.6,1.6c0.7,0.7,1.5,1.6,2.5,1.6C19,14.5,19.7,13.7,19.7,12.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,7 +0,0 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/assets">
<file alias="underconstruction">underconstruction.png</file>
<file alias="deadglitch">deadglitch.svg</file>
</qresource>
</RCC>

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
class="tw-svg__asset tw-svg__asset--deadglitch tw-svg__asset--inherit"
width="92px"
height="96px"
version="1.1"
viewBox="0 0 30 30"
x="0px"
y="0px"
id="svg8"
sodipodi:docname="deadglitch.svg"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1353"
inkscape:window-height="828"
id="namedview10"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="44.285787"
inkscape:cy="52.833458"
inkscape:window-x="2958"
inkscape:window-y="702"
inkscape:window-maximized="0"
inkscape:current-layer="svg8" />
<g
id="g6"
style="fill:#898395;fill-opacity:1">
<path
d="M26,17.4589613 L26,3 L4,3 L4,22.0601057 L10.0032868,22.0601057 L10.0032868,26 L14.0004537,22.0601057 L21.3322933,22.0601057 L26,17.4589613 L26,17.4589613 Z M21.0896458,26.0850335 L15.1583403,26.0850335 L11.2051771,30 L7.24798611,30 L7.24798611,26.0850335 L0,26.0850335 L0,5.21746493 L1.97773958,0 L29,0 L29,18.2620736 L21.0896458,26.0850335 L21.0896458,26.0850335 Z"
id="path2"
style="fill:#898395;fill-opacity:1" />
<path
d="M20.8587626,12.1710126 L22.4052753,13.7175252 L23.7175252,12.4052753 L22.1710126,10.8587626 L23.7175252,9.31224999 L22.4052753,8 L20.8587626,9.54651264 L19.31225,8 L18,9.31224999 L19.5465126,10.8587626 L18,12.4052753 L19.31225,13.7175252 L20.8587626,12.1710126 Z M11.8587626,12.1710126 L13.4052753,13.7175252 L14.7175252,12.4052753 L13.1710126,10.8587626 L14.7175252,9.31224999 L13.4052753,8 L11.8587626,9.54651264 L10.31225,8 L9,9.31224999 L10.5465126,10.8587626 L9,12.4052753 L10.31225,13.7175252 L11.8587626,12.1710126 Z"
id="path4"
style="fill:#898395;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,353 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="68.26667"
height="68.26667"
id="svg4427"
version="1.1"
inkscape:version="0.92.1 r"
sodipodi:docname="multimc-smooth-biginfinity.svg"
inkscape:export-filename="/home/peterix/playground/MultiMC-icons/multimc-smooth-biginfinity.png"
inkscape:export-xdpi="180"
inkscape:export-ydpi="180">
<defs
id="defs4429">
<linearGradient
inkscape:collect="always"
id="linearGradient4809">
<stop
style="stop-color:#98c867;stop-opacity:1"
offset="0"
id="stop4805" />
<stop
style="stop-color:#5c9a33;stop-opacity:1"
offset="1"
id="stop4807" />
</linearGradient>
<linearGradient
id="linearGradient5668"
inkscape:collect="always">
<stop
id="stop5670"
offset="0"
style="stop-color:#75b54b;stop-opacity:1;" />
<stop
id="stop5672"
offset="1"
style="stop-color:#75b54b;stop-opacity:0.6" />
</linearGradient>
<linearGradient
id="linearGradient5084"
inkscape:collect="always">
<stop
id="stop5086"
offset="0"
style="stop-color:#000000;stop-opacity:0.8" />
<stop
id="stop5088"
offset="1"
style="stop-color:#000000;stop-opacity:0.35" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient5072"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient5082"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3281"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3283"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3286"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3288"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3290"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3293"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114"
gradientTransform="scale(1.2671525,0.89790119)" />
<linearGradient
inkscape:collect="always"
id="linearGradient5580">
<stop
style="stop-color:#000000;stop-opacity:0.0627451"
offset="0"
id="stop5576" />
<stop
style="stop-color:#322217;stop-opacity:0.58823532"
offset="1"
id="stop5578" />
</linearGradient>
<linearGradient
id="linearGradient3999"
inkscape:collect="always">
<stop
id="stop3995"
offset="0"
style="stop-color:#a3704b;stop-opacity:1" />
<stop
id="stop3997"
offset="1"
style="stop-color:#6a4a33;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient2727"
inkscape:collect="always">
<stop
id="stop2723"
offset="0"
style="stop-color:#966c4a;stop-opacity:1" />
<stop
id="stop2725"
offset="1"
style="stop-color:#593d29;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2727"
id="linearGradient2050"
gradientUnits="userSpaceOnUse"
x1="36.546478"
y1="33.80484"
x2="86.415741"
y2="97.065842" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3999"
id="radialGradient2052"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-9.105292e-4,-0.00104444)"
cx="34.133331"
cy="34.133335"
fx="34.133331"
fy="34.133335"
r="29.866665" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5580"
id="linearGradient2140"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.0010513,-9.083059e-4)"
x1="29.866674"
y1="29.867579"
x2="38.400005"
y2="38.400913" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient4790"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4809"
id="radialGradient4803"
cx="-42.66758"
cy="-34.134373"
fx="-42.66758"
fy="-34.134373"
r="34.132812"
gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.6203867"
inkscape:cx="52.171166"
inkscape:cy="11.292073"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1368"
inkscape:window-height="905"
inkscape:window-x="2452"
inkscape:window-y="723"
inkscape:window-maximized="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:snap-bbox-edge-midpoints="false"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-midpoints="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="false"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true">
<inkscape:grid
type="xygrid"
id="grid4446"
empspacing="16"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="4.2666667"
spacingy="4.2666667"
originx="0"
originy="0" />
</sodipodi:namedview>
<metadata
id="metadata4432">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g2048"
transform="translate(9.113e-4,0.00104183)">
<rect
rx="8.5333338"
ry="8.5333338"
style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756"
id="rect2026"
width="68.26667"
height="68.26667"
x="-1.3322676e-15"
y="3.0270508e-06" />
<rect
rx="4.2666626"
y="4.2656283"
x="4.2657552"
height="59.733334"
width="59.73333"
id="rect2028"
style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:14.93333435"
ry="4.2666669" />
<path
inkscape:connector-curvature="0"
id="path4811"
d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z"
style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"
sodipodi:nodetypes="ccccccccccccccccccccccccccc" />
<path
style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756"
d="m 8.5329442,-0.0018207 c -4.7274675,0 -8.5332035,3.805736 -8.5332035,8.533203 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 c 0,-4.727467 -3.805737,-8.533203 -8.533203,-8.533203 z m -3.019531,5.513671 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z"
id="path4794"
inkscape:connector-curvature="0" />
<path
style="opacity:1;fill:url(#linearGradient2140);fill-opacity:1;stroke:none;stroke-width:17.06666756"
d="m 8.5322887,-9.083059e-4 c -4.72747,0 -8.5332,3.8057359059 -8.5332,8.5332029059 V 59.731515 c 0,4.727467 3.80573,8.535156 8.5332,8.535156 H 59.731502 c 4.72747,0 8.5332,-3.807689 8.5332,-8.535156 V 8.5322946 c 0,-4.727467 -3.80573,-8.5332029059 -8.5332,-8.5332029059 z m 0,4.2675779059 H 59.731502 c 2.36373,0 4.26758,1.901892 4.26758,4.265625 V 59.731515 c 0,2.363733 -1.90385,4.267578 -4.26758,4.267578 H 8.5322887 c -2.36373,0 -4.26758,-1.903845 -4.26758,-4.267578 V 8.5322946 c 0,-2.363733 1.90385,-4.265625 4.26758,-4.265625 z"
id="path2046"
inkscape:connector-curvature="0" />
</g>
<g
id="g1092">
<path
inkscape:connector-curvature="0"
id="path4786"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251" />
<path
d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
id="path3279"
inkscape:connector-curvature="0" />
<path
d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672"
id="path3272"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc"
inkscape:connector-curvature="0"
id="text5100"
d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z"
style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
<path
sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc"
id="text5058-0"
d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z"
style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,353 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="68.26667"
height="68.26667"
id="svg4427"
version="1.1"
inkscape:version="0.92.1 r"
sodipodi:docname="multimc-smooth-biginfinity.svg"
inkscape:export-filename="/home/peterix/playground/MultiMC-icons/multimc-smooth-biginfinity.png"
inkscape:export-xdpi="180"
inkscape:export-ydpi="180">
<defs
id="defs4429">
<linearGradient
inkscape:collect="always"
id="linearGradient4809">
<stop
style="stop-color:#98c867;stop-opacity:1"
offset="0"
id="stop4805" />
<stop
style="stop-color:#5c9a33;stop-opacity:1"
offset="1"
id="stop4807" />
</linearGradient>
<linearGradient
id="linearGradient5668"
inkscape:collect="always">
<stop
id="stop5670"
offset="0"
style="stop-color:#75b54b;stop-opacity:1;" />
<stop
id="stop5672"
offset="1"
style="stop-color:#75b54b;stop-opacity:0.6" />
</linearGradient>
<linearGradient
id="linearGradient5084"
inkscape:collect="always">
<stop
id="stop5086"
offset="0"
style="stop-color:#000000;stop-opacity:0.8" />
<stop
id="stop5088"
offset="1"
style="stop-color:#000000;stop-opacity:0.35" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient5072"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient5082"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3281"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3283"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.01532073,-0.00938002)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5668"
id="linearGradient3286"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
y2="61.773685" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3288"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3290"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient3293"
gradientUnits="userSpaceOnUse"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114"
gradientTransform="scale(1.2671525,0.89790119)" />
<linearGradient
inkscape:collect="always"
id="linearGradient5580">
<stop
style="stop-color:#000000;stop-opacity:0.0627451"
offset="0"
id="stop5576" />
<stop
style="stop-color:#322217;stop-opacity:0.58823532"
offset="1"
id="stop5578" />
</linearGradient>
<linearGradient
id="linearGradient3999"
inkscape:collect="always">
<stop
id="stop3995"
offset="0"
style="stop-color:#a3704b;stop-opacity:1" />
<stop
id="stop3997"
offset="1"
style="stop-color:#6a4a33;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient2727"
inkscape:collect="always">
<stop
id="stop2723"
offset="0"
style="stop-color:#966c4a;stop-opacity:1" />
<stop
id="stop2725"
offset="1"
style="stop-color:#593d29;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2727"
id="linearGradient2050"
gradientUnits="userSpaceOnUse"
x1="36.546478"
y1="33.80484"
x2="86.415741"
y2="97.065842" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3999"
id="radialGradient2052"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-9.105292e-4,-0.00104444)"
cx="34.133331"
cy="34.133335"
fx="34.133331"
fy="34.133335"
r="29.866665" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5580"
id="linearGradient2140"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-0.0010513,-9.083059e-4)"
x1="29.866674"
y1="29.867579"
x2="38.400005"
y2="38.400913" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5084"
id="linearGradient4790"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)"
x1="14.312115"
y1="9.7948904"
x2="44.097023"
y2="82.973114" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4809"
id="radialGradient4803"
cx="-42.66758"
cy="-34.134373"
fx="-42.66758"
fy="-34.134373"
r="34.132812"
gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.6203867"
inkscape:cx="52.171166"
inkscape:cy="11.292073"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1368"
inkscape:window-height="905"
inkscape:window-x="2452"
inkscape:window-y="723"
inkscape:window-maximized="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:snap-bbox-edge-midpoints="false"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-midpoints="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="false"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true">
<inkscape:grid
type="xygrid"
id="grid4446"
empspacing="16"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="4.2666667"
spacingy="4.2666667"
originx="0"
originy="0" />
</sodipodi:namedview>
<metadata
id="metadata4432">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g2048"
transform="translate(9.113e-4,0.00104183)">
<rect
rx="8.5333338"
ry="8.5333338"
style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756"
id="rect2026"
width="68.26667"
height="68.26667"
x="-1.3322676e-15"
y="3.0270508e-06" />
<rect
rx="4.2666626"
y="4.2656283"
x="4.2657552"
height="59.733334"
width="59.73333"
id="rect2028"
style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:14.93333435"
ry="4.2666669" />
<path
inkscape:connector-curvature="0"
id="path4811"
d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z"
style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"
sodipodi:nodetypes="ccccccccccccccccccccccccccc" />
<path
style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756"
d="m 8.5329442,-0.0018207 c -4.7274675,0 -8.5332035,3.805736 -8.5332035,8.533203 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 c 0,-4.727467 -3.805737,-8.533203 -8.533203,-8.533203 z m -3.019531,5.513671 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z"
id="path4794"
inkscape:connector-curvature="0" />
<path
style="opacity:1;fill:url(#linearGradient2140);fill-opacity:1;stroke:none;stroke-width:17.06666756"
d="m 8.5322887,-9.083059e-4 c -4.72747,0 -8.5332,3.8057359059 -8.5332,8.5332029059 V 59.731515 c 0,4.727467 3.80573,8.535156 8.5332,8.535156 H 59.731502 c 4.72747,0 8.5332,-3.807689 8.5332,-8.535156 V 8.5322946 c 0,-4.727467 -3.80573,-8.5332029059 -8.5332,-8.5332029059 z m 0,4.2675779059 H 59.731502 c 2.36373,0 4.26758,1.901892 4.26758,4.265625 V 59.731515 c 0,2.363733 -1.90385,4.267578 -4.26758,4.267578 H 8.5322887 c -2.36373,0 -4.26758,-1.903845 -4.26758,-4.267578 V 8.5322946 c 0,-2.363733 1.90385,-4.265625 4.26758,-4.265625 z"
id="path2046"
inkscape:connector-curvature="0" />
</g>
<g
id="g1092">
<path
inkscape:connector-curvature="0"
id="path4786"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251" />
<path
d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
id="path3279"
inkscape:connector-curvature="0" />
<path
d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672"
id="path3272"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc"
inkscape:connector-curvature="0"
id="text5100"
d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z"
style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
<path
sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc"
id="text5058-0"
d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z"
style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2479" viewBox="0 0 1464.248 1452.156"><path fill="#1389D2" d="M644.592 5.192c159.049-19.094 324.188 14.764 461.941 96.756 143.299 84.188 256.41 218.769 313.509 374.945 62.353 168.133 58.63 359.689-10.432 525.205-63.691 154.529-182.775 285.021-330.227 363.417-149.71 80.709-327.849 105.845-494.333 71.979-172.465-34.279-331.022-133.533-437.232-273.729C51.059 1037.725-2.015 878.924.058 719.941c.548-167.949 63.08-334.559 172.343-461.998C290.451 118.177 462.794 25.934 644.592 5.192m-120.67 181.494c-131.408 49.538-243.479 148.488-308.511 273.003-60.641 114.509-80.038 249.94-54.906 377.018 25.076 130.193 97.609 250.192 200.162 334.012 101.76 84.247 232.924 131.896 365.063 132.754-.795-37.831-.366-75.591-1.04-113.416-.792-31.415-30.866-53.744-59.722-58.563-.917 28.305.119 62.895-24.771 82.113-23.731 16.288-55.638 15.919-82.479 8.782-27.149-7.626-42.216-35.808-43.011-62.532-1.647-40.565-.244-81.196-.729-121.828-10.86-.059-21.781-.059-32.702-.059-.06-35.442-.06-70.892 0-106.273 10.92-.065 21.84-.125 32.762-.125 0-12.076 0-24.093-.06-36.115-14.033-4.635-30.263-10.188-35.934-25.379-14.033-33.186 3.417-70.462-10.188-103.891-13.911-35.203-27.759-74.125-14.825-111.888 7.992-26.598 34.042-43.56 61.007-45.694-.06-76.808 0-153.553-.06-230.358-.429-40.754 16.288-82.725 48.924-108.164 37.215-29.525 86.57-37.092 132.812-36.786 102.921.061 203.701 48.864 270.2 126.893-.124 18.362 0 36.727-.124 55.149-92.185.061-184.359-.063-276.54 0-10.006-1.828-19.523 7.505-17.754 17.569.304 58.812-.549 117.682-.308 176.492 17.021.061 34.104.304 51.125-.488 2.866-11.774 6.038-23.425 9.454-35.019 74.982 18.424 149.893 36.789 224.809 55.335-2.502 8.6-4.938 17.266-7.382 25.927-6.588 2.5-13.177 5.001-19.767 7.504-2.621 203.154-4.331 406.363-6.648 609.514 176.251-60.091 319.795-208.828 369.755-388.73 39.415-137.021 25.262-288.622-39.527-415.699-63.628-126.646-175.579-227.98-307.72-279.104-133.783-52.65-287.092-53.321-421.365-1.954m140.741 710.665c0 11.401 0 22.817.063 34.221 10.125.06 20.253.06 30.381.06.061 35.448.061 70.896 0 106.34-10.128 0-20.315 0-30.442.059v32.333c19.949.06 39.961.06 59.909-.061-.917-57.646-.365-115.305-1.283-172.95a9391.232 9391.232 0 0 0-58.628-.002z"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="Layer_1"
data-name="Layer 1"
viewBox="0 0 134 134"
version="1.1"
sodipodi:docname="twitch.svg"
inkscape:version="0.92.2 2405546, 2018-03-11"
width="134"
height="134">
<metadata
id="metadata13">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Glitch</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1414"
inkscape:window-height="944"
id="namedview11"
showgrid="false"
inkscape:zoom="1.761194"
inkscape:cx="-109.17797"
inkscape:cy="66.999998"
inkscape:window-x="2640"
inkscape:window-y="554"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" />
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#6441a4;fill-rule:evenodd;}</style>
</defs>
<title
id="title6">Glitch</title>
<path
class="cls-1"
d="M 9,0 0,23 v 94 h 32 v 17 H 50 L 67,117 H 93 L 128,82 V 0 Z M 116,76 96,96 H 64 L 47,113 V 96 H 20 V 12 h 96 z M 96,35 V 70 H 84 V 35 Z M 64,35 V 70 H 52 V 35 Z"
id="path8"
style="fill:#6441a4;fill-opacity:1;fill-rule:evenodd"
inkscape:connector-curvature="0" />
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<rect fill="#754C24" width="32" height="32"/>
<polygon fill="#39B54A" points="0,5 1,5 1,4 4,4 4,5 8,5 8,8 9,8 9,2 12,2 12,5 13,5 13,4 16,4 16,8 17,8 17,6 18,6 18,5 20,5 20,8
21,8 21,6 22,6 22,5 24,5 24,4 26,4 26,5 29,5 29,4 32,4 32,0 0,0 "/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-963.8778" y1="47.5718" x2="-963.0319" y2="49.6501" gradientTransform="matrix(20.79 0 0 14.7315 20048.0879 -696.8257)">
<stop offset="0" style="stop-color:#000000;stop-opacity:0.8"/>
<stop offset="1" style="stop-color:#000000;stop-opacity:0.35"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M18.5,21.5c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1c-2,0-3.6-0.5-4.9-1.6
c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5c0.9,0.3,1.7,0.8,2.3,1.4
c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6c1.4,1.1,2.1,2.6,2.1,4.4
c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5S19.2,22.1,18.5,21.5 M9.6,21.3c2.4,0,4.3-1.2,5.8-3.7
c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1c-0.8,0.8-1.2,1.7-1.2,2.8c0,1.2,0.4,2.1,1.2,2.8C7.1,20.9,8.2,21.3,9.6,21.3
M23.9,13.5c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1c1.5,0,2.6-0.4,3.3-1.1c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8
C26.3,13.8,25.2,13.5,23.9,13.5"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-964.0289" y1="48.0195" x2="-962.7877" y2="48.9627" gradientTransform="matrix(20.79 0 0 14.7315 20047.7695 -696.964)">
<stop offset="0" style="stop-color:#75B54B"/>
<stop offset="1" style="stop-color:#75B54B;stop-opacity:0.6"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M17.6,20.1c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1c-2,0-3.6-0.5-4.9-1.6
c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5c0.9,0.3,1.7,0.8,2.3,1.4
c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6c1.4,1.1,2.1,2.6,2.1,4.4
c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5C19.2,21.2,18.4,20.8,17.6,20.1 M8.7,19.9c2.4,0,4.3-1.2,5.8-3.7
c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1C4.6,14,4.2,14.9,4.2,16c0,1.2,0.4,2.1,1.2,2.8C6.2,19.5,7.3,19.9,8.7,19.9
M23,12.1c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1c1.5,0,2.6-0.4,3.3-1.1c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8
C25.5,12.5,24.4,12.1,23,12.1"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="-463 265 32 32" enable-background="new -463 265 32 32" xml:space="preserve">
<rect x="-463" y="265" fill="#754C24" width="32" height="32"/>
<polygon fill="#39B54A" points="-463,270 -462,270 -462,269 -459,269 -459,270 -455,270 -455,273 -454,273 -454,267 -451,267
-451,270 -450,270 -450,269 -447,269 -447,273 -446,273 -446,271 -445,271 -445,270 -443,270 -443,273 -442,273 -442,271 -441,271
-441,270 -439,270 -439,269 -437,269 -437,270 -434,270 -434,269 -431,269 -431,265 -463,265 "/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1426.8778" y1="312.5718" x2="-1426.032" y2="314.6501" gradientTransform="matrix(20.79 0 0 14.7315 29210.8574 -4335.6729)">
<stop offset="0" style="stop-color:#000000;stop-opacity:0.8"/>
<stop offset="1" style="stop-color:#000000;stop-opacity:0.35"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M-444.5,286.5c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1
c-2,0-3.6-0.5-4.9-1.6c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5
c0.9,0.3,1.7,0.8,2.3,1.4c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6
c1.4,1.1,2.1,2.6,2.1,4.4c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5S-443.8,287.1-444.5,286.5 M-453.4,286.3
c2.4,0,4.3-1.2,5.8-3.7c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1c-0.8,0.8-1.2,1.7-1.2,2.8c0,1.2,0.4,2.1,1.2,2.8
C-455.9,285.9-454.8,286.3-453.4,286.3 M-439.1,278.5c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1c1.5,0,2.6-0.4,3.3-1.1
c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8C-436.7,278.8-437.8,278.5-439.1,278.5"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-1427.0289" y1="313.0195" x2="-1425.7877" y2="313.9627" gradientTransform="matrix(20.79 0 0 14.7315 29210.5391 -4335.8115)">
<stop offset="0" style="stop-color:#75B54B"/>
<stop offset="1" style="stop-color:#75B54B;stop-opacity:0.6"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M-445.4,285.1c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1
c-2,0-3.6-0.5-4.9-1.6c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5
c0.9,0.3,1.7,0.8,2.3,1.4c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6
c1.4,1.1,2.1,2.6,2.1,4.4c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5C-443.8,286.2-444.6,285.8-445.4,285.1
M-454.3,284.9c2.4,0,4.3-1.2,5.8-3.7c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1c-0.8,0.8-1.2,1.7-1.2,2.8
c0,1.2,0.4,2.1,1.2,2.8C-456.8,284.5-455.7,284.9-454.3,284.9 M-440,277.1c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1
c1.5,0,2.6-0.4,3.3-1.1c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8C-437.5,277.5-438.6,277.1-440,277.1"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<rect fill="#666666" width="32" height="32"/>
<polygon fill="#999999" points="0,5 1,5 1,4 4,4 4,5 8,5 8,8 9,8 9,2 12,2 12,5 13,5 13,4 16,4 16,8 17,8 17,6 18,6 18,5 20,5 20,8
21,8 21,6 22,6 22,5 24,5 24,4 26,4 26,5 29,5 29,4 32,4 32,0 0,0 "/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-963.8778" y1="47.5718" x2="-963.0319" y2="49.6501" gradientTransform="matrix(20.79 0 0 14.7315 20048.0879 -696.8257)">
<stop offset="0" style="stop-color:#000000;stop-opacity:0.8"/>
<stop offset="1" style="stop-color:#000000;stop-opacity:0.35"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M18.5,21.5c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1c-2,0-3.6-0.5-4.9-1.6
c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5c0.9,0.3,1.7,0.8,2.3,1.4
c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6c1.4,1.1,2.1,2.6,2.1,4.4
c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5S19.2,22.1,18.5,21.5 M9.6,21.3c2.4,0,4.3-1.2,5.8-3.7
c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1c-0.8,0.8-1.2,1.7-1.2,2.8c0,1.2,0.4,2.1,1.2,2.8C7.1,20.9,8.2,21.3,9.6,21.3
M23.9,13.5c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1c1.5,0,2.6-0.4,3.3-1.1c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8
C26.3,13.8,25.2,13.5,23.9,13.5"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-964.0289" y1="48.0195" x2="-962.7877" y2="48.9627" gradientTransform="matrix(20.79 0 0 14.7315 20047.7695 -696.964)">
<stop offset="0" style="stop-color:#999999"/>
<stop offset="1" style="stop-color:#999999;stop-opacity:0.6"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M17.6,20.1c-0.5-0.4-1-1-1.8-1.9c-1,1.2-1.9,2.1-2.8,2.7c-1.1,0.7-2.5,1-4.1,1c-2,0-3.6-0.5-4.9-1.6
c-1.4-1.1-2.1-2.6-2.1-4.4c0-1.7,0.7-3.2,2.1-4.4c1.2-1,2.9-1.6,5-1.6c1.1,0,2,0.2,2.8,0.5c0.9,0.3,1.7,0.8,2.3,1.4
c0.6,0.5,1.2,1.2,1.8,1.9c1-1.2,1.9-2.1,2.8-2.7c1.1-0.7,2.5-1,4.1-1c2,0,3.6,0.5,4.9,1.6c1.4,1.1,2.1,2.6,2.1,4.4
c0,1.7-0.7,3.2-2.1,4.4c-1.2,1-2.9,1.6-5,1.6c-1.1,0-2-0.2-2.8-0.5C19.2,21.2,18.4,20.8,17.6,20.1 M8.7,19.9c2.4,0,4.3-1.2,5.8-3.7
c-1.9-2.7-3.8-4.1-5.8-4.1c-1.5,0-2.6,0.4-3.3,1.1C4.6,14,4.2,14.9,4.2,16c0,1.2,0.4,2.1,1.2,2.8C6.2,19.5,7.3,19.9,8.7,19.9
M23,12.1c-2.2,0-4.1,1.2-5.8,3.7c1.8,2.7,3.8,4.1,5.8,4.1c1.5,0,2.6-0.4,3.3-1.1c0.8-0.8,1.2-1.7,1.2-2.8c0-1.2-0.4-2.1-1.2-2.8
C25.5,12.5,24.4,12.1,23,12.1"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Some files were not shown because too many files have changed in this diff Show More