mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 17:14:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b852e30285 | ||
|
|
7ea571f593 | ||
|
|
7a4ceebf5e | ||
|
|
31efcfc3b2 | ||
|
|
49cb893d12 | ||
|
|
009d4dee23 | ||
|
|
3e278acb71 | ||
|
|
d0d31d9575 | ||
|
|
ef492407ef | ||
|
|
cd417c6786 | ||
|
|
89e4e483c2 | ||
|
|
fd36f50d82 | ||
|
|
1d71c017f4 | ||
|
|
99c681ad87 | ||
|
|
57f7f1d7ab | ||
|
|
b2550b8a69 | ||
|
|
a30f173627 | ||
|
|
2aaaeba15e | ||
|
|
9c96a0a10a | ||
|
|
f5c38e2359 | ||
|
|
e544adba5c | ||
|
|
58f7487cfa | ||
|
|
9f928963f2 | ||
|
|
38cec7a11f | ||
|
|
7ab3c3a37c | ||
|
|
53127eec30 | ||
|
|
30d532f4fc | ||
|
|
01fb451b69 | ||
|
|
3714426e19 | ||
|
|
c2b919d339 | ||
|
|
c4b8a393f9 | ||
|
|
30894f7afa | ||
|
|
e9a7660cb8 | ||
|
|
4d46abf761 | ||
|
|
23baf3a8ce | ||
|
|
c6a21d0d62 | ||
|
|
da72640c7e | ||
|
|
b7f34629c3 | ||
|
|
5500d69b77 | ||
|
|
6af42bc656 | ||
|
|
e19fcb8844 | ||
|
|
0a62333890 | ||
|
|
e7512f13e4 | ||
|
|
4708d77c98 | ||
|
|
9b1d2bdd13 | ||
|
|
a6dc03aa7e | ||
|
|
6cd8349542 | ||
|
|
976c589c78 | ||
|
|
24ec338a51 | ||
|
|
4f634855ac | ||
|
|
9f01e3407e | ||
|
|
adeaeb74aa | ||
|
|
e13c3966e0 | ||
|
|
02c3b4e74b | ||
|
|
89a07d3668 | ||
|
|
b52eb6d699 | ||
|
|
3281faf2be | ||
|
|
3d25b4e793 | ||
|
|
f4532410ec | ||
|
|
8f87021b0f | ||
|
|
bc57ff9e83 | ||
|
|
de8ab934d8 | ||
|
|
81fe1c9682 | ||
|
|
58a299aabc | ||
|
|
243a21a353 | ||
|
|
f0dc530b28 | ||
|
|
dac7f590ea | ||
|
|
bf54d4d667 | ||
|
|
da4337efbf | ||
|
|
f92962bbbb | ||
|
|
2714da1022 | ||
|
|
96be82343c | ||
|
|
58577775d2 | ||
|
|
fc3e7647d2 | ||
|
|
a381874bb9 | ||
|
|
55525f6d2e | ||
|
|
bc64ceabef | ||
|
|
63ba3aa009 | ||
|
|
5265e34a35 | ||
|
|
35ab9f52b0 | ||
|
|
9b55f1f48f | ||
|
|
e356f4d713 | ||
|
|
bfc25bc5da | ||
|
|
9f0f9e7288 | ||
|
|
ce6404fd5b | ||
|
|
8f18a3de9a | ||
|
|
9815d8c379 | ||
|
|
1d902df581 | ||
|
|
8e26c99de3 | ||
|
|
fc8c3bef03 | ||
|
|
a4de22c1ad | ||
|
|
70cffe6982 | ||
|
|
39620f1e41 | ||
|
|
0e80fcb8f7 | ||
|
|
4a94c2c71b | ||
|
|
79c39f8b7c | ||
|
|
f6190e8752 | ||
|
|
0950b9fc84 | ||
|
|
25981706ef | ||
|
|
82d896a35e | ||
|
|
e7dd728f95 | ||
|
|
8f2e675581 | ||
|
|
ff75416a32 | ||
|
|
95afc95b24 | ||
|
|
34493b0d99 | ||
|
|
e77d2547ad | ||
|
|
40a34af431 | ||
|
|
352117fd78 | ||
|
|
81328b6684 | ||
|
|
b5af446d71 | ||
|
|
d0c6e92900 | ||
|
|
2a9c485edc | ||
|
|
a13598b17e | ||
|
|
40b54013b4 | ||
|
|
1c9a853868 | ||
|
|
da70cdd35a | ||
|
|
b993cf4a98 | ||
|
|
ac065ff5f7 | ||
|
|
913f6f6d36 | ||
|
|
9468e41bbf | ||
|
|
ffdd056a18 | ||
|
|
f157e7a6ec | ||
|
|
4a9d0f9bd5 | ||
|
|
0398916666 | ||
|
|
dec9a3924d | ||
|
|
2470120680 | ||
|
|
ba90b61607 | ||
|
|
9a5e24e338 | ||
|
|
29ef3d5ab4 | ||
|
|
f46a538b41 | ||
|
|
182aa9c0cb | ||
|
|
1857064afa | ||
|
|
cb05c8c4aa | ||
|
|
f275a24b43 | ||
|
|
1d84e8063e | ||
|
|
57befa89bb | ||
|
|
10b16d32da | ||
|
|
3f21a2c1b4 | ||
|
|
b2958c9066 | ||
|
|
9520978b81 | ||
|
|
4981aa3421 | ||
|
|
c48b48ee15 | ||
|
|
9947ba8503 | ||
|
|
a3f48734ff | ||
|
|
0378355c37 | ||
|
|
c625ef3009 | ||
|
|
d4b30422b6 | ||
|
|
6de36d78f5 | ||
|
|
7348929819 | ||
|
|
b9f4b02cb0 | ||
|
|
20e93a412e | ||
|
|
e44e8b3112 | ||
|
|
bab4d87ce7 | ||
|
|
bc9d1fe657 | ||
|
|
d4d522c597 | ||
|
|
97269a5e3d | ||
|
|
c913258ca3 | ||
|
|
c7ab872c85 | ||
|
|
e91431fb70 | ||
|
|
dfa3a2665d | ||
|
|
4fa375683f | ||
|
|
0db4624b27 | ||
|
|
c997f2adbc | ||
|
|
26e5f4b4ff | ||
|
|
2336002f20 | ||
|
|
e88a0cd13f | ||
|
|
dbf5fe38ce | ||
|
|
c47d08c89d | ||
|
|
7af5c21d74 | ||
|
|
6f96d89e2b | ||
|
|
111b0216fd | ||
|
|
1d0384f8f6 | ||
|
|
adc8972277 | ||
|
|
5990e29af7 | ||
|
|
2676c8999c | ||
|
|
3a200e901e | ||
|
|
c97ac284a5 | ||
|
|
54e06e841d | ||
|
|
7ce84491d1 | ||
|
|
532bee7ce1 | ||
|
|
e24f949517 | ||
|
|
4545beb49d | ||
|
|
3f4bf61ae6 | ||
|
|
155e846818 | ||
|
|
ba3707af22 | ||
|
|
ad79b9ed86 | ||
|
|
5f7df5e742 | ||
|
|
7aa7221756 | ||
|
|
8844554da6 | ||
|
|
6f0eedc9a6 | ||
|
|
f21cd286ca | ||
|
|
f1b533ad87 | ||
|
|
5a97d183df | ||
|
|
fbd57b3264 | ||
|
|
efc369ee83 | ||
|
|
91ecf71109 | ||
|
|
9b6525e805 | ||
|
|
dc7d02ebf6 | ||
|
|
7037184a4e | ||
|
|
bf38d94756 | ||
|
|
3d17434510 | ||
|
|
29d6c875d0 | ||
|
|
902772ed0c | ||
|
|
1229c4c4ea | ||
|
|
7fecf2ad6b | ||
|
|
c418da353f | ||
|
|
110cf372eb | ||
|
|
7a004087f7 | ||
|
|
70cfe24e67 | ||
|
|
8960f6869f | ||
|
|
71a92de5d9 | ||
|
|
3f15cd54c2 | ||
|
|
12c928c538 | ||
|
|
7406b38d8a | ||
|
|
ef646d3146 | ||
|
|
18371bd8c9 | ||
|
|
e35e4b16ff | ||
|
|
3711da9c45 | ||
|
|
6a318ddb78 | ||
|
|
2766f9a293 | ||
|
|
2e3f2461fa | ||
|
|
95f7345ff7 | ||
|
|
bc02f3e67b | ||
|
|
8db42c44bf | ||
|
|
404e6f1cc0 | ||
|
|
5741f669d5 | ||
|
|
6a67149b16 | ||
|
|
c8d934bafd | ||
|
|
075e240303 | ||
|
|
d00665ee65 | ||
|
|
ebb74a5f30 | ||
|
|
5b0117ec76 | ||
|
|
e06d9d0fbc | ||
|
|
ab91647546 | ||
|
|
b236a4a4db | ||
|
|
a480a83a98 | ||
|
|
3a9f123a16 | ||
|
|
8d27fb0692 | ||
|
|
d369fe1c25 | ||
|
|
98b71f2dd4 | ||
|
|
fef0f6422d | ||
|
|
2eb81ac88b | ||
|
|
e02a91f008 | ||
|
|
ced1b95155 | ||
|
|
8f910dfa5d | ||
|
|
05a854cd2a | ||
|
|
22738de028 | ||
|
|
cd0466395a | ||
|
|
24c4056651 | ||
|
|
59fe905695 | ||
|
|
6f3da97365 | ||
|
|
edc1fb24e2 | ||
|
|
f1d68ed4c8 | ||
|
|
ac97370f94 | ||
|
|
82e9de8641 | ||
|
|
843a13c1ab | ||
|
|
3bf50c4bbe | ||
|
|
4922e90d81 | ||
|
|
a3a920437f | ||
|
|
787c0c4a6a | ||
|
|
8517a824e5 | ||
|
|
2cca7aa759 | ||
|
|
23b070c7d0 | ||
|
|
49111e9c5c | ||
|
|
c0cca7f41d | ||
|
|
99bb50d8f8 | ||
|
|
00f00b17bf | ||
|
|
62fdbad74c | ||
|
|
f0bba74a47 | ||
|
|
cf9b5b445b | ||
|
|
aa200923f0 | ||
|
|
ddadf93ca1 | ||
|
|
781c681216 | ||
|
|
cfda7fc738 | ||
|
|
58ba24add2 | ||
|
|
dcf069ca52 | ||
|
|
c3fda11d10 | ||
|
|
11ac46daeb | ||
|
|
2dcabe0123 | ||
|
|
42f1d87955 | ||
|
|
334a76de75 | ||
|
|
677a6e578e | ||
|
|
a3b5eaacfd | ||
|
|
b3d4ea05ec | ||
|
|
8f35adca7a | ||
|
|
394f424185 | ||
|
|
ada52078c7 | ||
|
|
0daf1a6df4 | ||
|
|
edffd04ed2 | ||
|
|
7aea09f4ca | ||
|
|
5673c1dd29 | ||
|
|
1774e2bf30 | ||
|
|
4836b3272a | ||
|
|
a31607ae7d | ||
|
|
57ad221cc6 | ||
|
|
00c7883088 | ||
|
|
1de89525cc | ||
|
|
704a70902f | ||
|
|
570faeb790 | ||
|
|
8b8837c9ff | ||
|
|
eff464cf0e | ||
|
|
26ccb9106e | ||
|
|
0d84487412 | ||
|
|
2bc6a0666f | ||
|
|
82bc9f1b35 | ||
|
|
d73ac867df | ||
|
|
ede93d438f | ||
|
|
5e1949eb0e | ||
|
|
2d368c4c2e | ||
|
|
d9db27275e | ||
|
|
591a25722b | ||
|
|
d1ff1967b5 | ||
|
|
012a755bb9 | ||
|
|
748eece456 | ||
|
|
f709ce7579 | ||
|
|
33efc35b60 | ||
|
|
687d3c059d | ||
|
|
aaf02ea2ac | ||
|
|
625da6492d | ||
|
|
a573faf92d | ||
|
|
9565529baf | ||
|
|
3c346247e1 | ||
|
|
ce949e6099 | ||
|
|
5e3af8ac0e | ||
|
|
5cc90f060c | ||
|
|
577b19c1cf | ||
|
|
8905237306 | ||
|
|
c5d5f4e516 | ||
|
|
24022ab6ba | ||
|
|
6370c242f0 | ||
|
|
4762d4d8ef | ||
|
|
8f2dbf7046 | ||
|
|
23ea835fa7 | ||
|
|
d88db6c0cf | ||
|
|
c6dab815f4 | ||
|
|
7b722bfcd6 | ||
|
|
a90504315e | ||
|
|
3765ceb02d | ||
|
|
dab22f5b74 | ||
|
|
b301e8e4c1 | ||
|
|
f647929993 | ||
|
|
984291cee9 | ||
|
|
ac16845fc4 | ||
|
|
29edd242cd | ||
|
|
ca2d8bd834 | ||
|
|
2a779ec328 | ||
|
|
591e65fa1a | ||
|
|
485db6be2e | ||
|
|
25f3fcad46 | ||
|
|
6bf7c9878c | ||
|
|
95d0ff18cd | ||
|
|
1642bad402 | ||
|
|
450dd8313c | ||
|
|
ebdb166ec8 | ||
|
|
1eba1dfdc0 | ||
|
|
368a2245ef | ||
|
|
58ee06ea8e | ||
|
|
62f5adf8e2 | ||
|
|
fe1623a36a | ||
|
|
889efc1c0a | ||
|
|
0419b9d975 | ||
|
|
f1c6bdd57d | ||
|
|
b3e854a15d | ||
|
|
80a40c03e6 | ||
|
|
5bfaf7c170 | ||
|
|
2d98835db8 | ||
|
|
81c6421f84 | ||
|
|
6eeae8c49f | ||
|
|
c42a210140 | ||
|
|
a723aa6860 | ||
|
|
e9ff699c2d | ||
|
|
abe4f73a9a | ||
|
|
74fffc6fdc | ||
|
|
debbdc00be | ||
|
|
aed2cc446a | ||
|
|
e74087ee43 | ||
|
|
f329084f1f | ||
|
|
33cd9ad0fe | ||
|
|
ce819ac143 | ||
|
|
f0495a8abc | ||
|
|
a696db5a21 | ||
|
|
fcdbb4f5b3 | ||
|
|
e105337ff4 | ||
|
|
f52e2112dd | ||
|
|
945d927fa0 | ||
|
|
2ee6946824 | ||
|
|
e08c43366a | ||
|
|
c508004429 | ||
|
|
21af85323a | ||
|
|
3410242f03 | ||
|
|
9b9c46d93b | ||
|
|
de8e76c7fd | ||
|
|
47b5e99b6f | ||
|
|
1a465b33f5 | ||
|
|
8e7956397d | ||
|
|
86a9c7d359 | ||
|
|
6a581803a9 | ||
|
|
b4e0c709c5 | ||
|
|
f3458456a2 | ||
|
|
dcb15a587c | ||
|
|
937e9a0169 | ||
|
|
decd230ac1 | ||
|
|
5347caa1c4 | ||
|
|
52f6a674ea | ||
|
|
a16c61f7ba | ||
|
|
67f12d6dec | ||
|
|
bad55cacfa | ||
|
|
a6e979b311 | ||
|
|
ce68456f97 | ||
|
|
29e59438c5 | ||
|
|
10d4acfad2 | ||
|
|
1c3dc132cb | ||
|
|
cd96718339 | ||
|
|
22dbe411d9 | ||
|
|
a99d110fff | ||
|
|
f21929b7d1 | ||
|
|
5d78b310da | ||
|
|
549b6fd535 | ||
|
|
9fafdc1695 | ||
|
|
f169f7c92f | ||
|
|
b9ccadb2f1 | ||
|
|
f00650481b | ||
|
|
5d4c2fed88 | ||
|
|
66a81e7dbe | ||
|
|
6b5a433f65 | ||
|
|
b5d5c95b33 | ||
|
|
3ac891fa5a | ||
|
|
2dc0189179 | ||
|
|
dd98784262 | ||
|
|
1c9a51098b | ||
|
|
d075daf00e | ||
|
|
1bd6847bc8 | ||
|
|
dd832f2a88 | ||
|
|
aebf6eb168 | ||
|
|
8921cfe061 | ||
|
|
cd67596d57 | ||
|
|
e1c6630569 | ||
|
|
5ce4e31855 | ||
|
|
a99c488d49 | ||
|
|
94282c5575 | ||
|
|
7ad8529390 | ||
|
|
15534edce4 | ||
|
|
aa822e32af | ||
|
|
a14ab913a9 | ||
|
|
0165742a30 | ||
|
|
26a5b316de | ||
|
|
2140cd8cb6 | ||
|
|
4a6245a215 | ||
|
|
48544db690 | ||
|
|
2c89b3def3 | ||
|
|
5689182088 | ||
|
|
4a1f4b90fd | ||
|
|
f2d42d1134 | ||
|
|
51e7606e74 | ||
|
|
723c2d98eb | ||
|
|
7dee072e1a | ||
|
|
3e18c4160f | ||
|
|
513ad19187 | ||
|
|
eabede2203 | ||
|
|
b22dbc12a2 | ||
|
|
708e86adfb | ||
|
|
6f1b762c9d | ||
|
|
d9bfb29d6c | ||
|
|
6fc7bc4941 | ||
|
|
fb701cdc5e | ||
|
|
f7cc50b521 | ||
|
|
1a232cf766 | ||
|
|
853f54bcc2 | ||
|
|
127be2237d | ||
|
|
7e6456d7cd | ||
|
|
664ebda599 | ||
|
|
9b4a225ad2 | ||
|
|
6c59fa2d2c | ||
|
|
5736d850f0 | ||
|
|
127d6ee8ac | ||
|
|
2055543042 | ||
|
|
40e57a26a9 | ||
|
|
d604e44195 | ||
|
|
8b7778ccb6 | ||
|
|
7e30814ecb | ||
|
|
e5b1dfb74c | ||
|
|
27bc77363e | ||
|
|
506b73d0a0 | ||
|
|
20337b6e8a | ||
|
|
33ff87d8d9 | ||
|
|
af06bbd373 | ||
|
|
92a47ace8b | ||
|
|
f285ac369a | ||
|
|
68e76c273a | ||
|
|
e98c311a2c | ||
|
|
293edb316e | ||
|
|
2d4465eaf7 | ||
|
|
9266ff394a | ||
|
|
bbfd85ed1e | ||
|
|
6ecc2303f4 | ||
|
|
d35cfda798 | ||
|
|
cffa2a92a6 | ||
|
|
179c504746 | ||
|
|
31695d5ec1 | ||
|
|
bfb3020505 | ||
|
|
22a88ea2e4 | ||
|
|
0423fa5885 | ||
|
|
d17fbb527f | ||
|
|
d40bb0fe32 | ||
|
|
a513060a81 | ||
|
|
8e594a347c | ||
|
|
9af365edcc | ||
|
|
70554d6a2b | ||
|
|
eb84f52493 | ||
|
|
d074830d95 | ||
|
|
f7fe9ebeb2 | ||
|
|
b2c8f37aec | ||
|
|
a5bcccab59 | ||
|
|
a11f368b62 | ||
|
|
59656b004b | ||
|
|
ff8cb67a99 | ||
|
|
fa5256c8a7 | ||
|
|
4b19987ee6 | ||
|
|
9f43303073 | ||
|
|
a20958fb56 | ||
|
|
8c71098081 | ||
|
|
a3a5b44acb | ||
|
|
0473273709 | ||
|
|
bf492f32f9 | ||
|
|
4744677646 | ||
|
|
f0af2e4ef5 | ||
|
|
c7f763d6f0 | ||
|
|
563b8d1215 | ||
|
|
221493453f | ||
|
|
86c928430a | ||
|
|
cd0a0e51d5 | ||
|
|
66ff95f9d7 | ||
|
|
8ad172295f | ||
|
|
26a2ef5995 | ||
|
|
b4e3490fa1 | ||
|
|
828341f987 | ||
|
|
bc03fbb3be | ||
|
|
7a8dae5e35 | ||
|
|
d3c0e1f589 | ||
|
|
9c77d3f25b | ||
|
|
6371e9b71e | ||
|
|
578ef5cfeb | ||
|
|
516954e6f9 | ||
|
|
1151d5c3e3 | ||
|
|
25f1ddac30 | ||
|
|
39a146f8e6 | ||
|
|
037dcaefea | ||
|
|
01d6da3a90 | ||
|
|
8b2665980d | ||
|
|
42b3e6ad56 | ||
|
|
e709ab8a70 | ||
|
|
501409d1fb | ||
|
|
0fb3a34235 | ||
|
|
e4ba1f863e | ||
|
|
1f5c58b723 | ||
|
|
61e4db2c3b | ||
|
|
5efc1eb34c | ||
|
|
96c6f91201 | ||
|
|
62e69a2804 | ||
|
|
e9352d2675 | ||
|
|
ee6abdf28d | ||
|
|
968ee7ab09 | ||
|
|
3b3600d44f | ||
|
|
edcc5ca554 | ||
|
|
ca79dfe298 | ||
|
|
5945279b8b | ||
|
|
57bf64318b | ||
|
|
d9d0a46809 | ||
|
|
2fac644914 | ||
|
|
570592b24b | ||
|
|
fde94144ec | ||
|
|
f348496c10 | ||
|
|
a5c31b5365 | ||
|
|
2dc3f0ddef | ||
|
|
fa567014a5 | ||
|
|
96935bb122 | ||
|
|
43d3042dff | ||
|
|
fec221f8a6 | ||
|
|
b0f0635eb3 | ||
|
|
23a25131b0 | ||
|
|
a6eaa2bebc | ||
|
|
c6a0741379 | ||
|
|
5a1930e2d6 | ||
|
|
418deba5bf | ||
|
|
4ff2cff3c0 | ||
|
|
bd2123cf9d | ||
|
|
c2a603c910 | ||
|
|
db12af48fa | ||
|
|
a0f574a0ac | ||
|
|
8356fe9a7b | ||
|
|
6edce8b5f4 | ||
|
|
ca2bbe0b5f | ||
|
|
c8941a7faa | ||
|
|
5e20c03736 | ||
|
|
b19e882aab | ||
|
|
9d7d32a1b5 | ||
|
|
d512539554 | ||
|
|
a3a93f3387 | ||
|
|
a7eb5dc773 | ||
|
|
b23081df78 | ||
|
|
66db4ec46e | ||
|
|
8bd98c3a2d | ||
|
|
34cc42d3cb | ||
|
|
09eade4623 | ||
|
|
9bfe7df373 | ||
|
|
8006b774bf | ||
|
|
7cc91cfb0c | ||
|
|
24d72852e7 | ||
|
|
cedf3d55d5 | ||
|
|
d915f39996 | ||
|
|
346691c298 | ||
|
|
6dadc1d889 | ||
|
|
8a55f68a18 | ||
|
|
5645fa5847 | ||
|
|
ff0f00f64f | ||
|
|
7a34246dc9 | ||
|
|
62a6b52487 | ||
|
|
b845cde73d | ||
|
|
2c36dfbb34 | ||
|
|
acd880dd2a | ||
|
|
e6f57e5d94 | ||
|
|
b1de0fc17c | ||
|
|
ad4461932f | ||
|
|
3a94211377 | ||
|
|
9386e23b1f | ||
|
|
10b318c71d | ||
|
|
995d5b7b3a | ||
|
|
bbb12c84fd | ||
|
|
b50058ee47 | ||
|
|
4a0bfa937a | ||
|
|
4d1ec666dd | ||
|
|
b52e3d0e7d | ||
|
|
a1011c99a9 | ||
|
|
0ebd4f3d94 | ||
|
|
89c68c46a2 | ||
|
|
e4702cb09d | ||
|
|
6e64fd7a11 | ||
|
|
014b3f4ad5 | ||
|
|
7628c67883 | ||
|
|
54a322c09f | ||
|
|
aadf396244 | ||
|
|
81e18552e3 | ||
|
|
5686fd40a4 | ||
|
|
989ab3e96b | ||
|
|
88f1dc97b6 | ||
|
|
470a18522c | ||
|
|
002951804a | ||
|
|
fe7a832fad | ||
|
|
a2bc30b356 | ||
|
|
bae1f74681 | ||
|
|
887aa277a4 | ||
|
|
e17f2677c7 | ||
|
|
3b9972af39 | ||
|
|
5c96afec70 | ||
|
|
1bca393ecb | ||
|
|
27cf731552 | ||
|
|
94c84cd994 | ||
|
|
c2d3195e76 | ||
|
|
d0e7c9e2ea | ||
|
|
f3e1cac5a1 | ||
|
|
51fb513213 | ||
|
|
65c9fdd8d7 | ||
|
|
c1d4458acc | ||
|
|
ca1ca4f944 | ||
|
|
15ee8bf5a1 | ||
|
|
a08b5b19d5 | ||
|
|
8dac58ffc1 | ||
|
|
f0837070d6 | ||
|
|
dd7502a051 | ||
|
|
b36d4471e6 | ||
|
|
efb1066a88 | ||
|
|
dd39ae540b | ||
|
|
b39f808618 | ||
|
|
a2c059e562 | ||
|
|
19316a0d38 | ||
|
|
fbc7a5c063 | ||
|
|
e9cfd10e39 | ||
|
|
1424883773 | ||
|
|
6c746ec6d9 | ||
|
|
aacbfeb66e | ||
|
|
1513b369d3 | ||
|
|
35852a69fb | ||
|
|
96537dc091 | ||
|
|
e88ddc56ca | ||
|
|
f24ff07b30 | ||
|
|
3420ea7a2d | ||
|
|
87104f0e2d | ||
|
|
640446b868 | ||
|
|
2cf4917ded | ||
|
|
6394ee8714 | ||
|
|
7299e05fe1 | ||
|
|
c4bd0405fd | ||
|
|
89b8236a9c | ||
|
|
70f55c59c0 | ||
|
|
1c1716d833 | ||
|
|
d567f79652 | ||
|
|
ecd4e46a45 | ||
|
|
16fe495d18 | ||
|
|
4f70d23d0e | ||
|
|
290a3c94b7 | ||
|
|
9485dc5a96 | ||
|
|
e6abb69b0e | ||
|
|
e862052bb4 | ||
|
|
4071ad1278 | ||
|
|
60619643e6 | ||
|
|
b818769c1f | ||
|
|
12bc4bab0c | ||
|
|
8fccfa21df | ||
|
|
a697d043fc | ||
|
|
9d812f12cc | ||
|
|
869cf78e98 | ||
|
|
186680cd8c | ||
|
|
5aed6d995d | ||
|
|
3d7a665987 | ||
|
|
10a716969b | ||
|
|
0eb7180fc1 | ||
|
|
b1b4c61b80 | ||
|
|
359cfb5c98 | ||
|
|
8656fd50ce | ||
|
|
25a4dcb653 | ||
|
|
c90a853377 | ||
|
|
18069d8fd2 | ||
|
|
a40f4a75a9 | ||
|
|
5b6460b68c | ||
|
|
c0d1348e65 | ||
|
|
482c163344 | ||
|
|
221d1131b4 | ||
|
|
f19f426449 | ||
|
|
9dd9b6eb71 | ||
|
|
acb0b21d6d | ||
|
|
b17dd3c695 | ||
|
|
fa66ffaf3f | ||
|
|
b106420774 | ||
|
|
c971adb59b | ||
|
|
d6326deeb0 | ||
|
|
483667c919 | ||
|
|
8bef512803 | ||
|
|
501009c11f | ||
|
|
d3d430827f | ||
|
|
2d9b88bd52 | ||
|
|
93d5934ea7 | ||
|
|
180a279558 | ||
|
|
cf38461911 | ||
|
|
2c85539517 | ||
|
|
d651189581 | ||
|
|
f6a8cd556e | ||
|
|
3bde961dc7 | ||
|
|
6396beaef7 | ||
|
|
6bc6defb07 | ||
|
|
732861092f | ||
|
|
9fcc452ed3 | ||
|
|
26302a6ff2 | ||
|
|
62dbfb3837 | ||
|
|
b91cbe9794 | ||
|
|
93b8ba9576 | ||
|
|
23fdbbd03d | ||
|
|
d7a74d84ea | ||
|
|
58e6dd9794 | ||
|
|
907cf2d925 | ||
|
|
ee48a3673d | ||
|
|
c6f362b77f | ||
|
|
6e01c59635 | ||
|
|
d411f66903 | ||
|
|
0768dc1610 | ||
|
|
4c3db16842 | ||
|
|
805fd1dd90 | ||
|
|
c59996b24b | ||
|
|
e0a1e8293b | ||
|
|
e8af9c0233 | ||
|
|
a955d30d64 | ||
|
|
dbab3bb259 | ||
|
|
bd9c4ee99a | ||
|
|
a0cbd3755d | ||
|
|
12323e233f | ||
|
|
8a308db3d4 | ||
|
|
0583dc8b74 | ||
|
|
789a4a3659 | ||
|
|
a87d0451c8 | ||
|
|
82e9309d27 | ||
|
|
8deaa378c9 | ||
|
|
b5363e3552 | ||
|
|
28a10ac340 | ||
|
|
c349d51923 | ||
|
|
ef01745e09 | ||
|
|
c97d6a65d0 | ||
|
|
2062273504 | ||
|
|
6e6436ee41 | ||
|
|
38f4a22bc4 | ||
|
|
3a8b047e3c | ||
|
|
52483ee6bd | ||
|
|
dce1cbc096 | ||
|
|
46c563a9a8 | ||
|
|
1380c2ecce | ||
|
|
71a3818bdd | ||
|
|
e3a343c797 | ||
|
|
195b1a043e | ||
|
|
69e66c327f | ||
|
|
885637cc7d | ||
|
|
90a7c8f787 | ||
|
|
2171c0caa4 | ||
|
|
924a6f9254 | ||
|
|
ad4cffb0c6 | ||
|
|
cf77ffb89d | ||
|
|
b2fdcd7f1c | ||
|
|
094ce2ae18 | ||
|
|
e8cb2c1bfe | ||
|
|
9c6c56c49d | ||
|
|
0d5affede3 | ||
|
|
13f210f403 | ||
|
|
c61ad51883 | ||
|
|
c0842b0797 | ||
|
|
2851079ff0 | ||
|
|
5197524d1c | ||
|
|
78efd1acc6 | ||
|
|
eee0a99b88 | ||
|
|
10c96c394d | ||
|
|
07cc3fb2d9 | ||
|
|
e31f1433bf | ||
|
|
af06c97efd | ||
|
|
400de1d66e | ||
|
|
2a9285eee8 | ||
|
|
785bdde6bd | ||
|
|
071e2dc524 | ||
|
|
fc08d072a4 | ||
|
|
0019485aba | ||
|
|
e76f145b65 | ||
|
|
27d5937f4c | ||
|
|
868c4c5977 | ||
|
|
5a37929ceb | ||
|
|
b1dc2dbf8a | ||
|
|
ecfbef95c7 | ||
|
|
bfd8fbefee | ||
|
|
f8c36104ad | ||
|
|
e26230fb2d | ||
|
|
6417ad0a3c | ||
|
|
0f988d087c | ||
|
|
7c01b64aed | ||
|
|
03130c78a7 | ||
|
|
30ab326d2b | ||
|
|
8d1cba57f8 | ||
|
|
cbfd6c1a66 | ||
|
|
81cb3c82f4 | ||
|
|
e64296e109 | ||
|
|
5872b31781 | ||
|
|
51b801b1c6 | ||
|
|
9ef153c0b6 | ||
|
|
2e236faec7 | ||
|
|
129c356ecf | ||
|
|
d8316b0bdc | ||
|
|
1d3a92f3f1 | ||
|
|
81c858c719 | ||
|
|
341041aedb | ||
|
|
25256b2295 | ||
|
|
13874ba547 | ||
|
|
bde58add62 | ||
|
|
9112c63347 | ||
|
|
78fa2b8d78 | ||
|
|
6922c369f3 | ||
|
|
b0fd438a1d | ||
|
|
99f4f0c7d3 | ||
|
|
b09c1cd242 | ||
|
|
5ca3bf9362 | ||
|
|
c6a9884bde | ||
|
|
43825ca78e | ||
|
|
44eeadb211 | ||
|
|
7011da5fa9 | ||
|
|
c61ddfa9d1 | ||
|
|
e64b6dcb13 | ||
|
|
183dc236e4 | ||
|
|
ef5349dec2 | ||
|
|
b39afc0711 | ||
|
|
a8579b43ac | ||
|
|
fa92eca328 | ||
|
|
42165df29f | ||
|
|
9860569536 | ||
|
|
7aa48a167f | ||
|
|
6e3764da48 | ||
|
|
4d503d6925 | ||
|
|
8e04d40034 | ||
|
|
cb57cd02e0 | ||
|
|
22fe48e477 | ||
|
|
204f73cb05 | ||
|
|
28096abea5 | ||
|
|
2d7eb5cb7f | ||
|
|
e6d6932e21 | ||
|
|
406e5d64c0 | ||
|
|
13756aad9f | ||
|
|
c1a8b16f30 | ||
|
|
e3671285c3 | ||
|
|
b33cff3613 | ||
|
|
afc2a4a75c | ||
|
|
7c4583c87a | ||
|
|
f563815961 | ||
|
|
0ce38f747c | ||
|
|
efce8e916b | ||
|
|
44728c9f93 | ||
|
|
d96a81e4f2 | ||
|
|
f11844374e | ||
|
|
8c63203bb4 | ||
|
|
751299d5f1 | ||
|
|
7d479bf854 | ||
|
|
c812a4ede5 | ||
|
|
a826283111 | ||
|
|
f61f30c1e6 | ||
|
|
8f76e8ba8b | ||
|
|
537d89b9e0 | ||
|
|
0927aaa0f1 | ||
|
|
d17aad797d | ||
|
|
53e611a03a | ||
|
|
7ac1be8a9a | ||
|
|
dd7d70d619 | ||
|
|
b9d1d1e2f4 | ||
|
|
2dc00adec8 | ||
|
|
b97e937f28 | ||
|
|
c54c4e73e6 | ||
|
|
93528597f4 | ||
|
|
a85bb7bfdb | ||
|
|
0f2d00abf3 | ||
|
|
0e4c716177 | ||
|
|
85b77c3d12 | ||
|
|
03884b1aaa | ||
|
|
dbdc76e6ea | ||
|
|
b8aaed368b | ||
|
|
6ec3379153 | ||
|
|
b177aad1ee | ||
|
|
e050c7807d | ||
|
|
f3f7398a8b | ||
|
|
da4311dc74 | ||
|
|
59471f6cac | ||
|
|
d8b92064d7 | ||
|
|
8e96e60179 | ||
|
|
54877176a6 | ||
|
|
a3ad32a3e5 | ||
|
|
602a4f219c | ||
|
|
f24977dc3d | ||
|
|
4607f9ebb0 | ||
|
|
672f4a03f2 | ||
|
|
f0736ade3d | ||
|
|
4da48cea7a | ||
|
|
1b02ea0677 | ||
|
|
0d1bd4c5d0 | ||
|
|
3f08f1a94c | ||
|
|
1069366219 | ||
|
|
0002bd2875 | ||
|
|
823077dd7d | ||
|
|
34971ff3e0 | ||
|
|
965069ba3a | ||
|
|
43766751a4 | ||
|
|
a92da71c6b | ||
|
|
da47985361 | ||
|
|
68ae32c48c | ||
|
|
3373e33d4b | ||
|
|
2839e79ab0 | ||
|
|
5bffbfbe0d | ||
|
|
4080338c1b | ||
|
|
e06e9393c1 | ||
|
|
4bb9fd7226 | ||
|
|
7edc7d74d4 | ||
|
|
9a5dac605c | ||
|
|
c0a781c0bd | ||
|
|
719bea515e | ||
|
|
153eafdcb0 | ||
|
|
2f588cf6ef | ||
|
|
3ad3c474b5 | ||
|
|
73ba4b68be | ||
|
|
b786d8909a | ||
|
|
ab45ec9fa4 | ||
|
|
fcbe56b504 | ||
|
|
693ec7ddf6 | ||
|
|
61b83ab992 | ||
|
|
6ce9b1ab05 | ||
|
|
a5d58d79fc | ||
|
|
dac3249dc4 | ||
|
|
99eddbfa13 | ||
|
|
ea1fc8f358 | ||
|
|
bbb5a0e891 | ||
|
|
3f491fa7cb | ||
|
|
ae007fcebc | ||
|
|
8580a99728 | ||
|
|
3257d2595d | ||
|
|
5434b3ca24 | ||
|
|
1ea6ba273b | ||
|
|
039d9252c6 | ||
|
|
ca67d6eb6d | ||
|
|
7179edfba7 | ||
|
|
baec9785a2 | ||
|
|
ae2039c65d | ||
|
|
3f803653c4 | ||
|
|
6ff67693b9 | ||
|
|
5501d644b9 | ||
|
|
276fccb170 | ||
|
|
e2fac06f64 | ||
|
|
25def1ceaf | ||
|
|
c0f70309dd | ||
|
|
899c1f519c | ||
|
|
5c1363150e | ||
|
|
4153584392 | ||
|
|
2858731063 | ||
|
|
874a0c32e0 | ||
|
|
4f845befaa | ||
|
|
95194f1c5b | ||
|
|
df6bbaa7c5 | ||
|
|
b1ca21a161 | ||
|
|
bc42a54a51 | ||
|
|
1555bd8c53 | ||
|
|
649e3e7bc5 | ||
|
|
ead4e5255e | ||
|
|
5eafb71083 | ||
|
|
8ed36f583d | ||
|
|
3e311fd4c1 | ||
|
|
a4ce90a918 | ||
|
|
ba46389a87 | ||
|
|
f7e07ca7d8 | ||
|
|
3e80a9ee7d | ||
|
|
dfc6f7b1c1 | ||
|
|
753f5f7bc8 | ||
|
|
982271e8ec | ||
|
|
802ec3d86a | ||
|
|
f1b0e33694 | ||
|
|
1d64fb01bd | ||
|
|
086f575a41 | ||
|
|
3cda115cd5 | ||
|
|
d491502750 | ||
|
|
1d4c33d161 | ||
|
|
47a879a961 | ||
|
|
b59184424b | ||
|
|
a7803d1785 | ||
|
|
5415bf87ce | ||
|
|
a33f2dde77 | ||
|
|
bb7ef791de | ||
|
|
aa1a5d3c19 | ||
|
|
a53d8c926e | ||
|
|
714cd8d8df | ||
|
|
c9f7444d06 | ||
|
|
ea05777032 | ||
|
|
14fea4a3a3 | ||
|
|
0613858855 | ||
|
|
6687ed360b | ||
|
|
409506d2b2 | ||
|
|
b96d6e00c3 | ||
|
|
b6ae926fd3 | ||
|
|
508c986166 | ||
|
|
725201040e | ||
|
|
70d7c8fc68 | ||
|
|
05f9861da4 | ||
|
|
c588750c96 | ||
|
|
d00e229198 | ||
|
|
be1a80ebbd | ||
|
|
44706ae343 | ||
|
|
2af41e7c8c | ||
|
|
e5c29a75de | ||
|
|
2a36dc64bd | ||
|
|
dd128c8502 | ||
|
|
3812dc4464 | ||
|
|
851038679a | ||
|
|
146ee9b3c7 | ||
|
|
6c42a77411 | ||
|
|
7e24034fcc | ||
|
|
e6eb550bfd | ||
|
|
2dbb374869 | ||
|
|
e5efbea2ab | ||
|
|
bc74a16149 | ||
|
|
4cd6c58159 | ||
|
|
9e6fbea698 | ||
|
|
e796630120 | ||
|
|
cad5ce54dc | ||
|
|
3c3e4ca2ff | ||
|
|
b9b90c6eef | ||
|
|
747c6131bf | ||
|
|
5c65939330 | ||
|
|
cddcd9ffb3 | ||
|
|
1295c3863e | ||
|
|
99ce6e2dee | ||
|
|
a9d4a15a4f | ||
|
|
2fbf60a846 | ||
|
|
1838344b07 | ||
|
|
fd6aacd0fe | ||
|
|
4b142cbdcc | ||
|
|
802425b4d5 | ||
|
|
745d388cb0 | ||
|
|
1f52ee1dea | ||
|
|
1af7d7970a | ||
|
|
201edc0249 | ||
|
|
2f4a572a23 | ||
|
|
0cf8cca248 | ||
|
|
af4a2589a6 | ||
|
|
2d55491692 | ||
|
|
1007720589 | ||
|
|
b2f470464a | ||
|
|
683c99c8b6 | ||
|
|
428cf691f1 | ||
|
|
bfe54ef7e7 | ||
|
|
40dff18025 | ||
|
|
4d620d91e4 | ||
|
|
a502e1fc56 | ||
|
|
4f0c220d99 | ||
|
|
08a4b21460 | ||
|
|
9fd6f89fc3 | ||
|
|
11652d597f | ||
|
|
f943cd9b4b | ||
|
|
1e392e0bb3 | ||
|
|
e536c58f81 | ||
|
|
63c02cbe35 | ||
|
|
d9a8392723 | ||
|
|
ae52b91d13 | ||
|
|
370062cc55 | ||
|
|
43eae28f55 | ||
|
|
c2c27044b5 | ||
|
|
09722f3ffc | ||
|
|
2cf960b64d | ||
|
|
05b31bdd6e | ||
|
|
e115807351 | ||
|
|
f5b29d2726 | ||
|
|
dfac296402 | ||
|
|
9e2117c955 | ||
|
|
4fdf2ea290 | ||
|
|
10fb465525 | ||
|
|
d44822b7d0 | ||
|
|
18a35c1ab7 | ||
|
|
26cf077cfa | ||
|
|
c3379b44dd | ||
|
|
3ad305aa06 | ||
|
|
89d74da576 | ||
|
|
9ad4177bfa | ||
|
|
20c830c848 | ||
|
|
877bb029f9 | ||
|
|
cfb5cb1a2f | ||
|
|
245f562e1c | ||
|
|
68dc92f0e1 | ||
|
|
ee4089304a | ||
|
|
f3dfa1a955 | ||
|
|
2320e42163 | ||
|
|
bcc35775f4 | ||
|
|
3239536832 | ||
|
|
9c1c414ca7 | ||
|
|
20e5963059 | ||
|
|
70c57e2ad6 | ||
|
|
4639786521 | ||
|
|
71e873ed1f | ||
|
|
c416a82e64 | ||
|
|
539252053f | ||
|
|
da791ef721 | ||
|
|
49bca8998a | ||
|
|
6e14c67e82 | ||
|
|
7fddb9d59e | ||
|
|
006112f3d3 | ||
|
|
e075a430e2 | ||
|
|
134299b413 | ||
|
|
03059c5cd2 | ||
|
|
918d9d8687 | ||
|
|
5017f8b038 | ||
|
|
78052d2ce2 | ||
|
|
8a6b10607d | ||
|
|
034117e253 | ||
|
|
a3ddd6140b | ||
|
|
ac292296fe | ||
|
|
d50a66c8ba | ||
|
|
fb4d3b2ca9 | ||
|
|
fcdd2a9176 | ||
|
|
fa921f0aa9 | ||
|
|
ac221275d9 | ||
|
|
33f8b158a9 | ||
|
|
b9b33ebe79 | ||
|
|
a23816134b | ||
|
|
4e8e84244b | ||
|
|
a94316cc9e | ||
|
|
01a1bc9e6a | ||
|
|
d2b69b2b08 | ||
|
|
91fbba3873 | ||
|
|
a4f3bc853a | ||
|
|
b7ece58caa | ||
|
|
fe354c867d | ||
|
|
326d52f534 | ||
|
|
fbd9ac7d95 | ||
|
|
96018996fc | ||
|
|
9b205b1355 | ||
|
|
56f915eb66 | ||
|
|
46f8868961 | ||
|
|
15089f9d87 | ||
|
|
4783bfe01d | ||
|
|
7b894946bf | ||
|
|
a01e513822 | ||
|
|
3061fd3d8f | ||
|
|
9b281f0a3c | ||
|
|
e433708056 | ||
|
|
43eae51bb3 | ||
|
|
72edbe596e | ||
|
|
53b5533936 | ||
|
|
84c86fd475 | ||
|
|
a9d644e1e8 | ||
|
|
7ed6c7bd7f | ||
|
|
3fd23cee0f | ||
|
|
0665c93c2b | ||
|
|
cd741d961e | ||
|
|
ea855f82b4 | ||
|
|
d775f42703 | ||
|
|
0981d3106d | ||
|
|
a20c7f6f76 | ||
|
|
cf9b57d9fa | ||
|
|
7deedb26f5 | ||
|
|
4c2c7cb2f3 | ||
|
|
7ec180ee7c | ||
|
|
b1ee6c3c95 | ||
|
|
a36fccc9b0 | ||
|
|
36b05d9378 | ||
|
|
2b8ac71253 | ||
|
|
1ffb096508 | ||
|
|
7a66d88e9f | ||
|
|
42c5b9534a | ||
|
|
0b1f3b51e3 | ||
|
|
6646b573e8 | ||
|
|
201762ee50 | ||
|
|
99ee905038 | ||
|
|
d2afbd9216 | ||
|
|
5f49c5f646 | ||
|
|
d91c22ea24 | ||
|
|
4c9745f73c | ||
|
|
edb0ba811f | ||
|
|
a1ca27b997 | ||
|
|
285d187884 | ||
|
|
351c193b98 | ||
|
|
8586d8ae85 | ||
|
|
1a40f433a5 | ||
|
|
6c6f792c6b | ||
|
|
149fa8a577 | ||
|
|
f279476684 | ||
|
|
0ca8ace988 | ||
|
|
80f87ef569 | ||
|
|
904efdd4d8 | ||
|
|
874d3f0b17 | ||
|
|
603005ba18 | ||
|
|
6b286543d4 | ||
|
|
28992c6f13 | ||
|
|
174e208369 | ||
|
|
c632d5145e | ||
|
|
f3bee8c070 | ||
|
|
8bfeb5ac21 | ||
|
|
05dc6f8e4b | ||
|
|
2eb77acf73 | ||
|
|
f7cec9be38 | ||
|
|
8f8ee60eb1 | ||
|
|
8f14c7321b | ||
|
|
aedb80c251 | ||
|
|
e7224c7bda | ||
|
|
cceeb88c6d | ||
|
|
65f39a50c1 | ||
|
|
0a52ed381d | ||
|
|
78f2e8f38a | ||
|
|
9fdc403396 | ||
|
|
9c3a2f052d | ||
|
|
5f26a86bf7 | ||
|
|
b1d9f3d833 | ||
|
|
05ff62d85f | ||
|
|
b0774b89c7 | ||
|
|
1ec797cddb | ||
|
|
2ff2a78694 | ||
|
|
93664d10e6 | ||
|
|
10e5d42522 | ||
|
|
d531d70f1f | ||
|
|
95596bc198 | ||
|
|
b32a92df5b | ||
|
|
ac6c35d7c4 | ||
|
|
2ed8a8b807 | ||
|
|
759e92cc2a | ||
|
|
feede96d6d | ||
|
|
fc33249d32 | ||
|
|
ff48f13746 | ||
|
|
47e61610ca | ||
|
|
4f97aedbb2 | ||
|
|
51203e49f9 | ||
|
|
1906a085ad | ||
|
|
5a3ac6b96e | ||
|
|
1146660fc6 | ||
|
|
2888e1680e | ||
|
|
7bf22659e7 | ||
|
|
86f8bbd47d | ||
|
|
5bc8929795 | ||
|
|
b2853aa811 | ||
|
|
c06fc00f24 | ||
|
|
1e6eec768c | ||
|
|
33d7d77775 | ||
|
|
83b060b573 | ||
|
|
df656cb98d | ||
|
|
cb21ddc438 | ||
|
|
7b45203461 | ||
|
|
ecb07e2abb | ||
|
|
647c40ba5e | ||
|
|
9210fe1a57 | ||
|
|
9efb9741d1 | ||
|
|
87b5c870d5 | ||
|
|
bd34b97e9b | ||
|
|
c74e119993 | ||
|
|
96e23dda6b | ||
|
|
c6a0bc2487 | ||
|
|
d1efbce945 | ||
|
|
381b2b8aa1 | ||
|
|
72478fac83 | ||
|
|
c4fd53d628 | ||
|
|
ec4750f422 | ||
|
|
bc0caee371 | ||
|
|
8087bd82bf | ||
|
|
ec3fb620dc | ||
|
|
d6e5df9092 | ||
|
|
160399cee7 | ||
|
|
4b4e4b30a9 | ||
|
|
67ed21052f | ||
|
|
532f8cfce9 | ||
|
|
a45af9ac39 | ||
|
|
3b487511d7 | ||
|
|
a1cbb799be | ||
|
|
8b9545d622 | ||
|
|
cd325dc3fd | ||
|
|
9768774896 | ||
|
|
e1781bf2ae | ||
|
|
b566a7b692 | ||
|
|
e7aa295b47 | ||
|
|
2d44b44372 | ||
|
|
17e19b8cc6 | ||
|
|
e88b897635 | ||
|
|
7a0bd95113 | ||
|
|
be52c01b9f | ||
|
|
c02319cec1 | ||
|
|
f7507d3f79 | ||
|
|
31d2c84541 | ||
|
|
84d4f7765c | ||
|
|
aed6bcc737 | ||
|
|
73e2229fb4 | ||
|
|
a0633ae5e3 | ||
|
|
bdce70e891 | ||
|
|
3197817a76 | ||
|
|
0a7e8a8c3e | ||
|
|
b23bdc628b | ||
|
|
d727aed293 |
No files matched your search
+1
-10
@@ -4,7 +4,6 @@
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=crlf
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.exe binary
|
||||
*.dll binary
|
||||
*.jar binary
|
||||
@@ -12,12 +11,4 @@
|
||||
*.zip binary
|
||||
*.pyd binary
|
||||
*.cfg text eol=lf
|
||||
*.py text eol=lf
|
||||
*.jks binary
|
||||
|
||||
# Disable autocrlf on generated files, they always generate with LF
|
||||
src/generated/**/.cache/cache text eol=lf
|
||||
src/generated/**/*.json text eol=lf
|
||||
src/generated_test/**/.cache/cache text eol=lf
|
||||
src/generated_test/**/*.json text eol=lf
|
||||
|
||||
*.py text eol=lf
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Want to request a feature?
|
||||
title: ''
|
||||
labels: Feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
|
||||
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
|
||||
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
|
||||
<!-- Discord: https://discord.gg/UvedJ9m -->
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Issue report
|
||||
about: Visit the forum first for issues with crashes.
|
||||
title: ''
|
||||
labels: Triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Thank you for reporting! -->
|
||||
|
||||
<!-- For support with crashes and issues, please create a post on the forum instead: -->
|
||||
<!-- https://www.minecraftforge.net/forum/forum/18-support-bug-reports/ -->
|
||||
|
||||
**Minecraft Version:** {Minecraft version}
|
||||
|
||||
**Forge Version:** {Forge version. *Version number, not latest/rb*}
|
||||
|
||||
**Logs:** {Link(s) to GitHub Gist with full latest.log and/or crash report}
|
||||
|
||||
**Steps to Reproduce:**
|
||||
1. Do this
|
||||
2. Then do that
|
||||
3. ...
|
||||
|
||||
**Description of issue:**
|
||||
@@ -1,43 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: Stale
|
||||
# Comment to post when removing the stale label. Set to `false` to disable
|
||||
unmarkComment: false
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 1
|
||||
# Issues with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- Assigned
|
||||
|
||||
pulls:
|
||||
# Number of days of inactivity before a Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale Pull Request is closed
|
||||
daysUntilClose: 14
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had recent
|
||||
activity, and will be closed if no further activity occurs. If this pull request was
|
||||
overlooked, forgotten, or should remain open for any other reason, please reply
|
||||
here to call attention to it and remove the stale status. Thank you for your contributions.
|
||||
# Comment to post when closing a stale Pull Request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This pull request has been automatically closed because it has not had
|
||||
recent activity. Please feel free to update or reopen it.
|
||||
issues:
|
||||
# Number of days of inactivity before an Issue becomes stale
|
||||
daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale Issue is closed
|
||||
daysUntilClose: 14
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had activity
|
||||
in a long time. If this issue is still relevant and should remain open, please reply
|
||||
with a short explanation (e.g. "I have checked the code and this issue is still relevant because ___."
|
||||
or "Here's a screenshot of this issue on the latest version").
|
||||
Thank you for your contributions!
|
||||
# Comment to post when closing a stale Issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because it has not had
|
||||
activity in a long time. Please feel free to reopen it or create a new issue.
|
||||
@@ -1,16 +0,0 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: Forum
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
👋 We use the issue tracker exclusively for final bug reports and feature requests.
|
||||
However, this issue appears to be better suited for the
|
||||
[Forge Support Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
|
||||
Please create a new topic on the support forum with this issue, and the conversation can
|
||||
continue there.
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
lock: true
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '1.18.x' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
|
||||
with:
|
||||
java: 17
|
||||
gradle_setup: './gradlew setup'
|
||||
gradle_tasks: 'build publish'
|
||||
artifact_name: 'forge'
|
||||
secrets:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
|
||||
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
|
||||
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
|
||||
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
|
||||
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
|
||||
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
|
||||
+7
-33
@@ -1,47 +1,21 @@
|
||||
#eclipse
|
||||
**/bin
|
||||
**/.settings
|
||||
**/.classpath
|
||||
*.project
|
||||
|
||||
#In case people make their workspace in the repo
|
||||
/eclipse/
|
||||
/bin
|
||||
/.settings
|
||||
/.classpath
|
||||
|
||||
#idea
|
||||
/.idea
|
||||
/*/out
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/buildSrc
|
||||
/.gradle
|
||||
/*/build
|
||||
/*/.gradle
|
||||
|
||||
# Projects repo, either ignore it, or ignore patches.
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
/projects/fmlonly/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
/projects/**/build/
|
||||
/projects/**/out/
|
||||
/projects/**/run/
|
||||
/projects/**/*.launch
|
||||
/eclipse/
|
||||
/repo/
|
||||
src/generated/resources/.cache/
|
||||
|
||||
#Patch rejects
|
||||
/patches-/
|
||||
*.patch.rej
|
||||
/projects/*/rejects/
|
||||
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
*.py
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
#Patch reject
|
||||
*.rej
|
||||
@@ -0,0 +1,27 @@
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/MinecraftForge/FML
|
||||
|
||||
This minecraft mod is a clean open source implementation of a mod loader for
|
||||
minecraft servers, minecraft bukkit servers, and minecraft clients.
|
||||
|
||||
The code is authored by cpw.
|
||||
|
||||
It (partially) implements API defined by the client side ModLoader, authored by Risugami.
|
||||
http://www.minecraftforum.net/topic/75440-
|
||||
|
||||
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
|
||||
http://www.minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
It also contains code from the Maven project for performing versioned dependency
|
||||
resolution. http://maven.apache.org/
|
||||
|
||||
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
|
||||
with credit to it's authors.
|
||||
|
||||
Forge Mod Loader downloads components from the Minecraft Coder Pack
|
||||
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
Minecraft Forge: Credits/Thank You
|
||||
|
||||
Forge is a set of tools and modifications to the Minecraft base game code to assist
|
||||
mod developers in creating new and exciting content. It has been in development for
|
||||
several years now, but I would like to take this time thank a few people who have
|
||||
helped it along it's way.
|
||||
|
||||
First, the people who originally created the Forge projects way back in Minecraft
|
||||
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
|
||||
of me taking over the project, who knows what Minecraft modding would be today.
|
||||
|
||||
Secondly, someone who has worked with me, and developed some of the core features
|
||||
that allow modding to be as functional, and as simple as it is, cpw. For developing
|
||||
FML, which stabelized the client and server modding ecosystem. As well as the base
|
||||
loading system that allows us to modify Minecraft's code as elegently as possible.
|
||||
|
||||
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
|
||||
sane as well as guiding the community into creating better additions to Forge.
|
||||
|
||||
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
|
||||
I am a part}. For creating some of the core tools needed to make Minecraft modding
|
||||
both possible, and as stable as can be.
|
||||
On that note, here is some specific information of the MCP data we use:
|
||||
* Minecraft Coder Pack (MCP) *
|
||||
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
|
||||
download components of MCP and distribute MCP data files. This permission is not
|
||||
transitive and others wishing to redistribute the Minecraft Forge source independently
|
||||
should seek permission of MCP or remove the MCP data files and request their users
|
||||
to download MCP separately.
|
||||
|
||||
And lastly, the countless community members who have spent time submitting bug reports,
|
||||
pull requests, and just helping out the community in general. Thank you.
|
||||
|
||||
--LexManos
|
||||
|
||||
=========================================================================
|
||||
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
|
||||
|
||||
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
|
||||
and minecraft clients.
|
||||
|
||||
The code is authored by cpw.
|
||||
|
||||
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
|
||||
http://www.minecraftforum.net/topic/75440-
|
||||
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
|
||||
|
||||
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
|
||||
http://www.minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
It also contains code from the Maven project for performing versioned dependency
|
||||
resolution. http://maven.apache.org/
|
||||
|
||||
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
|
||||
with credit to it's authors.
|
||||
|
||||
Forge Mod Loader downloads components from the Minecraft Coder Pack
|
||||
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
Unless noted below, Minecraft Forge, Forge Mod Loader, and all
|
||||
parts herein are licensed under the terms of the LGPL 2.1 found
|
||||
here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and
|
||||
copied below.
|
||||
This minecraft mod, Forge Mod Loader, including all parts herein except as noted below,
|
||||
is licensed under the GNU LGPL v2.1 or later.
|
||||
|
||||
Homepage: http://minecraftforge.net/
|
||||
https://github.com/MinecraftForge/MinecraftForge
|
||||
|
||||
Homepage: https://github.com/MinecraftForge/FML
|
||||
|
||||
A note on authorship:
|
||||
All source artifacts are property of their original author, with
|
||||
the exclusion of the contents of the patches directory and others
|
||||
copied from it from time to time. Authorship of the contents of
|
||||
the patches directory is retained by the Minecraft Forge project.
|
||||
This is because the patches are partially machine generated
|
||||
artifacts, and are changed heavily due to the way forge works.
|
||||
Individual attribution within them is impossible.
|
||||
This software includes portions from the Apache Maven project at
|
||||
http://maven.apache.org/ specifically the ComparableVersion.java code. It is
|
||||
included based on guidelines at
|
||||
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
|
||||
with notices intact. The only change is a non-functional change of package name.
|
||||
|
||||
Consent:
|
||||
All contributions to Forge must consent to the release of any
|
||||
patch content to the Forge project.
|
||||
|
||||
A note on infectivity:
|
||||
The LGPL is chosen specifically so that projects may depend on Forge
|
||||
features without being infected with its license. That is the
|
||||
purpose of the LGPL. Mods and others using this code via ordinary
|
||||
Java mechanics for referencing libraries are specifically not bound
|
||||
by Forge's license for the Mod code.
|
||||
This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating
|
||||
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
|
||||
authored by genman, heikok, pivot.
|
||||
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
|
||||
|
||||
|
||||
=== MCP Data ===
|
||||
@@ -39,26 +26,14 @@ I grant permission for some parts of FML to be redistributed outside the terms o
|
||||
the minecraft modding community. All contributions to these parts should be licensed under the same additional grant.
|
||||
|
||||
-- Runtime patcher --
|
||||
License is granted to redistribute the runtime patcher code (src/main/java/net/minecraftforge/fml/common/patcher
|
||||
and subdirectories) under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
License is granted to redistribute the runtime patcher code (common/cpw/mods/fml/patcher and subdirectories) under
|
||||
any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
-- ASM transformers --
|
||||
License is granted to redistribute the ASM transformer code (src/main/java/net/minecraftforge/common/asm/ and subdirectories)
|
||||
License is granted to redistribute the ASM transformer code (common/cpw/mods/fml/common/asm/ and subdirectories)
|
||||
under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
=========================================================================
|
||||
This software includes portions from the Apache Maven project at
|
||||
http://maven.apache.org/ specifically the ComparableVersion.java code. It is
|
||||
included based on guidelines at
|
||||
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
|
||||
with notices intact. The only change is a non-functional change of package name.
|
||||
|
||||
This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating
|
||||
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
|
||||
authored by genman, heikok, pivot.
|
||||
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
|
||||
=========================================================================
|
||||
|
||||
========
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
@@ -68,10 +43,6 @@ The only changes are to replace some Trove collection types with standard Java c
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
@@ -117,7 +88,7 @@ modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
@@ -173,7 +144,7 @@ modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
@@ -220,7 +191,7 @@ Library.
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
@@ -278,7 +249,7 @@ instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
@@ -329,7 +300,7 @@ Library will still fall under Section 6.)
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
@@ -391,7 +362,7 @@ restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
@@ -432,7 +403,7 @@ subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
@@ -484,7 +455,7 @@ conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
@@ -1,2 +0,0 @@
|
||||
Copyright (c) Forge Development LLC and contributors
|
||||
SPDX-License-Identifier: LGPL-2.1-only
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
This is Forge Mod Loader, or FML for short, by cpw.
|
||||
|
||||
More information can be found at https://github.com/MinecraftForge/FML/wiki
|
||||
|
||||
It is a clean reimplementation of a mod loading system for client, server and
|
||||
bukkit use, incorporating API implementations of client side ModLoader by
|
||||
Risugami.
|
||||
|
||||
It can be installed on its own, or as part of Minecraft Forge.
|
||||
|
||||
----------------------
|
||||
About Forge Mod Loader
|
||||
----------------------
|
||||
|
||||
Environments and compatibility
|
||||
==============================
|
||||
FML covers the three main environments: client, server and bukkit. All
|
||||
environments share the main mod loading code base, with additional varying hooks
|
||||
based on the specific environment. Compatibility is almost 100% with all
|
||||
ModLoader based mods- any mod with any difficulty (unless noted below) should
|
||||
be reported as a ticket to the github, please.
|
||||
|
||||
Client side mods should be 100% compatible with ModLoader. The only exception is
|
||||
Optifine, however new versions of optifine are compatible. There are some very
|
||||
visible changes to the client when FML is installed.
|
||||
|
||||
Server side mods are mostly compatible with previous MLMP based incarnations,
|
||||
however, this compatibility is not guaranteed. Most mods have adapted to FML
|
||||
and MLMP is not assured and will cause errors. Ask your mod author to update
|
||||
to FML.
|
||||
|
||||
Bukkit side modding is an adaptation of the server side mod code to the bukkit
|
||||
environment. Mods need to be recompiled against the bukkit decompilation to work
|
||||
however there are guides and tools in development to make this process easier.
|
||||
Consult mcportcentral.co.za for more information.
|
||||
|
||||
Minecraft Forge in all cases bundles FML as it's modloading technology of choice
|
||||
because FML is open source, freely distributable, and can be easily updated by
|
||||
contributors through github.
|
||||
|
||||
Notable integrations
|
||||
====================
|
||||
Optifine has FML compatibility. It varies from Optifine release to release, however
|
||||
in general it will work well alongside an FML or
|
||||
Minecraft Forge installation. FML will detect and ensure the good operation of
|
||||
Optifine (you can see it in your client as an additional data line on the
|
||||
bottom left).
|
||||
|
||||
Client notes
|
||||
============
|
||||
FML does provide a standard pattern for mods to provide HD textures. This is why
|
||||
optifine needed integration code. FML supports HD texture packs
|
||||
up to 128x resolution.
|
||||
|
||||
Mod information
|
||||
===============
|
||||
FML exposes the mod information through a mod list visible on the main screen as
|
||||
well as some small branding enhancements. For full data mods need to provide an
|
||||
information file. This file is a standard format so hopefully tools providing
|
||||
launch capabilities can also leverage this content.
|
||||
|
||||
-------------------------------
|
||||
Binary installation information
|
||||
-------------------------------
|
||||
If you have downloaded a binary zip file you can install it as follows (client
|
||||
or server):
|
||||
|
||||
Installation
|
||||
============
|
||||
To install on a server, simply execute the FML or Forge jar file, with a copy of
|
||||
minecraft_server.jar placed in the same directory. FML will launch it's patched
|
||||
copy.
|
||||
|
||||
To install on a client, FIRST delete META-INF from the minecraft.jar file, then
|
||||
copy the contents of the FML or Forge jar file into it. FML and Forge provide
|
||||
their own META-INF data which should *not* be deleted.
|
||||
|
||||
Forge Installation
|
||||
==================
|
||||
This code also ships as a part of Minecraft Forge. You do not need to install it
|
||||
separately from your Minecraft Forge installation. Minecraft Forge contains the
|
||||
exact same code as this. Generally, you should not install FML if you are also
|
||||
installing Minecraft Forge.
|
||||
|
||||
-------------------------------------------
|
||||
Source installation information for modders
|
||||
-------------------------------------------
|
||||
This code follows the Minecraft Forge installation methodology. It will apply
|
||||
some small patches to your MCP extracted source code, giving you and it access
|
||||
to some of the data and functions you need to build a successful mod.
|
||||
|
||||
Note also that the patches are built against "unrenamed" MCP source code- this
|
||||
means that you will not be able to read them directly against normal code.
|
||||
|
||||
Source pack installation information:
|
||||
|
||||
Standalone source installation
|
||||
==============================
|
||||
|
||||
To install this source code for development purposes, extract this zip file
|
||||
into an mcp installation containing vanilla jars only. It should create a new
|
||||
folder "fml" inside that installation.
|
||||
|
||||
Be sure that you have both vanilla client jars, and the vanilla server jar installed
|
||||
as per the MCP install instructions. Both are required for FML to run properly.
|
||||
|
||||
Once extracted, run the install.sh or install.bat script provided from the fml
|
||||
directory. It should decompile and patch your MCP source code for the client
|
||||
and server.
|
||||
|
||||
Forge source installation
|
||||
=========================
|
||||
MinecraftForge ships with this code and installs it as part of the forge
|
||||
installation process, no further action is required on your part.
|
||||
|
||||
For reference this is version @MAJOR@.@MINOR@.@REV@.@BUILD@ of FML
|
||||
for Minecraft version @MCVERSION@.
|
||||
+88
-1641
File diff suppressed because it is too large.
Load diff
@@ -1,3 +0,0 @@
|
||||
/.gradle/
|
||||
/build/
|
||||
/out/
|
||||
@@ -1,12 +0,0 @@
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.5'
|
||||
implementation 'org.ow2.asm:asm-tree:9.5'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.+'
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class BytecodeFinder extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getJar()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
BytecodeFinder() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
|
||||
def outputFile = output.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
pre()
|
||||
|
||||
jar.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
def node = new ClassNode(Opcodes.ASM9)
|
||||
new ClassReader(zin).accept(node, 0)
|
||||
process(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post()
|
||||
outputFile.text = new JsonBuilder(getData()).toPrettyString()
|
||||
}
|
||||
|
||||
|
||||
protected process(ClassNode node) {
|
||||
if (node.fields != null) node.fields.each { process(node, it) }
|
||||
if (node.methods != null) node.methods.each { process(node, it) }
|
||||
}
|
||||
|
||||
protected pre() {}
|
||||
protected process(ClassNode parent, FieldNode node) {}
|
||||
protected process(ClassNode parent, MethodNode node) {}
|
||||
protected post() {}
|
||||
protected abstract Object getData()
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
public class ClosureHelper {
|
||||
BiConsumer<String, Closure> callback
|
||||
|
||||
public ClosureHelper(Closure cl, BiConsumer<String, Closure> callback) {
|
||||
this.callback = callback
|
||||
apply(this, cl)
|
||||
}
|
||||
|
||||
|
||||
def methodMissing(String name, Object args) {
|
||||
if (!args.class.isArray()) return
|
||||
Object[] aargs = (Object[])args
|
||||
|
||||
if (aargs.length == 1 && aargs[0] instanceof Closure) {
|
||||
this.callback.accept(name, (Closure)aargs[0])
|
||||
} else {
|
||||
throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this)
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T apply(T obj, Closure cl) {
|
||||
cl.delegate = obj
|
||||
cl.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
cl()
|
||||
return obj
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class Crowdin extends DefaultTask {
|
||||
@Input abstract Property<String> getId()
|
||||
@Input @Optional abstract Property<String> getKey()
|
||||
@Input boolean json = true
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getExport()
|
||||
|
||||
Crowdin() {
|
||||
outputs.upToDateWhen{ false }
|
||||
id.convention('minecraft-forge')
|
||||
output.convention(project.layout.buildDirectory.dir(name).map {it.file("output.zip") })
|
||||
export.convention(project.layout.buildDirectory.dir(name).map {it.file("export.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
File outputFile = output.get().asFile
|
||||
File exportFile = export.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
if (!key.isPresent())
|
||||
return
|
||||
String key = this.key.get()
|
||||
String id = this.id.get()
|
||||
|
||||
// Force an export
|
||||
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
|
||||
exportFile.withOutputStream { it << i }
|
||||
}
|
||||
|
||||
if (!exportFile.text.contains('success')) {
|
||||
throw new RuntimeException("Crowdin export failed, see ${exportFile} for more info")
|
||||
}
|
||||
|
||||
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
outputFile.withOutputStream { out ->
|
||||
new ZipOutputStream(out).withCloseable { zout ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.isDirectory()) {
|
||||
zout.putNextEntry(new ZipEntry(zein.name))
|
||||
} else {
|
||||
// 1.13+ uses json
|
||||
if (zein.name.endsWith('.json') == json) {
|
||||
ZipEntry zeout = new ZipEntry(json ? zein.name.toLowerCase() : zein.name)
|
||||
zeout.time = 1
|
||||
zout.putNextEntry(zeout)
|
||||
zout << zin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class DownloadLibraries extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInput()
|
||||
@OutputDirectory abstract DirectoryProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getLibrariesOutput()
|
||||
|
||||
DownloadLibraries() {
|
||||
output.convention(project.layout.buildDirectory.dir(name))
|
||||
librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt')))
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
Util.init()
|
||||
File outputDir = output.get().asFile
|
||||
def libraries = new ArrayList()
|
||||
|
||||
def json = input.get().asFile.json().libraries.each { lib ->
|
||||
//TODO: Thread?
|
||||
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
|
||||
artifacts.each{ art ->
|
||||
def target = new File(outputDir, art.path)
|
||||
libraries.add(target.absolutePath)
|
||||
if (!target.exists() || !art.sha1.equals(target.sha1())) {
|
||||
project.logger.lifecycle("Downloading ${art.url}")
|
||||
if (!target.parentFile.exists()) {
|
||||
target.parentFile.mkdirs()
|
||||
}
|
||||
new URL(art.url).withInputStream { i ->
|
||||
target.withOutputStream { it << i }
|
||||
}
|
||||
if (!art.sha1.equals(target.sha1())) {
|
||||
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Files.write(librariesOutput.get().asFile.toPath(), libraries)
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.TreeMap
|
||||
import java.util.TreeSet
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*
|
||||
|
||||
abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
@Nested
|
||||
Map<String, Search> fields = [:] as HashMap
|
||||
@Internal
|
||||
Map<Search, String> fieldsReverse = [:] as HashMap
|
||||
@Internal
|
||||
Map<String, Set<ObjectTarget>> targets = [:] as TreeMap
|
||||
|
||||
@Override
|
||||
protected pre() {
|
||||
//fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
def last = null
|
||||
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
|
||||
for (int x = 0; x < node.instructions.size(); x++) {
|
||||
def current = node.instructions.get(x)
|
||||
if (current.opcode == IF_ACMPEQ || current.opcode == IF_ACMPNE) {
|
||||
if (last != null && (last.opcode == GETSTATIC || last.opcode == GETFIELD)) {
|
||||
def target = new Search(cls: last.owner, name: last.name)
|
||||
def wanted = fieldsReverse.get(target)
|
||||
def original = fields.get(wanted)
|
||||
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
|
||||
if (wanted != null && (original.blacklist == null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
|
||||
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
last = current
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
def ret = [:] as HashMap
|
||||
targets.forEach{ k, v ->
|
||||
def e = fields.get(k)
|
||||
ret[k] = [
|
||||
cls: e.cls,
|
||||
name: e.name,
|
||||
replacement: e.replacement,
|
||||
targets: v
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
|
||||
public static class Search {
|
||||
@Input
|
||||
String cls
|
||||
|
||||
@Input
|
||||
String name
|
||||
|
||||
@Input
|
||||
String replacement
|
||||
|
||||
@Nested
|
||||
@Optional
|
||||
Set<ObjectTarget> blacklist
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return cls + '.' + name
|
||||
}
|
||||
|
||||
def blacklist(def owner, def name, def desc) {
|
||||
if (blacklist == null)
|
||||
blacklist = new HashSet()
|
||||
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
|
||||
}
|
||||
def blacklist(def owner) {
|
||||
blacklist(owner, '', '')
|
||||
}
|
||||
}
|
||||
|
||||
def fields(Closure cl) {
|
||||
new ClosureHelper(cl, {name, ccl ->
|
||||
def search = ClosureHelper.apply(new Search(), ccl)
|
||||
this.fields.put(name, search)
|
||||
this.fieldsReverse.put(search, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
@CompileStatic
|
||||
@TupleConstructor
|
||||
final class InheritanceData implements Annotatable {
|
||||
String name
|
||||
int access
|
||||
String superName
|
||||
List<String> interfaces = []
|
||||
Map<String, Method> methods = [:]
|
||||
Map<String, Field> fields = [:]
|
||||
List<Annotation> annotations = []
|
||||
|
||||
@TupleConstructor
|
||||
static final class Method implements Annotatable {
|
||||
int access
|
||||
String override
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Field implements Annotatable {
|
||||
int access
|
||||
String desc
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Annotation {
|
||||
String desc
|
||||
}
|
||||
|
||||
private static final Gson GSON = new Gson()
|
||||
static Map<String, InheritanceData> parse(File file) {
|
||||
try (final reader = file.newReader()) {
|
||||
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
interface Annotatable {
|
||||
List<InheritanceData.Annotation> getAnnotations()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.tasks.bundling.Zip
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.provider.SetProperty
|
||||
|
||||
abstract class InstallerJar extends Zip {
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
|
||||
InstallerJar() {
|
||||
archiveClassifier.set('installer')
|
||||
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
|
||||
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
|
||||
|
||||
def installerJson = project.tasks.installerJson
|
||||
def launcherJson = project.tasks.launcherJson
|
||||
def downloadInstaller = project.tasks.downloadInstaller
|
||||
|
||||
dependsOn(installerJson, launcherJson, downloadInstaller)
|
||||
from(installerJson, launcherJson)
|
||||
|
||||
from(project.rootProject.file('/src/main/resources/url.png'))
|
||||
project.afterEvaluate {
|
||||
from(project.zipTree(downloadInstaller.output)) {
|
||||
duplicatesStrategy = 'exclude'
|
||||
}
|
||||
|
||||
if (!System.env.MAVEN_USER) {
|
||||
dependsOn(project.universalJar)
|
||||
from(project.universalJar) {
|
||||
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
|
||||
}
|
||||
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.getTasks().findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
from(jarTask) {
|
||||
into '/maven/' + jarTask.project.group.replace('.', '/') + '/' + jarTask.archiveBaseName.get() + '/' + jarTask.project.version + '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class InstallerJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@InputFile abstract RegularFileProperty getIcon()
|
||||
@Input abstract Property<String> getLauncherJsonName()
|
||||
@Input abstract Property<String> getLogo()
|
||||
@Input abstract Property<String> getMirrors()
|
||||
@Input abstract Property<String> getWelcome()
|
||||
|
||||
InstallerJson() {
|
||||
getLauncherJsonName().convention('/version.json')
|
||||
getLogo().convention('/big_logo.png')
|
||||
getMirrors().convention('https://files.minecraftforge.net/mirrors-2.0.json')
|
||||
getWelcome().convention("Welcome to the simple ${project.name.capitalize()} installer.")
|
||||
|
||||
getOutput().convention(project.layout.buildDirectory.file('install_profile.json'))
|
||||
|
||||
['client', 'server'].each { side ->
|
||||
['slim', 'extra'].each { type ->
|
||||
def tsk = project.tasks.getByName("download${side.capitalize()}${type.capitalize()}")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
def tsk = project.tasks.getByName("create${side.capitalize()}SRG")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
|
||||
|
||||
project.afterEvaluate {
|
||||
(packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]).forEach {
|
||||
dependsOn(it)
|
||||
input.from it.archiveFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def libs = libraries
|
||||
for (def child : packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]) {
|
||||
def dep = Util.getMavenDep(child)
|
||||
def path = Util.getMavenPath(child)
|
||||
libs.put(dep.toString(), [
|
||||
name: dep,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: child.archiveFile.get().asFile.sha1(),
|
||||
size: child.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
])
|
||||
}
|
||||
json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)}
|
||||
json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath())))
|
||||
json.json = launcherJsonName.get()
|
||||
json.logo = logo.get()
|
||||
if (!mirrors.get().isEmpty())
|
||||
json.mirrorList = mirrors.get()
|
||||
json.welcome = welcome.get()
|
||||
|
||||
Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
import static net.minecraftforge.forge.tasks.Util.getArtifacts
|
||||
import static net.minecraftforge.forge.tasks.Util.iso8601Now
|
||||
|
||||
abstract class LauncherJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
|
||||
@Internal final vanilla = project.project(':mcp').file('build/mcp/downloadJson/version.json')
|
||||
@Internal final timestamp = iso8601Now()
|
||||
@Internal final comment = [
|
||||
"Please do not automate the download and installation of Forge.",
|
||||
"Our efforts are supported by ads from the download page.",
|
||||
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
|
||||
]
|
||||
@Internal final id = "${project.rootProject.ext.MC_VERSION}-${project.name}${project.version.substring(project.rootProject.ext.MC_VERSION.length())}"
|
||||
|
||||
LauncherJson() {
|
||||
getOutput().convention(project.layout.buildDirectory.file('version.json'))
|
||||
|
||||
dependsOn(':fmlloader:jar', 'universalJar')
|
||||
getInput().from(project.tasks.universalJar.archiveFile,
|
||||
project.project(':fmlloader').jar.archiveFile,
|
||||
vanilla)
|
||||
|
||||
project.afterEvaluate {
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.tasks.findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
input.from jarTask.archiveFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
if (!json.libraries)
|
||||
json.libraries = []
|
||||
def libs = [:]
|
||||
getArtifacts(project, project.configurations.installer, false).each { key, lib -> libs[key] = lib }
|
||||
getArtifacts(project, project.configurations.moduleonly, false).each { key, lib -> libs[key] = lib }
|
||||
|
||||
packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) }.forEach {
|
||||
def path = Util.getMavenPath(it)
|
||||
def key = Util.getMavenDep(it)
|
||||
|
||||
libs[key] = [
|
||||
name: key,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: it.archiveFile.get().asFile.sha1(),
|
||||
size: it.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
libs.each { key, lib -> json.libraries.add(lib) }
|
||||
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class MergeJars extends DefaultTask {
|
||||
MergeJars() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def jars = inputJars.files
|
||||
|
||||
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
|
||||
for (def jar : jars) {
|
||||
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
|
||||
def entry
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
ZipEntry _new = new ZipEntry(entry.getName())
|
||||
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
|
||||
zout.putNextEntry(_new)
|
||||
IOUtils.copy(zin, zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
abstract ConfigurableFileCollection getInputJars()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getOutput()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.gradle.api.tasks.Optional
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class ObjectTarget implements Comparable<ObjectTarget> {
|
||||
@Input
|
||||
String owner
|
||||
|
||||
@Input
|
||||
String name
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
String desc
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
if (desc == null)
|
||||
return owner + '.' + name
|
||||
return owner + '.' + name + desc
|
||||
}
|
||||
|
||||
@Override
|
||||
int compareTo(ObjectTarget o) {
|
||||
return toString().compareTo(o.toString())
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
SetupCheckJarCompatibility() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() != null
|
||||
}
|
||||
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
|
||||
|
||||
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def inputVersion = inputVersion.get()
|
||||
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
|
||||
def artifactId = it.split(':')[1]
|
||||
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
|
||||
}.toArray(Dependency[]::new))
|
||||
|
||||
project.tasks.named('checkJarCompatibility') {
|
||||
baseLibraries.from(project.provider {
|
||||
fmlLibs.resolvedConfiguration.lenientConfiguration.files
|
||||
})
|
||||
}
|
||||
|
||||
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
|
||||
dest baseForgeUserdev
|
||||
}
|
||||
|
||||
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
|
||||
|
||||
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getInputVersion()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getBaseBinPatchesOutput()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import groovy.transform.CompileStatic
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import javax.annotation.Nullable
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@CompileStatic
|
||||
class TeamcityRequests {
|
||||
public static final Gson GSON = new Gson()
|
||||
|
||||
@Nullable
|
||||
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
|
||||
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
|
||||
conn.setRequestProperty('Accept', 'application/json')
|
||||
conn.setReadTimeout(5 * 1000)
|
||||
conn.setConnectTimeout(5 * 1000)
|
||||
conn.connect()
|
||||
|
||||
if (conn.responseCode !== 200) {
|
||||
return null
|
||||
}
|
||||
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
GSON.fromJson(new InputStreamReader(is), clazz.getType())
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Map<String, Build> buildsByCommit() throws IOException {
|
||||
final Map<String, Build> builds = [:]
|
||||
jsonRequest(new TypeToken<Builds>() {}, "https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),count:300,status:SUCCESS")
|
||||
?.build?.forEach {
|
||||
it.revisions.revision.each { rev -> builds[rev.version] = it }
|
||||
}
|
||||
return builds
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String attemptFindBase(File gitPath) {
|
||||
try (final git = Git.open(gitPath)) {
|
||||
final Map<String, Build> buildByCommit = buildsByCommit()
|
||||
for (final commit in git.log().setMaxCount(100).call()) {
|
||||
// Find the first commit which was built on CI
|
||||
final build = buildByCommit[commit.id.name]
|
||||
if (build) {
|
||||
return build.number
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return null
|
||||
}
|
||||
|
||||
static final class Builds {
|
||||
public List<Build> build
|
||||
}
|
||||
|
||||
static final class Build {
|
||||
public String number
|
||||
public Revisions revisions
|
||||
}
|
||||
|
||||
static final class Revisions {
|
||||
public int count
|
||||
public List<Revision> revision
|
||||
}
|
||||
|
||||
static final class Revision {
|
||||
public String version
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
public class Util {
|
||||
static void init() {
|
||||
File.metaClass.sha1 = { ->
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
delegate.eachByte 4096, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
|
||||
|
||||
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
|
||||
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
|
||||
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
|
||||
|
||||
Date.metaClass.iso8601 = { ->
|
||||
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
def result = format.format(delegate)
|
||||
return result[0..21] + ':' + result[22..-1]
|
||||
}
|
||||
|
||||
String.metaClass.rsplit = { String del, int limit = -1 ->
|
||||
def lst = new ArrayList()
|
||||
def x = 0, idx
|
||||
def tmp = delegate
|
||||
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit == -1 || x++ < limit)) {
|
||||
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
|
||||
tmp = tmp.substring(0, idx)
|
||||
}
|
||||
lst.add(0, tmp)
|
||||
return lst
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getClasspath(project, libs, artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
libs[lib.name] = lib
|
||||
if (lib.name != artifact)
|
||||
ret.add(lib.name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getArtifacts(project, config, classifiers) {
|
||||
def ret = [:]
|
||||
config.resolvedConfiguration.resolvedArtifacts.each {
|
||||
def art = [
|
||||
group: it.moduleVersion.id.group,
|
||||
name: it.moduleVersion.id.name,
|
||||
version: it.moduleVersion.id.version,
|
||||
classifier: it.classifier,
|
||||
extension: it.extension,
|
||||
file: it.file
|
||||
]
|
||||
def key = art.group + ':' + art.name
|
||||
def folder = "${art.group.replace('.', '/')}/${art.name}/${art.version}/"
|
||||
def filename = "${art.name}-${art.version}"
|
||||
if (art.classifier != null)
|
||||
filename += "-${art.classifier}"
|
||||
filename += ".${art.extension}"
|
||||
def path = "${folder}${filename}"
|
||||
def url = "https://libraries.minecraft.net/${path}"
|
||||
if (!checkExists(url)) {
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
//TODO remove when Mojang launcher is updated
|
||||
if (!classifiers && art.classifier != null) {
|
||||
//Mojang launcher doesn't currently support classifiers, so... move it to part of the version, and force the extension to 'jar'
|
||||
// However, keep the path normal so that our mirror system works.
|
||||
art.version = "${art.version}-${art.classifier}"
|
||||
art.classifier = null
|
||||
art.extension = 'jar'
|
||||
}
|
||||
ret[key] = [
|
||||
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: url,
|
||||
sha1: sha1(art.file),
|
||||
size: art.file.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getMavenPath(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
|
||||
}
|
||||
|
||||
public static def getMavenDep(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
}
|
||||
|
||||
public static def iso8601Now() { new Date().iso8601() }
|
||||
|
||||
public static def sha1(file) {
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
file.eachByte 4096, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
|
||||
private static def artifactTree(project, artifact) {
|
||||
if (!project.ext.has('tree_resolver'))
|
||||
project.ext.tree_resolver = 1
|
||||
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
|
||||
def dep = project.dependencies.create(artifact)
|
||||
cfg.dependencies.add(dep)
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
requestMethod = 'HEAD'
|
||||
connect()
|
||||
responseCode
|
||||
}
|
||||
return code == 200
|
||||
} catch (Exception e) {
|
||||
if (e.toString().contains('unable to find valid certification path to requested target'))
|
||||
throw new RuntimeException('Failed to connect to ' + url + ': Missing certificate root authority, try updating java')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
static String getLatestForgeVersion(mcVersion) {
|
||||
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
|
||||
final ver = json.promos["$mcVersion-latest"]
|
||||
ver === null ? null : (mcVersion + '-' + ver)
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import net.minecraftforge.srgutils.IMappingFile
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckATs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
ats.each {
|
||||
final lines = process(it, reporter, inheritance)
|
||||
if (fix) {
|
||||
it.text = joinBack(lines, inheritance, mappings).join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
|
||||
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
|
||||
final Map<String, ATParser.Entry> constructorGroups = [:]
|
||||
|
||||
final itr = lines.entrySet().iterator()
|
||||
final toRemove = []
|
||||
while (itr.hasNext()) {
|
||||
final next = itr.next()
|
||||
String key = next.key
|
||||
final entry = next.value
|
||||
if (entry === null) continue
|
||||
|
||||
final binaryName = entry.cls.replaceAll('\\.', '/')
|
||||
|
||||
// Process Groups, this will remove any entries outside the group that is covered by the group
|
||||
if (entry.group) {
|
||||
final jcls = inheritance[binaryName]
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group: $key")
|
||||
} else if ('*' == entry.desc) {
|
||||
if (!jcls.fields) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no fields: $key")
|
||||
} else {
|
||||
jcls.fields.each { field, value ->
|
||||
final fkey = entry.cls + ' ' + field
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
} else if (!entry.existing.contains(fkey)) {
|
||||
reporter.report("Missing group entry: $fkey")
|
||||
}
|
||||
entry.children.add(fkey)
|
||||
} else if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
reporter.report("Found invalid group entry: $fkey")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('*()' == entry.desc) {
|
||||
if (!jcls.methods) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no methods: $key")
|
||||
} else {
|
||||
jcls.methods.each { mtd, value ->
|
||||
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
|
||||
key = entry.cls + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
} else if (!entry.existing.contains(key)) {
|
||||
reporter.report("Missing group entry: $key")
|
||||
}
|
||||
entry.children.add(key)
|
||||
} else if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
reporter.report("Found invalid group entry: $key")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
|
||||
constructorGroups.put(binaryName, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Process normal lines, remove invalid and remove narrowing
|
||||
else {
|
||||
def jcls = inheritance.get(binaryName)
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else if (entry.desc == '') {
|
||||
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
} else if (!entry.desc.contains('(')) {
|
||||
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.fields[entry.desc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final jdesc = entry.desc.replace('(', ' (')
|
||||
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.methods[jdesc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inheritance.each { tcls, value ->
|
||||
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
|
||||
String parent = tcls
|
||||
while (parent !== null) {
|
||||
constructorGroups[parent]?.tap { entry ->
|
||||
value.methods.each { mtd, v ->
|
||||
if (mtd.startsWith('<init>')) {
|
||||
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(v.access) < 3) {
|
||||
if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
} else if (child !in entry.existing) {
|
||||
reporter.report("Missing group entry: $child")
|
||||
}
|
||||
entry.children.add(child)
|
||||
} else if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
reporter.report("Found invalid group entry: $child")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = inheritance[parent]?.superName
|
||||
}
|
||||
}
|
||||
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
|
||||
|
||||
toRemove.each(lines.&remove)
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
|
||||
final List<String> data = []
|
||||
final remapComment = { ATParser.Entry entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
final comment = entry.comment?.substring(1)?.trim()
|
||||
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
|
||||
final mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (mappingsClass === null) return entry.comment
|
||||
final idx = entry.desc.indexOf('(')
|
||||
|
||||
String mappedName = idx == -1
|
||||
? mappingsClass.remapField(entry.desc)
|
||||
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
|
||||
if (!mappedName) return entry.comment
|
||||
if (mappedName == '<init>')
|
||||
mappedName = 'constructor'
|
||||
|
||||
if (comment?.startsWith(mappedName))
|
||||
return '# ' + comment
|
||||
if (comment && comment.indexOf(' ') !== -1) {
|
||||
def split = comment.split(' - ').toList()
|
||||
if (split[0].indexOf(' ') !== -1)
|
||||
// The first string is more than one word, so append before it
|
||||
return "# ${mappedName} - ${comment}"
|
||||
split.remove(0)
|
||||
return "# ${mappedName} - ${String.join(' - ', split)}"
|
||||
}
|
||||
return '# ' + mappedName
|
||||
}
|
||||
lines.each { key, value ->
|
||||
if (!value.group) {
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
|
||||
value.children.each {
|
||||
final line = value.modifier + ' ' + it
|
||||
final entry = ATParser.parseEntry(line)
|
||||
final comment = remapComment(entry)
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
static int accessStr(String access) {
|
||||
if (access.endsWith('-f') || access.endsWith('+f')) return 4
|
||||
switch (access.toLowerCase()) {
|
||||
case 'public': return 3
|
||||
case 'protected': return 2
|
||||
case 'default': return 1
|
||||
case 'private': return 0
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
|
||||
static int accessLevel(int access) {
|
||||
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
|
||||
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
|
||||
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
class ATParser {
|
||||
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
|
||||
TreeMap<String, Entry> outLines = new TreeMap<>()
|
||||
Entry group = null
|
||||
for (final line : lines) {
|
||||
if (line.isEmpty()) continue
|
||||
if (line.startsWith('#group ')) {
|
||||
final entry = parseEntry(line.substring(7))
|
||||
|
||||
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
|
||||
reporter.report("Invalid group: $line", false)
|
||||
}
|
||||
|
||||
entry.group = true
|
||||
entry.children = []
|
||||
entry.existing = []
|
||||
|
||||
group = entry
|
||||
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Duplicate group: $line", false)
|
||||
}
|
||||
|
||||
outLines[entry.key] = group
|
||||
} else if (group !== null) {
|
||||
if (line.startsWith('#endgroup')) {
|
||||
group = null
|
||||
} else {
|
||||
final key = parseEntry(line).key
|
||||
group.existing.add(key)
|
||||
}
|
||||
} else if (line.startsWith('#endgroup')) {
|
||||
reporter.report("Invalid group ending: $line", false)
|
||||
} else if (line.startsWith('#')) {
|
||||
//Nom
|
||||
} else {
|
||||
final entry = parseEntry(line)
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Found duplicate: $line")
|
||||
continue
|
||||
}
|
||||
outLines[entry.key] = entry
|
||||
}
|
||||
}
|
||||
return outLines
|
||||
}
|
||||
|
||||
static Entry parseEntry(String line) {
|
||||
final idx = line.indexOf('#')
|
||||
final String comment = idx === -1 ? null : line.substring(idx)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
final data = (line.trim() + ' ').split(' ', -1)
|
||||
new Entry(data[0], data[1], data[2], comment)
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Entry {
|
||||
String modifier, cls, desc, comment
|
||||
|
||||
Set<String> existing
|
||||
TreeSet<String> children
|
||||
boolean group = false
|
||||
|
||||
@Lazy
|
||||
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
|
||||
|
||||
Object getAt(String key) {
|
||||
return getProperty(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class CheckExcs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getBinary()
|
||||
@InputFiles abstract ConfigurableFileCollection getExcs()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final Set<String> known = []
|
||||
collectKnown(known)
|
||||
|
||||
excs.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
|
||||
def (String key, String value) = line.split('=', 2)
|
||||
if (!known.contains(key)) {
|
||||
reporter.report("Unknown: $line")
|
||||
return
|
||||
}
|
||||
|
||||
String desc = key.split('\\.', 2)[1]
|
||||
if (!desc.contains('(')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
desc = '(' + desc.split('\\(', 2)[1]
|
||||
|
||||
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
|
||||
|
||||
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.sort().join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
private void collectKnown(Collection<String> known) {
|
||||
binary.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
final visitor = new ClassVisitor(Opcodes.ASM9) {
|
||||
private String cls
|
||||
@Override
|
||||
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.cls = name
|
||||
}
|
||||
|
||||
@Override
|
||||
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
known.add(this.cls + '.' + name + descriptor)
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
ClassReader reader = new ClassReader(zin)
|
||||
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
@CompileStatic
|
||||
enum CheckMode {
|
||||
CHECK,
|
||||
FIX
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.Optional
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckPatches extends CheckTask {
|
||||
|
||||
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
|
||||
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
|
||||
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
|
||||
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
|
||||
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
|
||||
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
|
||||
|
||||
@InputDirectory abstract DirectoryProperty getPatchDir()
|
||||
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final patchDir = getPatchDir().get().asFile.toPath()
|
||||
Files.walk(patchDir).withCloseable {
|
||||
it.filter(Files.&isRegularFile).forEach { path ->
|
||||
final String relativeName = patchDir.relativize(path).toString()
|
||||
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
|
||||
final oldFixedErrors = reporter.fixed.size()
|
||||
|
||||
final lines = Files.readAllLines(patch)
|
||||
|
||||
int hunksStart = 0
|
||||
boolean onlyWhiteSpace = false
|
||||
|
||||
final List<String> newLines = []
|
||||
|
||||
// First two lines are file name ++/-- and we do not care
|
||||
newLines.add(lines[0] + '\n')
|
||||
newLines.add(lines[1] + '\n')
|
||||
|
||||
int i
|
||||
for (i = 2; i < lines.size(); ++i) {
|
||||
def line = lines[i]
|
||||
newLines.add(line + '\n')
|
||||
|
||||
if (HUNK_START_PATTERN.matcher(line).find()) {
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
int toRemove = i - hunksStart
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
hunksStart = i
|
||||
onlyWhiteSpace = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.startsWithAny('+','-')) {
|
||||
def prefixChange = false
|
||||
def prevLine = lines[i - 1]
|
||||
|
||||
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
|
||||
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
|
||||
def currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (prevTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
|
||||
def pMatcher = FIELD_PATTERN.matcher(prevLine)
|
||||
def cMatcher = FIELD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // = ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // field name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = METHOD_PATTERN.matcher(prevLine)
|
||||
cMatcher = METHOD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // params
|
||||
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = CLASS_PATTERN.matcher(prevLine)
|
||||
cMatcher = CLASS_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // class | interface
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
|
||||
final nextLine = lines[i + 1]
|
||||
if (nextLine.charAt(0) == (char)'+') {
|
||||
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
|
||||
final currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (nextTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
|
||||
|
||||
if (!prefixChange && !isWhiteSpaceChange) {
|
||||
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
|
||||
} else if (isWhiteSpaceChange) {
|
||||
final prevLineChange = prevLine.startsWithAny('+','-')
|
||||
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
|
||||
|
||||
if (!prevLineChange && !nextLineChange) {
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains('\t')) {
|
||||
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
|
||||
line = line.replaceAll('\t', ' ')
|
||||
newLines.remove(newLines.size() - 1)
|
||||
newLines.add(line + '\n')
|
||||
}
|
||||
|
||||
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
|
||||
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
def toRemove = i - hunksStart;
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
|
||||
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
|
||||
if (newLines.size() <= 2) {
|
||||
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
|
||||
Files.delete(patch)
|
||||
}
|
||||
else {
|
||||
if (!hasS2SArtifact)
|
||||
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
|
||||
Files.newBufferedWriter(patch).withCloseable {
|
||||
newLines.each { l -> it.write(l) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.forge.tasks.Annotatable
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckSAS extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getSass()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
sass.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
|
||||
final idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
lines.add(line)
|
||||
return
|
||||
}
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
|
||||
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
|
||||
cls = cls.replaceAll('\\.', '/')
|
||||
|
||||
if (inheritance[cls] === null) {
|
||||
reporter.report("Invalid: $line")
|
||||
} else if (name.isEmpty()) { //Class SAS
|
||||
final toAdd = []
|
||||
final clsSided = isSided(inheritance[cls])
|
||||
boolean sided = clsSided
|
||||
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null) {
|
||||
for (entry in json[cls]['fields']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key)
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods) {
|
||||
for (entry in clsInh.methods) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
} else if (clsSided) {
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sided) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
lines.addAll(toAdd.sort())
|
||||
} else {
|
||||
reporter.report("Invalid: $line")
|
||||
}
|
||||
|
||||
} else if (desc.isEmpty()) { // Fields
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
|
||||
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
|
||||
else */
|
||||
reporter.report("Invalid: $line")
|
||||
} else { // Methods
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
|
||||
reporter.report("Invalid: $line")
|
||||
else {
|
||||
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
protected static isSided(Annotatable annotatable) {
|
||||
if (annotatable === null) return false
|
||||
for (ann in annotatable.annotations) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.forge.tasks.Util
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.VerificationTask
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
@Input
|
||||
abstract Property<CheckMode> getMode()
|
||||
|
||||
private boolean ignoreFailures = false
|
||||
|
||||
@Input
|
||||
@Override
|
||||
boolean getIgnoreFailures() {
|
||||
return ignoreFailures
|
||||
}
|
||||
|
||||
@Override
|
||||
void setIgnoreFailures(boolean ignoreFailures) {
|
||||
this.ignoreFailures = ignoreFailures
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
Util.init()
|
||||
|
||||
final doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
if (reporter.messages) {
|
||||
if (getMode().get() === CheckMode.CHECK) {
|
||||
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
|
||||
}
|
||||
} else {
|
||||
if (logger.isEnabled(LogLevel.DEBUG)) {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
|
||||
} else {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
|
||||
}
|
||||
|
||||
if (reporter.notFixed) {
|
||||
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract void check(Reporter reporter, boolean fix)
|
||||
|
||||
@CompileStatic
|
||||
static final class Reporter {
|
||||
final boolean trackFixed
|
||||
Reporter(boolean trackFixed) {
|
||||
this.trackFixed = trackFixed
|
||||
}
|
||||
|
||||
public final List<String> messages = []
|
||||
|
||||
public final List<String> fixed = []
|
||||
public final List<String> notFixed = []
|
||||
|
||||
void report(String message, boolean canBeFixed = true) {
|
||||
messages.add(message)
|
||||
|
||||
if (trackFixed) {
|
||||
if (canBeFixed) {
|
||||
fixed.add(message)
|
||||
} else {
|
||||
notFixed.add(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
|
||||
taskName = taskName.capitalize()
|
||||
tasks.register("check$taskName", clazz) { CheckTask task ->
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.CHECK)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
|
||||
|
||||
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.FIX)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Artistic Style format configuration
|
||||
# see http://astyle.sourceforge.net/astyle.html
|
||||
|
||||
style=allman
|
||||
|
||||
add-brackets
|
||||
break-closing-brackets
|
||||
|
||||
indent-switches
|
||||
|
||||
max-instatement-indent=40
|
||||
|
||||
pad-oper
|
||||
pad-header
|
||||
unpad-paren
|
||||
|
||||
break-blocks
|
||||
|
||||
delete-empty-lines
|
||||
+8690
File diff suppressed because it is too large.
Load diff
+5264
File diff suppressed because it is too large.
Load diff
+16738
File diff suppressed because it is too large.
Load diff
+21464
File diff suppressed because it is too large.
Load diff
+165
@@ -0,0 +1,165 @@
|
||||
[DEFAULT]
|
||||
DirTemp = temp
|
||||
DirSrc = src
|
||||
DirLogs = logs
|
||||
DirBin = bin
|
||||
DirJars = jars
|
||||
DirReobf = reobf
|
||||
DirConf = conf
|
||||
DirRuntime = runtime
|
||||
DirLib = lib
|
||||
DirTempSrc = temp/src
|
||||
DirTempCls = temp/cls
|
||||
DirTempBin = temp/bin
|
||||
DirModSrc = modsrc
|
||||
DirEclipse = eclipse
|
||||
|
||||
[CSV]
|
||||
Classes = %(DirConf)s/classes.csv
|
||||
Methods = %(DirConf)s/methods.csv
|
||||
Fields = %(DirConf)s/fields.csv
|
||||
Params = %(DirConf)s/params.csv
|
||||
NewIds = %(DirConf)s/newids.csv
|
||||
|
||||
[SRGS]
|
||||
ConfClient = %(DirConf)s/client.srg
|
||||
ConfServer = %(DirConf)s/server.srg
|
||||
Client = %(DirTemp)s/client_rg.srg
|
||||
Server = %(DirTemp)s/server_rg.srg
|
||||
DeobfClient = %(DirTemp)s/client_deobf.srg
|
||||
DeobfServer = %(DirTemp)s/server_deobf.srg
|
||||
ReobfClient = %(DirTemp)s/client_ro.srg
|
||||
ReobfServer = %(DirTemp)s/server_ro.srg
|
||||
ReobfClientSrg = %(DirTemp)s/client_ro_srg.srg
|
||||
ReobfServerSrg = %(DirTemp)s/server_ro_srg.srg
|
||||
|
||||
[JAR]
|
||||
DirNatives = %(DirJars)s/bin/natives
|
||||
Client = %(DirJars)s/bin/minecraft.jar
|
||||
Server = %(DirJars)s/minecraft_server.jar
|
||||
LWJGL = %(DirJars)s/bin/jinput.jar,%(DirJars)s/bin/lwjgl.jar,%(DirJars)s/bin/lwjgl_util.jar
|
||||
MD5Client = 7a304554fdea879a799efe6eaedd8c95
|
||||
MD5Server = c48168f463e935262ea65b702f175153
|
||||
|
||||
[RETROGUARD]
|
||||
Location = %(DirRuntime)s/bin/retroguard.jar
|
||||
RetroConf = %(DirTemp)s/retroguard.cfg
|
||||
RetroReobConf = %(DirTemp)s/retroguard_ro.cfg
|
||||
ClientConf = %(DirTemp)s/client_rg.cfg
|
||||
ServerConf = %(DirTemp)s/server_rg.cfg
|
||||
ClientReobConf = %(DirTemp)s/client_ro.cfg
|
||||
ServerReobConf = %(DirTemp)s/server_ro.cfg
|
||||
ClientOut = %(DirTemp)s/minecraft_rg.jar
|
||||
ServerOut = %(DirTemp)s/minecraft_server_rg.jar
|
||||
ClientLog = %(DirLogs)s/client_rg.log
|
||||
ServerLog = %(DirLogs)s/server_rg.log
|
||||
ClientDeobLog = %(DirLogs)s/client_deob.log
|
||||
ServerDeobLog = %(DirLogs)s/server_deob.log
|
||||
NullPkg = net/minecraft/src
|
||||
|
||||
[EXCEPTOR]
|
||||
XClientJson = %(DirConf)s/exceptor.json
|
||||
XServerJson = %(DirConf)s/exceptor.json
|
||||
XClientCfg = %(DirConf)s/joined.exc
|
||||
XServerCfg = %(DirConf)s/joined.exc
|
||||
XClientOut = %(DirTemp)s/minecraft_exc.jar
|
||||
XServerOut = %(DirTemp)s/minecraft_server_exc.jar
|
||||
XClientLog = %(DirLogs)s/client_exc.log
|
||||
XServerLog = %(DirLogs)s/server_exc.log
|
||||
|
||||
[DECOMPILE]
|
||||
ClsClientTemp = %(DirTempCls)s/minecraft
|
||||
ClsServerTemp = %(DirTempCls)s/minecraft_server
|
||||
SrcClientTemp = %(DirTempSrc)s/minecraft
|
||||
SrcServerTemp = %(DirTempSrc)s/minecraft_server
|
||||
FFSource = net
|
||||
FFClientIn = %(DirTemp)s/minecraft_ff_in.jar
|
||||
FFServerIn = %(DirTemp)s/minecraft_server_ff_in.jar
|
||||
|
||||
[OUTPUT]
|
||||
BinClientTemp = %(DirTempBin)s/minecraft
|
||||
BinServerTemp = %(DirTempBin)s/minecraft_server
|
||||
SrcClient = %(DirSrc)s/minecraft
|
||||
SrcServer = %(DirSrc)s/minecraft_server
|
||||
TestClient = net/minecraft/client/main/Main
|
||||
TestServer = net/minecraft/server/MinecraftServer
|
||||
|
||||
[PATCHES]
|
||||
PatchClient = %(DirConf)s/patches/minecraft.patch
|
||||
PatchServer = %(DirConf)s/patches/minecraft_server.patch
|
||||
PatchTemp = %(DirTemp)s/temp.patch
|
||||
FFPatchClient = %(DirConf)s/patches/minecraft_ff.patch
|
||||
FFPatchServer = %(DirConf)s/patches/minecraft_server_ff.patch
|
||||
PatchClient_osx = %(DirConf)s/patches/minecraft_osx.patch
|
||||
PatchServer_osx = %(DirConf)s/patches/minecraft_server_osx.patch
|
||||
|
||||
[RECOMPILE]
|
||||
BinClient = %(DirBin)s/minecraft
|
||||
BinServer = %(DirBin)s/minecraft_server
|
||||
LogClient = %(DirLogs)s/client_compile.log
|
||||
LogServer = %(DirLogs)s/server_compile.log
|
||||
ClassPathClient = %(DirLib)s/,%(DirLib)s/*,%(DirJars)s/bin/minecraft.jar,%(DirJars)s/bin/jinput.jar,%(DirJars)s/bin/lwjgl.jar,%(DirJars)s/bin/lwjgl_util.jar
|
||||
ClassPathServer = %(DirLib)s/,%(DirLib)s/*,%(DirJars)s/minecraft_server.jar
|
||||
ClientFixes = %(DirConf)s/patches
|
||||
FixStart = Start
|
||||
IgnorePkg = paulscode,com,isom,ibxm,de/matthiasmann/twl,org,javax,argo,gnu,io/netty
|
||||
|
||||
[REOBF]
|
||||
MD5Client = %(DirTemp)s/client.md5
|
||||
MD5Server = %(DirTemp)s/server.md5
|
||||
MD5PreReobfClient = %(DirTemp)s/client_reobf.md5
|
||||
MD5PreReobfServer = %(DirTemp)s/server_reobf.md5
|
||||
RecompJarClient = %(DirTemp)s/client_recomp.jar
|
||||
RecompJarServer = %(DirTemp)s/server_recomp.jar
|
||||
ObfJarClient = %(DirTemp)s/client_reobf.jar
|
||||
ObfJarServer = %(DirTemp)s/server_reobf.jar
|
||||
ReobfDirClient = %(DirReobf)s/minecraft
|
||||
ReobfDirServer = %(DirReobf)s/minecraft_server
|
||||
ClientRoLog = %(DirLogs)s/client_ro.log
|
||||
ServerRoLog = %(DirLogs)s/server_ro.log
|
||||
ReobfClientLog = %(DirLogs)s/client_reob.log
|
||||
ReobfServerLog = %(DirLogs)s/server_reob.log
|
||||
|
||||
[GETMODSOURCE]
|
||||
OutSRCClient = %(DirModSrc)s/minecraft
|
||||
OutSRCServer = %(DirModSrc)s/minecraft_server
|
||||
|
||||
[MCP]
|
||||
LogFile = %(DirLogs)s/mcp.log
|
||||
LogFileErr = %(DirLogs)s/mcperr.log
|
||||
UpdateUrl = http://mcp.ocean-labs.de/files/mcprolling_{version}/
|
||||
IgnoreUpdate = %(DirBin)s,%(DirLib)s,%(DirLogs)s,%(DirModSrc)s,%(DirReobf)s,%(DirSrc)s,%(DirTemp)s,%(DirEclipse)s/Client/bin,%(DirEclipse)s/Server/bin,%(DirJars)s/world,%(DirJars)s/saves,%(DirJars)s/resources
|
||||
RGIndex = 145000
|
||||
ParamIndex = 45000
|
||||
|
||||
[ASTYLE]
|
||||
AstyleConfig = %(DirConf)s/astyle.cfg
|
||||
|
||||
[COMMANDS]
|
||||
Wine = wine
|
||||
Patcher_win = %(DirRuntime)s/bin/applydiff.exe
|
||||
Patcher_linux = patch
|
||||
Patcher_osx = patch
|
||||
Jad_win = %(DirRuntime)s/bin/jad.exe
|
||||
Jad_osx = %(DirRuntime)s/bin/jad-osx
|
||||
AStyle_win = %(DirRuntime)s/bin/astyle.exe
|
||||
AStyle_linux = astyle
|
||||
AStyle_osx = %(DirRuntime)s/bin/astyle-osx
|
||||
JadRetro = %(DirRuntime)s/bin/jadretro.jar
|
||||
Fernflower = %(DirRuntime)s/bin/fernflower.jar
|
||||
Exceptor = %(DirRuntime)s/bin/mcinjector.jar
|
||||
SpecialSource = %(DirRuntime)s/bin/specialsource.jar
|
||||
CmdPatch = %s -p1 -u -i {patchfile} -d {srcdir}
|
||||
CmdJad = %s -b -d {outdir} -dead -o -r -s .java -stat -ff {classes}
|
||||
CmdAStyle = %s --suffix=none --quiet --options={conffile} {classes}
|
||||
CmdRG = %s -cp "{classpath}" RetroGuard -searge {conffile}
|
||||
CmdRGReobf = %s -cp "{classpath}" RetroGuard -notch {conffile}
|
||||
CmdSS = %s -cp "{classpath}" -jar %s -i {injar} -o {outjar} -m {mapfile} --kill-source
|
||||
CmdSSReobf = %s -cp "{classpath}" -jar %s -i {injar} -o {outjar} -r -m {mapfile} -d {identifier} -e %s
|
||||
CmdJadretro = %s -jar %s {targetdir}
|
||||
CmdFernflower = %s -jar %s -din=1 -rbr=0 -dgs=1 -asc=1 -log=WARN {indir} {outdir}
|
||||
CmdExceptor = %s -jar %s --jarIn {input} --jarOut {output} --mapIn {conf} --log {log} --jsonIn {json} --applyMarkers
|
||||
CmdRecomp = %s -Xlint:-options -deprecation -g -source 1.6 -target 1.6 -classpath "{classpath}" -sourcepath {sourcepath} -d {outpath} {pkgs}
|
||||
CmdRecompScala = %s -encoding UTF-8 -deprecation -target:jvm-1.6 -classpath "{classpath}" -sourcepath {sourcepath} -d {outpath} {pkgs}
|
||||
CmdStartSrv = %s -Xincgc -Xms1024M -Xmx1024M -cp "{classpath}" net.minecraft.server.MinecraftServer
|
||||
CmdStartClt = %s -Xincgc -Xms1024M -Xmx1024M -cp "{classpath}" -Djava.library.path={natives} Start
|
||||
+4633
File diff suppressed because it is too large.
Load diff
+1570
File diff suppressed because it is too large.
Load diff
+11740
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,114 @@
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Arrays;
|
||||
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.main.Main;
|
||||
|
||||
public class Start
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
int userIndex = -1;
|
||||
int passIndex = -1;
|
||||
int sessIndex = -1;
|
||||
int versIndex = -1;
|
||||
|
||||
for( int x = 0; x < args.length; x++)
|
||||
{
|
||||
if (args[x].equals("--username")) userIndex = x + 1;
|
||||
else if (args[x].equals("--password")) passIndex = x + 1;
|
||||
else if (args[x].equals("--session")) sessIndex = x + 1;
|
||||
else if (args[x].equals("--version")) versIndex = x + 1;
|
||||
}
|
||||
|
||||
if (userIndex != 0-1 && passIndex != -1 && sessIndex == -1)
|
||||
{
|
||||
String[] session = getSession(args[userIndex], args[passIndex]);
|
||||
if (session != null)
|
||||
{
|
||||
args[userIndex] = session[0];
|
||||
args = concat(args, new String[]{"--session", session[1]});
|
||||
}
|
||||
}
|
||||
|
||||
//Kill the password if its there so it isn't printed to the console.
|
||||
if (passIndex != -1)
|
||||
{
|
||||
args[passIndex-1] = "no_password_for_joo";
|
||||
args[passIndex] = "no_password_for_joo";
|
||||
}
|
||||
|
||||
if (versIndex == -1)
|
||||
{
|
||||
args = concat(args, new String[]{ "--version", "fml_mcp" });
|
||||
}
|
||||
|
||||
Main.main(args);
|
||||
}
|
||||
|
||||
private static String[] getSession(String username, String password) throws UnsupportedEncodingException
|
||||
{
|
||||
String parameters = "http://login.minecraft.net/?user=" + URLEncoder.encode(username, "UTF-8") +
|
||||
"&password=" + URLEncoder.encode(password, "UTF-8") +
|
||||
"&version=" + 13;
|
||||
String result = openUrl(parameters);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
System.out.println("Can't connect to minecraft.net");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!result.contains(":"))
|
||||
{
|
||||
System.out.println("Login Failed: " + result);
|
||||
return null;
|
||||
}
|
||||
String[] values = result.split(":");
|
||||
return new String[]{ values[2].trim(), values[3].trim() };
|
||||
}
|
||||
|
||||
private static String openUrl(String addr)
|
||||
{
|
||||
try
|
||||
{
|
||||
URL url = new URL(addr);
|
||||
java.io.InputStream is;
|
||||
is = url.openConnection().getInputStream();
|
||||
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is));
|
||||
String buf = "";
|
||||
String line = null;
|
||||
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
buf += "\n" + line;
|
||||
}
|
||||
|
||||
reader.close();
|
||||
return buf;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> T[] concat(T[] first, T[] second)
|
||||
{
|
||||
T[] result = Arrays.copyOf(first, first.length + second.length);
|
||||
System.arraycopy(second, 0, result, first.length, second.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\Minecraft.java minecraft_patched\net\minecraft\client\Minecraft.java
|
||||
--- minecraft\net\minecraft\client\Minecraft.java
|
||||
+++ minecraft_patched\net\minecraft\client\Minecraft.java
|
||||
@@ -695,9 +695,7 @@
|
||||
|
||||
while(true) {
|
||||
try {
|
||||
- if(!this.field_71425_J) {
|
||||
- break;
|
||||
- }
|
||||
+ while(this.field_71425_J) {
|
||||
|
||||
if(!this.field_71434_R || this.field_71433_S == null) {
|
||||
try {
|
||||
@@ -711,6 +709,7 @@
|
||||
}
|
||||
|
||||
this.func_71377_b(this.field_71433_S);
|
||||
+ }
|
||||
} catch (MinecraftError var12) {
|
||||
break;
|
||||
} catch (ReportedException var13) {
|
||||
@@ -0,0 +1,12 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\Minecraft.java minecraft_patched\net\minecraft\client\Minecraft.java
|
||||
--- minecraft\net\minecraft\client\Minecraft.java
|
||||
+++ minecraft_patched\net\minecraft\client\Minecraft.java
|
||||
@@ -695,7 +695,7 @@
|
||||
|
||||
while(true) {
|
||||
try {
|
||||
- if(this.field_71425_J) {
|
||||
+ while(this.field_71425_J) {
|
||||
if(!this.field_71434_R || this.field_71433_S == null) {
|
||||
try {
|
||||
this.func_71411_J();
|
||||
@@ -0,0 +1,21 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\gui\FontRenderer.java minecraft_patched\net\minecraft\client\gui\FontRenderer.java
|
||||
--- minecraft\net\minecraft\client\gui\FontRenderer.java
|
||||
+++ minecraft_patched\net\minecraft\client\gui\FontRenderer.java
|
||||
@@ -581,8 +581,6 @@
|
||||
case 10:
|
||||
--var5;
|
||||
break;
|
||||
- case 32:
|
||||
- var6 = var5;
|
||||
case 167:
|
||||
if(var5 < var3 - 1) {
|
||||
++var5;
|
||||
@@ -596,6 +594,8 @@
|
||||
}
|
||||
}
|
||||
break;
|
||||
+ case 32:
|
||||
+ var6 = var5;
|
||||
default:
|
||||
var4 += this.func_78263_a(var8);
|
||||
if(var7) {
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\network\NetHandlerHandshakeMemory.java minecraft_patched\net\minecraft\client\network\NetHandlerHandshakeMemory.java
|
||||
--- minecraft\net\minecraft\client\network\NetHandlerHandshakeMemory.java
|
||||
+++ minecraft_patched\net\minecraft\client\network\NetHandlerHandshakeMemory.java
|
||||
@@ -31,6 +31,7 @@
|
||||
switch(NetHandlerHandshakeMemory.SwitchEnumConnectionState.field_151263_a[p_147232_2_.ordinal()]) {
|
||||
case 1:
|
||||
this.field_147384_b.func_150719_a(new NetHandlerLoginServer(this.field_147385_a, this.field_147384_b));
|
||||
+ break;
|
||||
case 2:
|
||||
throw new UnsupportedOperationException("NYI");
|
||||
default:
|
||||
@@ -0,0 +1,23 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\particle\EffectRenderer.java minecraft_patched\net\minecraft\client\particle\EffectRenderer.java
|
||||
--- minecraft\net\minecraft\client\particle\EffectRenderer.java
|
||||
+++ minecraft_patched\net\minecraft\client\particle\EffectRenderer.java
|
||||
@@ -53,7 +53,8 @@
|
||||
}
|
||||
|
||||
public void func_78868_a() {
|
||||
- for(final int var1 = 0; var1 < 4; ++var1) {
|
||||
+ for(int var11 = 0; var11 < 4; ++var11) {
|
||||
+ final int var1 = var11;
|
||||
for(int var2 = 0; var2 < this.field_78876_b[var1].size(); ++var2) {
|
||||
final EntityFX var3 = (EntityFX)this.field_78876_b[var1].get(var2);
|
||||
|
||||
@@ -99,7 +100,8 @@
|
||||
EntityFX.field_70554_ao = p_78874_1_.field_70137_T + (p_78874_1_.field_70163_u - p_78874_1_.field_70137_T) * (double)p_78874_2_;
|
||||
EntityFX.field_70555_ap = p_78874_1_.field_70136_U + (p_78874_1_.field_70161_v - p_78874_1_.field_70136_U) * (double)p_78874_2_;
|
||||
|
||||
- for(final int var8 = 0; var8 < 3; ++var8) {
|
||||
+ for(int var88 = 0; var88 < 3; ++var88) {
|
||||
+ final int var8 = var88;
|
||||
if(!this.field_78876_b[var8].isEmpty()) {
|
||||
switch(var8) {
|
||||
case 0:
|
||||
@@ -0,0 +1,33 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\renderer\RenderBlocks.java minecraft_patched\net\minecraft\client\renderer\RenderBlocks.java
|
||||
--- minecraft\net\minecraft\client\renderer\RenderBlocks.java
|
||||
+++ minecraft_patched\net\minecraft\client\renderer\RenderBlocks.java
|
||||
@@ -1929,16 +1929,16 @@
|
||||
float var24 = (float)(p_147788_2_ + 1);
|
||||
float var25 = (float)(p_147788_4_ + 0);
|
||||
float var26 = (float)(p_147788_4_ + 1);
|
||||
- boolean var27 = false;
|
||||
+ int var27 = 0;
|
||||
if((var19 || var20) && !var21 && !var22) {
|
||||
- var27 = true;
|
||||
+ var27 = 1;
|
||||
}
|
||||
|
||||
if((var21 || var22) && !var20 && !var19) {
|
||||
- var27 = true;
|
||||
+ var27 = 2;
|
||||
}
|
||||
|
||||
- if(!var27) {
|
||||
+ if(var27 == 0) {
|
||||
int var28 = 0;
|
||||
int var29 = 0;
|
||||
int var30 = 16;
|
||||
@@ -1985,7 +1985,7 @@
|
||||
var5.func_78374_a((double)var24, (double)p_147788_3_ + 0.015625D, (double)var25, (double)var9.func_94214_a((double)var30), (double)var9.func_94207_b((double)var29));
|
||||
var5.func_78374_a((double)var23, (double)p_147788_3_ + 0.015625D, (double)var25, (double)var9.func_94214_a((double)var28), (double)var9.func_94207_b((double)var29));
|
||||
var5.func_78374_a((double)var23, (double)p_147788_3_ + 0.015625D, (double)var26, (double)var9.func_94214_a((double)var28), (double)var9.func_94207_b((double)var31));
|
||||
- } else if(var27) {
|
||||
+ } else if(var27 == 1) {
|
||||
var5.func_78374_a((double)var24, (double)p_147788_3_ + 0.015625D, (double)var26, (double)var8.func_94212_f(), (double)var8.func_94210_h());
|
||||
var5.func_78374_a((double)var24, (double)p_147788_3_ + 0.015625D, (double)var25, (double)var8.func_94212_f(), (double)var8.func_94206_g());
|
||||
var5.func_78374_a((double)var23, (double)p_147788_3_ + 0.015625D, (double)var25, (double)var8.func_94209_e(), (double)var8.func_94206_g());
|
||||
@@ -0,0 +1,12 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\renderer\RenderGlobal.java minecraft_patched\net\minecraft\client\renderer\RenderGlobal.java
|
||||
--- minecraft\net\minecraft\client\renderer\RenderGlobal.java
|
||||
+++ minecraft_patched\net\minecraft\client\renderer\RenderGlobal.java
|
||||
@@ -1566,7 +1566,7 @@
|
||||
double var15 = this.field_72777_q.field_71451_h.field_70165_t - p_72726_2_;
|
||||
double var17 = this.field_72777_q.field_71451_h.field_70163_u - p_72726_4_;
|
||||
double var19 = this.field_72777_q.field_71451_h.field_70161_v - p_72726_6_;
|
||||
- Object var21 = null;
|
||||
+ EntityFX var21 = null;
|
||||
if(p_72726_1_.equals("hugeexplosion")) {
|
||||
this.field_72777_q.field_71452_i.func_78873_a(var21 = new EntityHugeExplodeFX(this.field_72769_h, p_72726_2_, p_72726_4_, p_72726_6_, p_72726_8_, p_72726_10_, p_72726_12_));
|
||||
} else if(p_72726_1_.equals("largeexplode")) {
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\renderer\texture\TextureManager.java minecraft_patched\net\minecraft\client\renderer\texture\TextureManager.java
|
||||
--- minecraft\net\minecraft\client\renderer\texture\TextureManager.java
|
||||
+++ minecraft_patched\net\minecraft\client\renderer\texture\TextureManager.java
|
||||
@@ -72,13 +72,14 @@
|
||||
|
||||
public boolean func_110579_a(ResourceLocation p_110579_1_, final ITextureObject p_110579_2_) {
|
||||
boolean var3 = true;
|
||||
-
|
||||
+ ITextureObject p_110579_2_2 = p_110579_2_;
|
||||
+
|
||||
try {
|
||||
((ITextureObject)p_110579_2_).func_110551_a(this.field_110582_d);
|
||||
} catch (IOException var8) {
|
||||
field_147646_a.warn("Failed to load texture: " + p_110579_1_, var8);
|
||||
- p_110579_2_ = TextureUtil.field_111001_a;
|
||||
- this.field_110585_a.put(p_110579_1_, p_110579_2_);
|
||||
+ p_110579_2_2 = TextureUtil.field_111001_a;
|
||||
+ this.field_110585_a.put(p_110579_1_, p_110579_2_2);
|
||||
var3 = false;
|
||||
} catch (Throwable var9) {
|
||||
CrashReport var5 = CrashReport.func_85055_a(var9, "Registering texture");
|
||||
@@ -95,7 +96,7 @@
|
||||
throw new ReportedException(var5);
|
||||
}
|
||||
|
||||
- this.field_110585_a.put(p_110579_1_, p_110579_2_);
|
||||
+ this.field_110585_a.put(p_110579_1_, p_110579_2_2);
|
||||
return var3;
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\client\resources\FolderResourcePack.java minecraft_patched\net\minecraft\client\resources\FolderResourcePack.java
|
||||
--- minecraft\net\minecraft\client\resources\FolderResourcePack.java
|
||||
+++ minecraft_patched\net\minecraft\client\resources\FolderResourcePack.java
|
||||
@@ -31,7 +31,7 @@
|
||||
HashSet var1 = Sets.newHashSet();
|
||||
File var2 = new File(this.field_110597_b, "assets/");
|
||||
if(var2.isDirectory()) {
|
||||
- File[] var3 = var2.listFiles(DirectoryFileFilter.DIRECTORY);
|
||||
+ File[] var3 = var2.listFiles((java.io.FileFilter)DirectoryFileFilter.DIRECTORY);
|
||||
int var4 = var3.length;
|
||||
|
||||
for(int var5 = 0; var5 < var4; ++var5) {
|
||||
@@ -0,0 +1,34 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\inventory\ContainerPlayer.java minecraft_patched\net\minecraft\inventory\ContainerPlayer.java
|
||||
--- minecraft\net\minecraft\inventory\ContainerPlayer.java
|
||||
+++ minecraft_patched\net\minecraft\inventory\ContainerPlayer.java
|
||||
@@ -29,7 +29,7 @@
|
||||
this.field_82862_h = p_i1819_3_;
|
||||
this.func_75146_a(new SlotCrafting(p_i1819_1_.field_70458_d, this.field_75181_e, this.field_75179_f, 0, 144, 36));
|
||||
|
||||
- final int var4;
|
||||
+ int var4;
|
||||
int var5;
|
||||
for(var4 = 0; var4 < 2; ++var4) {
|
||||
for(var5 = 0; var5 < 2; ++var5) {
|
||||
@@ -38,6 +38,7 @@
|
||||
}
|
||||
|
||||
for(var4 = 0; var4 < 4; ++var4) {
|
||||
+ final int var44 = var4;
|
||||
this.func_75146_a(new Slot(p_i1819_1_, p_i1819_1_.func_70302_i_() - 1 - var4, 8, 8 + var4 * 18) {
|
||||
|
||||
private static final String __OBFID = "CL_00001755";
|
||||
@@ -46,11 +47,11 @@
|
||||
return 1;
|
||||
}
|
||||
public boolean func_75214_a(ItemStack p_75214_1_) {
|
||||
- return p_75214_1_ == null?false:(p_75214_1_.func_77973_b() instanceof ItemArmor?((ItemArmor)p_75214_1_.func_77973_b()).field_77881_a == var4:(p_75214_1_.func_77973_b() != Item.func_150898_a(Blocks.field_150423_aK) && p_75214_1_.func_77973_b() != Items.field_151144_bL?false:var4 == 0));
|
||||
+ return p_75214_1_ == null?false:(p_75214_1_.func_77973_b() instanceof ItemArmor?((ItemArmor)p_75214_1_.func_77973_b()).field_77881_a == var44:(p_75214_1_.func_77973_b() != Item.func_150898_a(Blocks.field_150423_aK) && p_75214_1_.func_77973_b() != Items.field_151144_bL?false:var44 == 0));
|
||||
}
|
||||
@SideOnly(Side.CLIENT)
|
||||
public IIcon func_75212_b() {
|
||||
- return ItemArmor.func_94602_b(var4);
|
||||
+ return ItemArmor.func_94602_b(var44);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\network\PacketBuffer.java minecraft_patched\net\minecraft\network\PacketBuffer.java
|
||||
--- minecraft\net\minecraft\network\PacketBuffer.java
|
||||
+++ minecraft_patched\net\minecraft\network\PacketBuffer.java
|
||||
@@ -713,10 +713,4 @@
|
||||
public boolean release(int p_release_1_) {
|
||||
return this.field_150794_a.release(p_release_1_);
|
||||
}
|
||||
-
|
||||
- // $FF: synthetic method
|
||||
- // $FF: bridge method
|
||||
- public int compareTo(Object p_compareTo_1_) {
|
||||
- return this.compareTo((ByteBuf)p_compareTo_1_);
|
||||
- }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
diff -r -U 3 minecraft_server\net\minecraft\server\dedicated\DedicatedServer.java minecraft_server_patched\net\minecraft\server\dedicated\DedicatedServer.java
|
||||
--- minecraft_server\net\minecraft\server\dedicated\DedicatedServer.java
|
||||
+++ minecraft_server_patched\net\minecraft\server\dedicated\DedicatedServer.java
|
||||
@@ -261,7 +261,7 @@
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
- protected void func_71190_q() {
|
||||
+ public void func_71190_q() {
|
||||
super.func_71190_q();
|
||||
this.func_71333_ah();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\server\integrated\IntegratedServer.java minecraft_patched\net\minecraft\server\integrated\IntegratedServer.java
|
||||
--- minecraft\net\minecraft\server\integrated\IntegratedServer.java
|
||||
+++ minecraft_patched\net\minecraft\server\integrated\IntegratedServer.java
|
||||
@@ -95,7 +95,7 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
- protected void func_71217_p() {
|
||||
+ public void func_71217_p() {
|
||||
boolean var1 = this.field_71348_o;
|
||||
this.field_71348_o = Minecraft.func_71410_x().func_147114_u() != null && Minecraft.func_71410_x().func_147113_T();
|
||||
if(!var1 && this.field_71348_o) {
|
||||
@@ -0,0 +1,11 @@
|
||||
diff -r -U 3 minecraft\net\minecraft\world\storage\ThreadedFileIOBase.java minecraft_patched\net\minecraft\world\storage\ThreadedFileIOBase.java
|
||||
--- minecraft\net\minecraft\world\storage\ThreadedFileIOBase.java
|
||||
+++ minecraft_patched\net\minecraft\world\storage\ThreadedFileIOBase.java
|
||||
@@ -21,6 +21,7 @@
|
||||
}
|
||||
|
||||
public void run() {
|
||||
+ while(true)
|
||||
this.func_75736_b();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[VERSION]
|
||||
MCPVersion = 9.01-alpha
|
||||
ClientVersion = 1.7.2
|
||||
ServerVersion = 1.7.2
|
||||
@@ -1,18 +0,0 @@
|
||||
Contributing to Forge
|
||||
=====================
|
||||
|
||||
1) Keep patches to Minecraft classes minimal. If you need a lot of things done, you may either add to relevant forge classes or make a new class.
|
||||
|
||||
2) An example mod must be provided for all PRs adding events/hooks to Forge. Whether it's a full mod using your proposed hooks, or just a pastebin containing a simple example, an example mod is required. Please also explain in detail what your new hook does.
|
||||
|
||||
3) Follow Forge coding guidelines (braces on newlines, spaces instead of tabs, etc)
|
||||
|
||||
For more information, refer to [the wiki](https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge)
|
||||
|
||||
|
||||
Contributor License Agreement
|
||||
=============================
|
||||
- You grant Forge a license to use your code contributed to the primary codebase (everything **not** under patches) in Forge, under the LGPLv2.1 license.
|
||||
- You assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the LGPLv2.1 license.
|
||||
|
||||
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
|
||||
@@ -1,68 +0,0 @@
|
||||

|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[](https://files.minecraftforge.net)
|
||||
[](https://files.minecraftforge.net) [][Discord] [](https://www.patreon.com/LexManos)
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
| Version | Support |
|
||||
| ------------- | ------------- |
|
||||
| 1.20.x | Active |
|
||||
| 1.19.x | Legacy |
|
||||
| 1.18.x | Legacy |
|
||||
| 1.16.x | Legacy |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
* [Discord]
|
||||
* [Documentation]
|
||||
|
||||
# Installing Forge
|
||||
|
||||
Go to [the Forge website](https://files.minecraftforge.net)
|
||||
and select the Minecraft version you wish to get Forge for from the list.
|
||||
|
||||
You can download the installer for the *Recommended Build* or the
|
||||
*Latest build* there. Latest builds may have newer features but may be
|
||||
more unstable as a result. The installer will attempt to install Forge
|
||||
into your vanilla launcher environment, where you can then create a new
|
||||
profile using that version and play the game!
|
||||
|
||||
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
|
||||
|
||||
[Here is a short video from Rorax showing how to install and setup Forge.](https://www.youtube.com/watch?v=lB3ArN_-3Oc)
|
||||
|
||||
# Creating Mods
|
||||
|
||||
[See the "Getting Started" section in the Forge Documentation](https://docs.minecraftforge.net/en/1.18.x/gettingstarted/).
|
||||
|
||||
# Contribute to Forge
|
||||
|
||||
If you wish to actually inspect Forge, submit PRs or otherwise work
|
||||
with Forge itself, you're in the right place!
|
||||
|
||||
[See the guide to setting up a Forge workspace](https://docs.minecraftforge.net/en/1.18.x/forgedev/).
|
||||
|
||||
### Pull requests
|
||||
|
||||
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://docs.minecraftforge.net/en/1.18.x/forgedev/#making-changes-and-pull-requests).
|
||||
|
||||
Please read the contributing guidelines found [here](CONTRIBUTING.md) before making a pull request.
|
||||
|
||||
### Contributor License Agreement
|
||||
We require all contributors to acknowledge the [Forge Contributor
|
||||
License Agreement](https://cla-assistant.io/MinecraftForge/MinecraftForge). Please ensure you have a valid email address
|
||||
associated with your GitHub account to do this. If you have previously
|
||||
signed it, you should be OK.
|
||||
|
||||
#### Donate
|
||||
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free to use and modify. However, it costs money to run such a large project as this, so please consider [becoming a patron](https://www.patreon.com/LexManos).*
|
||||
|
||||
[Download]: https://files.minecraftforge.net/
|
||||
[Forum]: https://forums.minecraftforge.net
|
||||
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
|
||||
[Discord]: https://discord.minecraftforge.net
|
||||
[Documentation]: https://docs.minecraftforge.net/en/1.18.x/
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
Getting Support Surrounding Forge
|
||||
=================================
|
||||
|
||||
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) for support.**
|
||||
|
||||
Before reporting an issue on this repository's bug tracker, please carefully read this document. There are various resources that are more likely to be able to help you with certain kinds of issues. This document provides instructions on how to determine which place best to report to.
|
||||
|
||||
*Note: While this guide generally is more geared towards end-user problems, modders should also read it before reporting any issue with Forge in order to ensure it actually is one. This does of course not cover feature suggestions.*
|
||||
|
||||
### Contents
|
||||
|
||||
1. [Identifying the Source of a Problem](#identifying-the-source-of-a-problem)
|
||||
1. [Analyzing a Stacktrace](#analyzing-a-stacktrace)
|
||||
2. [Isolating an Issue](#isolating-an-issue)
|
||||
2. [Choosing the Correct Support Channel](#choosing-the-correct-support-channel)
|
||||
1. [Forge Support](#forge-support)
|
||||
2. [Mod Support](#mod-support)
|
||||
3. [Vanilla Support](#vanilla-support)
|
||||
3. [Writing a Helpful Bug Report](#writing-a-helpful-bug-report)
|
||||
1. [Preventing Duplicates and Unnecessary Reports](#preventing-duplicates-and-unnecessary-reports)
|
||||
2. [Required Information](#required-information)
|
||||
3. [Markdown Issue Template](#markdown-issue-template)
|
||||
4. [Waiting for an Issue to Be Resolved](#waiting-for-an-issue-to-be-resolved)
|
||||
|
||||
Identifying the Source of a Problem
|
||||
-----------------------------------
|
||||
|
||||
You're most likely to get fast and valuable responses to your issues if you report them in the correct place. For this, you need to find the root cause of the problem.
|
||||
|
||||
The most important source for this information are the log files Forge creates for every run of the game. You can find them in the `logs` subdirectory of your Minecraft game directory. The most important log file is `latest.log` which contains all of Forge's and any mod's output for `info` of above. `debug.log` contains all the logs in `latest.log` plus the `debug` logs for more detailed debugging.
|
||||
|
||||
If you're faced by a crash, consult these logs files first. Near the end of the log should be a section labelled 'Minecraft Crash Report', and after it, one about `a detailed walkthrough of the error, ...`. In these sections, you'll see a bunch of lines that look like
|
||||
|
||||
```
|
||||
at some.words.related.to.a.mod.MoreWords(MoreWords.java:number)
|
||||
```
|
||||
|
||||
These lines are the most important part of a crash log, called the stacktrace, because they pinpoint the place in the code where something went irrecoverably wrong. Mod authors usually name their code so that these lines describe what the game was doing when it crashed. The further down one of these lines is, the earlier that piece of code ran, so at the bottom you'll see the place where Minecraft started, and at the top is where it stopped. You should use this information to figure out who to report the bug to.
|
||||
|
||||
### Analyzing a Stacktrace
|
||||
|
||||
You can generally tell pretty quickly what caused the error at the head of your stacktrace, simply follow these rules:
|
||||
|
||||
1. If you can find the name of a mod in the trace, that mod most likely is the culprit.
|
||||
- Be aware that you may find multiple mods as participants of a crash. In that case, you can either report to both or try to reason (using the names in the stacktrace) about which mod is at fault.
|
||||
2. If you can only find Forge (usually starting with `net.minecraftforge`) alongside Vanilla (usually starting with `net.minecraft`) references, chances are that you have found a bug in Forge. In this case, use one of the various [support channels](#forge-support) listed below.
|
||||
- The occurence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
|
||||
3. If there's no mention of Forge or a mod, but any Vanilla class, you might have found a Vanilla bug. You should probably still report it via the Forge channels first, however, due the nature of modded environments and the rarity of Vanilla crash bugs.
|
||||
4. Always check whether there's a mention of coremods at the beginning of a crash report. If there is and you can't find any clear culprit, consider reporting to these coremods first, since they are free to change any of the code executed in your environment.
|
||||
|
||||
The rules are numbered in decreasing priority, which means that you should always prefer finding a mod in a crash report over directly jumping to conclusions about Forge or even Vanilla.
|
||||
|
||||
### Isolating an Issue
|
||||
|
||||
A significant step of reporting an issue, after having found the involved parties, is reliably reproducing it to identify potential incompatibilities or weird interactions between mods. If you're unable to pinpoint a bug to a set of mods and just randomly report it to any mod, chances are it won't get much attention and hence not a fix.
|
||||
|
||||
Reliable methods of isolating the issue are listed below:
|
||||
|
||||
- **Retrace your last steps before the crash/bug occurred.** It might sound too simple, but a big part of reliably reproducing an issue is determining the steps required to trigger it. If you can't exactly remember what you last did, looking at the stacktrace again can also help you a bit, since there are often descriptive names in there which reflect an action you could take, such as using an item or placing a block.
|
||||
- **Reduce the number of mod interactions.** You can achieve this in multiple ways. One of them is simply disabling any compatibility layers between involved mods via configuration files. The other one, which takes a little more effort, is actually removing mods from your instance. But don't be afraid, you won't have to remove mods one-by-one if you follow these steps (known as a binary search):
|
||||
1. Move half the mods out of your instance (obviously keep those definitely participating in the issue).
|
||||
2. If the issue persists after another try of reproducing it, repeat from step 1 one by halving the mod list again.
|
||||
3. If the issue does not occur anymore without the other half, repeat from step 1 but instead discard the half you just used and split the other one (plus any definitely required mods).
|
||||
4. Repeat these steps until you can't remove any more mods from your instance without also 'solving' the issue. This set of mods can serve as a basis for your issue report.
|
||||
|
||||
Choosing the Correct Support Channel
|
||||
-------------------------------------
|
||||
|
||||
The place you need to report issues to varies depending on the source of the problem. Below you can find a list of support channels you can usually consult for each of the sources listed in the previous section.
|
||||
|
||||
Please note that this list has to be considered incomplete as there is a large number of sub-communities within the Minecraft modding world. You should always prefer channels you know and have access to over any of those listed below which would require extra effort on your part.
|
||||
|
||||
### Forge Support
|
||||
|
||||
If you're certain you've found an issue that is directly caused by Forge, you have the following three platforms available for dealing with it, with decreasing relevance for end-users:
|
||||
|
||||
1. **Forge Forums:** There's a dedicated [Support & Bug Report](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
|
||||
- For modders, there also is a separate [Modder Support](http://www.minecraftforge.net/forum/forum/70-modder-support/) section where you can ask all your development-related questions. *You should prefer this section for coding help.*
|
||||
2. **Forge IRC Channel:** You can find a lot of help on the official [`#minecraftforge`](irc://irc.esper.net:6667/minecraftforge) channel on EsperNet. Especially smaller issues that you quickly need help with could be reported there. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
|
||||
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation](https://mcforge.readthedocs.io) are the best sources of information.*
|
||||
|
||||
### Mod Support
|
||||
|
||||
A lot of mods also use GitHub or other source management systems and their integrated issue trackers. The following options will give you the best chances of reporting your issue in the right place:
|
||||
|
||||
- **CurseForge:** Many mods are hosted on the [CurseForge platform](https://minecraft.curseforge.com) nowadays. Chances are you already downloaded them from there or as a part of a modpack hosted there. Projects may specify an issue reporting link on their own page, so simply look for it on the mod's project page and you should be directed to the preferred means of error reporting.
|
||||
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com) and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
|
||||
|
||||
### Vanilla Support
|
||||
|
||||
Just like Forge or mods, Mojang has its own issue tracker for Vanilla Minecraft. This official [bug tracker](https://bugs.mojang.com/projects/MC/issues/) has the exclusive purpose of managing any concrete issues with the game's code and functionality. It is *not* a place for requesting any features or getting help with playing the game, similar to the Forge issue tracker.
|
||||
|
||||
Writing a Helpful Bug Report
|
||||
----------------------------
|
||||
|
||||
Now that you've identified the source of your issue and decided on the correct support channel, it's time to actually write your report. Before you actually try to blindly create it, however, you should first check if the problem was noticed or even fixed already.
|
||||
|
||||
### Preventing Duplicates and Unnecessary Reports
|
||||
Use the target issue tracker's search functionality to look for issues which have descriptions similar to yours, but make sure to also search for already closed issues. If you can find one, verify it is in fact the same (or a very similar issue) and simply comment on it if you have anything else to contribute to the discussion, such as other steps to reproduce it. *Do not create duplicates of an existing issue, since it only puts extra effort on maintainers to recognize and close/mark them.*
|
||||
|
||||
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was adressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
|
||||
|
||||
### Required Information
|
||||
If you're certain that your issue went unnoticed up until now, you can get to actually writing up the report. Below you can find a list of the *required* information any bug report should contain:
|
||||
|
||||
- **Minecraft Version:** Although many developers can deduce the Minecraft version in use from the mod's version, it is still an important piece of information to include, since it helps maintainers more easily decide whether to look into your issue at all or whether to close it right away due to being for an outdated version.
|
||||
- **Forge Version:** The Forge API is in constant flux and while breaking changes are limited to major Minecraft updates, new bugs or regressions might creep in during active development or newer versions than a mod was built with might change semantics slightly that manifest themselves for this mod.
|
||||
- **Mod Version:** Just like the Forge and Minecraft version, this is crucial for quickly telling where an issue might come from. *If you're reporting an issue caused by Forge that only occurs with a certain mod installed, include this mod here. No mod version is required if it's a pure Forge bug.*
|
||||
- **Complete List of Mods:** In order to reliably analyze a bug, developers need the exact same context as you do when trying to find its cause. Hence, you should always include a *full list of mods with their corresponding version* in your report. Note that you should probably only include the ones you found by [isolating the issue](#isolating-an-issue).
|
||||
- **Steps to Reproduce:** You should always include a list of steps required to provoke an issue to occur. Without it, developers can only take rough guesses at what might cause the bug. Try to reduce the amount of steps to a bare minimum, to make reproducing the issue easier. If you require a sophisticated setup to get even close to the issue, you might want to consider including your save file in your report.
|
||||
- **Full Log:** This is probably the most important part of your bug report. You *always* have to include the full log (`debug.log` from earlier), since it is the only way of telling what has caused an issue in code. *Do not* paste the full log as plain text in your report, but use [GitHub Gist](https://gist.github.com) instead and link to that from your report.
|
||||
|
||||
### Markdown Issue Template
|
||||
GitHub and BitBucket allow you to use the Markdown format for writing your issues. Below you can find a simple template that you can copy and fill in with your data. `{variables}` are written in braces and are supposed to be replaced by you with the information the variable name indicates. Everything behind two slashes (`//`) has to be interpreted as a comment and should be removed. If you want to include any link, you have to write in the `[title](url)` form. For further information on Markdown, see [this cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
|
||||
|
||||
```markdown
|
||||
**Minecraft Version:** {mc version}
|
||||
|
||||
**Forge Version:** {forge version}
|
||||
|
||||
**Mod Version:** {mod version} // Remove this line if you're reporting to Forge and no specific mod is involved
|
||||
|
||||
[**Full Log**]({link to gist/pastebin with full log})
|
||||
|
||||
**Mods in This Instance:**
|
||||
| Name | Version |
|
||||
| ------------------------ | ----------- |
|
||||
| [{mod 1}]({download 1}) | {version 1} | // The download links are entirely optional
|
||||
| {mod 2} | {version 2} |
|
||||
| {mod 3} | {version 3} |
|
||||
| ... | ... |
|
||||
|
||||
**Steps to Reproduce:**
|
||||
1. Do this
|
||||
2. Then do that
|
||||
3. ...
|
||||
```
|
||||
|
||||
*Note: The mod list here is formatted as table for easy reading (when displayed) and to look nice in text. Markdown does not care about any alignment of the spaces, so you can simply write individual rows of the table as* `| {mod name} | {version} |` *without having to care about the spacing.*
|
||||
|
||||
### Waiting for an Issue to Be Resolved
|
||||
After you have written your bug report, it is time to *wait*. Please be *patient* after submitting your issue, since it probably isn't the only one. If there's no response after 5 minutes or even a day of writing it, *do not* try to bump the issue by commenting on it, people *will* read it in due time. The only excuse for bumping a report is if it is a *critical* bug that prevents you from playing the game.
|
||||
|
||||
Every project has a different way of dealing with issues, so don't wonder if there's no comment on it, but somebody "labels" it with certain markers. This helps developers to more easily keep track of bugs and priorities. When any further input from you is required, you will be asked specifically. Do not bug anyone through other channels about resolving your issue.
|
||||
|
||||
Once your issue is closed, you will know whether it was valid or turned out to be irreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
|
||||
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="164.072 372.466 294.41 46.56">
|
||||
<g fill="#DFA86A">
|
||||
<path d="M266.16 417.99h10.96v-16.94h17.97v-10.27h-17.97v-7.01h19.06V373.5h-30.02zM337.33 378.7c-2.103-2.057-4.629-3.64-7.51-4.704-2.743-1.015-5.786-1.528-9.041-1.528l-.356.002c-3.362 0-6.503.556-9.338 1.651-2.857 1.106-5.357 2.707-7.434 4.761-2.073 2.052-3.714 4.559-4.877 7.45-1.158 2.876-1.748 6.104-1.748 9.59 0 3.417.59 6.582 1.753 9.403 1.163 2.829 2.807 5.297 4.88 7.329 2.073 2.03 4.572 3.621 7.425 4.724 2.834 1.096 5.975 1.651 9.339 1.651h.023c3.402-.043 6.57-.639 9.42-1.772 2.863-1.142 5.375-2.757 7.465-4.804 2.091-2.051 3.742-4.524 4.91-7.358 1.162-2.823 1.75-5.987 1.75-9.402 0-3.487-.588-6.705-1.748-9.568-1.168-2.874-2.82-5.371-4.913-7.425zm-4.644 16.762c0 1.96-.302 3.782-.896 5.413-.587 1.607-1.42 3.011-2.479 4.167-1.05 1.147-2.335 2.065-3.821 2.728-1.479.656-3.164.99-5.008.99-1.84 0-3.514-.333-4.973-.988-1.47-.662-2.747-1.578-3.799-2.729-1.062-1.156-1.895-2.559-2.479-4.167-.595-1.63-.896-3.451-.896-5.413 0-1.801.298-3.497.887-5.044.584-1.536 1.419-2.894 2.479-4.037 1.048-1.132 2.323-2.03 3.792-2.674 1.465-.642 3.145-.968 4.988-.968 1.848 0 3.539.326 5.025.971 1.481.643 2.766 1.541 3.812 2.671 1.062 1.143 1.896 2.501 2.479 4.036.589 1.548.889 3.245.889 5.044zM376.27 395.65c1.902-2.327 2.867-5.221 2.867-8.603 0-2.614-.508-4.84-1.506-6.613-.997-1.762-2.354-3.188-4.035-4.234-1.603-.995-3.445-1.705-5.482-2.114-1.938-.388-3.969-.583-6.037-.583h-16.021v44.487h10.961v-17.282h2.35l9.447 17.282h13.127l-11.199-18.646c2.262-.728 4.116-1.964 5.528-3.694zm-19.254-12.223h4.426c.844 0 1.73.054 2.636.16.784.094 1.503.283 2.132.568.496.225.888.553 1.193.998.283.415.428 1.031.428 1.836 0 .931-.16 1.643-.459 2.06-.34.472-.771.813-1.32 1.046-.686.289-1.459.477-2.301.554-.975.089-1.963.136-2.938.136h-3.795l-.002-7.358z"/>
|
||||
<path d="M394.82 386.38c1.05-1.132 2.325-2.03 3.793-2.674 1.464-.642 3.144-.968 4.988-.968 1.843 0 3.611.311 5.26.921 1.609.599 2.992 1.449 4.107 2.531l1.314 1.278 8.019-8.105-1.454-1.298c-2.355-2.103-5.066-3.586-8.058-4.407-2.879-.789-5.987-1.189-9.247-1.189-3.361 0-6.503.556-9.339 1.651-2.856 1.106-5.355 2.707-7.431 4.761-2.074 2.052-3.716 4.559-4.88 7.45-1.158 2.876-1.746 6.104-1.746 9.59 0 3.417.59 6.582 1.752 9.403 1.164 2.829 2.807 5.297 4.88 7.329 2.073 2.03 4.572 3.621 7.425 4.724 2.832 1.096 5.975 1.651 9.339 1.651 6.501 0 12.475-1.432 17.751-4.25l.979-.522v-23.814h-19.313v10.27h8.354v6.639c-.871.397-1.834.709-2.881.933-1.5.314-3.127.478-4.83.478-1.841 0-3.514-.333-4.973-.988-1.472-.662-2.748-1.578-3.801-2.729-1.06-1.156-1.893-2.559-2.478-4.167-.595-1.63-.896-3.451-.896-5.413 0-1.801.299-3.497.887-5.044.586-1.54 1.421-2.897 2.479-4.041zM437.69 407.72v-7.36h18.72v-10.27h-18.72v-6.32h19.75V373.5h-30.72v44.49h31.76v-10.27z"/>
|
||||
</g>
|
||||
<path fill="#1E2D41" d="m243.63 379.09-37.842-1.852 46.229-.034v-3.727h-52.125l-.001 7.798v6.201c0 .115-1.52-9.144-1.87-11.734h-4.098v13.029c0 .123-1.753-10.888-1.939-12.264H164.07c1.902 1.648 12.401 10.598 19.865 14.28 3.741 1.846 8.33 1.86 12.414 1.974 2.075.06 4.25.217 5.803 1.754 2.255 2.236 2.758 5.704.814 8.33-1.922 2.594-7.335 3.156-7.335 3.156l-4.509 5.531v6.424h10.251l.319-6.348 8.863-6.285c-.946.757-3.058 2.783-6.229 7.666-.717 1.102-1.275 2.306-1.711 3.49 2.238-1.896 6.84-3.194 12.153-3.194 5.307 0 9.903 1.295 12.146 3.188-.437-1.185-.994-2.385-1.709-3.483-3.172-4.883-5.284-6.909-6.229-7.666l8.863 6.285.32 6.348h9.565v-6.424l-4.507-5.531s-6.675-.425-8.423-3.156c-5.032-7.869 2.115-20.082 18.836-23.756z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
@@ -1,71 +0,0 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
|
||||
dependencies {
|
||||
compileOnly('org.jetbrains:annotations:21.0.1')
|
||||
implementation(project(':fmlloader'))
|
||||
api('net.minecraftforge:eventbus:5.0.+')
|
||||
implementation('commons-io:commons-io:2.5')
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'': [
|
||||
'FMLModType': 'LIBRARY',
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}"
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/loading/': [
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Loader',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
else
|
||||
manifest.attributes(values, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives jar
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Bindings {
|
||||
private static final Bindings INSTANCE = new Bindings();
|
||||
|
||||
private final IBindingsProvider provider;
|
||||
|
||||
private Bindings() {
|
||||
final var providers = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(FMLLoader.getGameLayer(), IBindingsProvider.class), sce->{}).toList();
|
||||
if (providers.size() != 1) {
|
||||
throw new IllegalStateException("Could not find bindings provider");
|
||||
}
|
||||
this.provider = providers.get(0);
|
||||
}
|
||||
|
||||
public static Supplier<IEventBus> getForgeBus() {
|
||||
return INSTANCE.provider.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
public static Supplier<I18NParser> getMessageParser() {
|
||||
return INSTANCE.provider.getMessageParser();
|
||||
}
|
||||
|
||||
public static Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
|
||||
return INSTANCE.provider.getConfigConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class CrashReportCallables {
|
||||
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public static void registerCrashCallable(ISystemReportExtender callable)
|
||||
{
|
||||
crashCallables.add(callable);
|
||||
}
|
||||
|
||||
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator) {
|
||||
registerCrashCallable(new ISystemReportExtender() {
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return headerName;
|
||||
}
|
||||
@Override
|
||||
public String get() {
|
||||
return reportGenerator.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static List<ISystemReportExtender> allCrashCallables() {
|
||||
return List.copyOf(crashCallables);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Utility for running code on the main launch thread at the next available
|
||||
* opportunity. There is no guaranteed order that work from various mods will be
|
||||
* run, but your own work will be run sequentially.
|
||||
* <p>
|
||||
* <strong>Use of this class after startup is not possible.</strong> At that
|
||||
* point, {@code ReentrantBlockableEventLoop} should be used instead.
|
||||
* <p>
|
||||
* Exceptions from tasks will be handled gracefully, causing a mod loading
|
||||
* error. Tasks that take egregiously long times to run will be logged.
|
||||
*/
|
||||
public class DeferredWorkQueue
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final Map<ModLoadingStage, DeferredWorkQueue> workQueues = new HashMap<>();
|
||||
|
||||
private final ConcurrentLinkedDeque<TaskInfo> tasks = new ConcurrentLinkedDeque<>();
|
||||
private final ModLoadingStage modLoadingStage;
|
||||
|
||||
public DeferredWorkQueue(ModLoadingStage modLoadingStage) {
|
||||
this.modLoadingStage = modLoadingStage;
|
||||
workQueues.put(modLoadingStage, this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public static Optional<DeferredWorkQueue> lookup(Optional<ModLoadingStage> parallelClass) {
|
||||
return Optional.ofNullable(workQueues.get(parallelClass.orElse(null)));
|
||||
}
|
||||
|
||||
public void runTasks() {
|
||||
if (tasks.isEmpty()) return;
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
tasks.forEach(t->makeRunnable(t, Runnable::run));
|
||||
timer.stop();
|
||||
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
|
||||
}
|
||||
|
||||
private static void makeRunnable(TaskInfo ti, Executor executor) {
|
||||
executor.execute(() -> {
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
ModLoadingContext.get().setActiveContainer(ti.owner);
|
||||
try {
|
||||
ti.task.run();
|
||||
} finally {
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
}
|
||||
timer.stop();
|
||||
if (timer.elapsed(TimeUnit.SECONDS) >= 1) {
|
||||
LOGGER.warn(LOADING, "Mod '{}' took {} to run a deferred task.", ti.owner.getModId(), timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> enqueueWork(final ModContainer modInfo, final Runnable work) {
|
||||
return CompletableFuture.runAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
|
||||
}
|
||||
|
||||
public <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, final Supplier<T> work) {
|
||||
return CompletableFuture.supplyAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
|
||||
}
|
||||
|
||||
record TaskInfo(ModContainer owner, Runnable task) {}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Use to execute code conditionally based on sidedness.
|
||||
* <ul>
|
||||
* <li>When you want to call something on one side and return a result {@link #safeCallWhenOn(Dist, Supplier)}</li>
|
||||
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
|
||||
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class DistExecutor
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private DistExecutor() {}
|
||||
|
||||
/**
|
||||
* Run the callable in the supplier only on the specified {@link Dist}.
|
||||
* This method is NOT sided-safe and special care needs to be taken in code using this method that implicit class
|
||||
* loading is not triggered by the Callable.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* Use {@link #safeCallWhenOn(Dist, Supplier)} where possible.
|
||||
*
|
||||
* @param dist The dist to run on
|
||||
* @param toRun A supplier of the callable to run (Supplier wrapper to ensure classloading only on the appropriate dist)
|
||||
* @param <T> The return type from the callable
|
||||
* @return The callable's result
|
||||
* @deprecated use {@link #safeCallWhenOn(Dist, Supplier)} instead. This remains for advanced use cases.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T callWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
return unsafeCallWhenOn(dist, toRun);
|
||||
}
|
||||
|
||||
public static <T> T unsafeCallWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
try
|
||||
{
|
||||
return toRun.get().call();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the SafeCallable when on the correct {@link Dist}.
|
||||
*
|
||||
* <strong>The lambda supplied here is required to be a method reference to a method defined in
|
||||
* another class, otherwise an invalid SafeReferent error will be thrown</strong>
|
||||
* @param dist the dist which this will run on
|
||||
* @param toRun the SafeCallable to run and return the result from
|
||||
* @param <T> The type of the SafeCallable
|
||||
* @return the result of the SafeCallable or null if on the wrong side
|
||||
*/
|
||||
public static <T> T safeCallWhenOn(Dist dist, Supplier<SafeCallable<T>> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
return callWhenOn(dist, toRun::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #callWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* @see #callWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
* @deprecated use {@link #safeRunWhenOn(Dist, Supplier)} where possible. Advanced uses only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void runWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
unsafeRunWhenOn(dist, toRun);
|
||||
}
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #unsafeCallWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFoundException problems in common scenarios. Understand the pitfalls of
|
||||
* the way the class verifier works to load classes before using this.
|
||||
*
|
||||
* Use {@link #safeRunWhenOn(Dist, Supplier)} if you can.
|
||||
*
|
||||
* @see #unsafeCallWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void unsafeRunWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the supplied SafeRunnable when on the correct Dist.
|
||||
* @param dist The dist to run on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void safeRunWhenOn(Dist dist, Supplier<SafeRunnable> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.runForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
* @deprecated Use {@link #safeRunForDist(Supplier, Supplier)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T runForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
return unsafeRunForDist(clientTarget, serverTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsafe version of {@link #safeRunForDist(Supplier, Supplier)}. Use only when you know what you're doing
|
||||
* and understand why the verifier can cause unexpected ClassNotFoundException crashes even when code is apparently
|
||||
* not sided. Ensure you test both sides fully to be confident in using this.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T unsafeRunForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.safeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T safeRunForDist(Supplier<SafeSupplier<T>> clientTarget, Supplier<SafeSupplier<T>> serverTarget) {
|
||||
validateSafeReferent(clientTarget);
|
||||
validateSafeReferent(serverTarget);
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safe referent. This will assert that it is being called via a separated class method reference. This will
|
||||
* avoid the common pitfalls of {@link #callWhenOn(Dist, Supplier)} above.
|
||||
*
|
||||
* SafeReferents assert that they are defined as a separate method outside the scope of the calling class.
|
||||
*
|
||||
* <strong>Implementations need to be defined in a separate class to the referring site, with appropriate
|
||||
* visibility to be accessible at the callsite (generally, avoid private methods).</strong>
|
||||
*
|
||||
* <p>
|
||||
* Valid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->AnotherClass::clientOnlyMethod);}
|
||||
*
|
||||
* <p>
|
||||
* Invalid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->()->Minecraft.getInstance().world);}
|
||||
*/
|
||||
public interface SafeReferent {}
|
||||
|
||||
/**
|
||||
* SafeCallable version of {@link SafeReferent}.
|
||||
* @see SafeReferent
|
||||
* @param <T> The return type of the Callable
|
||||
*/
|
||||
public interface SafeCallable<T> extends SafeReferent, Callable<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeSupplier version of {@link SafeReferent}
|
||||
* @param <T> The return type of the Supplier
|
||||
*/
|
||||
public interface SafeSupplier<T> extends SafeReferent, Supplier<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeRunnable version of {@link SafeReferent}
|
||||
*/
|
||||
public interface SafeRunnable extends SafeReferent, Runnable, Serializable {}
|
||||
|
||||
private static final void validateSafeReferent(Supplier<? extends SafeReferent> safeReferentSupplier) {
|
||||
if (FMLEnvironment.production) return;
|
||||
final SafeReferent setter;
|
||||
try {
|
||||
setter = safeReferentSupplier.get();
|
||||
} catch (Exception e) {
|
||||
// Typically a class cast exception, just return out, expected.
|
||||
return;
|
||||
}
|
||||
|
||||
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
|
||||
try {
|
||||
Method m = cl.getDeclaredMethod("writeReplace");
|
||||
m.setAccessible(true);
|
||||
Object replacement = m.invoke(setter);
|
||||
if (!(replacement instanceof SerializedLambda))
|
||||
break;// custom interface implementation
|
||||
SerializedLambda l = (SerializedLambda) replacement;
|
||||
if (Objects.equals(l.getCapturingClass(), l.getImplClass())) {
|
||||
LOGGER.fatal("Detected unsafe referent usage, please view the code at {}",Thread.currentThread().getStackTrace()[3]);
|
||||
throw new RuntimeException("Unsafe Referent usage found in safe referent method");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
public interface I18NParser {
|
||||
String parseMessage(String i18nMessage, Object... args);
|
||||
String stripControlCodes(String toStrip);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IBindingsProvider {
|
||||
Supplier<IEventBus> getForgeBusSupplier();
|
||||
Supplier<I18NParser> getMessageParser();
|
||||
Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IExtensionPoint<T extends Record>
|
||||
{
|
||||
/**
|
||||
* Compatibility display test for the mod.
|
||||
* Used for displaying compatibility with remote servers with the same mod, and on disk saves.
|
||||
*
|
||||
* The supplier provides my "local" version for sending across the impl or writing to disk
|
||||
* The predicate tests the version from a remote instance or save for acceptability (Boolean is true for impl, false for local save)
|
||||
* and returns true if the version is compatible.
|
||||
*
|
||||
* <p>Return {@code net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY} in the supplier, if you wish to be ignored
|
||||
* as a server side only mod.</p>
|
||||
* <p>Return true in the predicate for all values of the input string (when impl boolean is true) if you are client side,
|
||||
* and don't care about matching any potential server version.</p>
|
||||
*
|
||||
* <p>
|
||||
* Examples: A server only mod
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* registerExtensionPoint(DisplayTest.class, ()->new DisplayTest(
|
||||
* ()->NetworkConstants.IGNORESERVERONLY, // ignore me, I'm a server only mod
|
||||
* (s,b)->true // i accept anything from the server or the save, if I'm asked
|
||||
* )
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Examples: A client only mod
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* registerExtensionPoint(DisplayTest.class, ()->new DisplayTest(
|
||||
* ()->"anything. i don't care", // if i'm actually on the server, this string is sent but i'm a client only mod, so it won't be
|
||||
* (remoteversionstring,networkbool)->networkbool // i accept anything from the server, by returning true if it's asking about the server
|
||||
* )
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
|
||||
|
||||
/**
|
||||
* Ignores any version information coming from the server - use for server only mods
|
||||
*/
|
||||
public static final Supplier<DisplayTest> IGNORE_SERVER_VERSION = () -> new DisplayTest(IGNORESERVERONLY, (remoteVersion, isFromServer) -> true);
|
||||
|
||||
/**
|
||||
* Ignores all information and provides no information
|
||||
*/
|
||||
public static final Supplier<DisplayTest> IGNORE_ALL_VERSION = () -> new DisplayTest("", (remoteVersion, isFromServer) -> true);
|
||||
|
||||
/**
|
||||
* An optional alternative to {@link #DisplayTest(Supplier, BiPredicate)} which accepts a constant version string
|
||||
* instead of a {@link Supplier}.
|
||||
* <p>Internally, the provided version string is wrapped in a Supplier for you.</p>
|
||||
*/
|
||||
public DisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
this(() -> version, remoteVersionTest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface IModLoadingState {
|
||||
String name();
|
||||
String previous();
|
||||
ModLoadingPhase phase();
|
||||
Function<ModList, String> message();
|
||||
Optional<Consumer<ModList>> inlineRunnable();
|
||||
|
||||
default <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor) {
|
||||
return buildTransition(syncExecutor, parallelExecutor, e->CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e));
|
||||
}
|
||||
|
||||
<T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IModStateProvider {
|
||||
List<IModLoadingState> getAllStates();
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface IModStateTransition {
|
||||
static IModStateTransition buildNoopTransition() {
|
||||
return new NoopTransition();
|
||||
}
|
||||
|
||||
default <T extends Event & IModBusEvent> CompletableFuture<List<Throwable>> build(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
List<CompletableFuture<List<Throwable>>> cfs = new ArrayList<>();
|
||||
this.eventFunctionStream().get()
|
||||
.map(f->(EventGenerator<T>)f)
|
||||
.reduce((head, tail)-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, head, ModLoadingStage::currentState, tail))
|
||||
.ifPresent(last-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, last, nextModLoadingStage(), null));
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<List<Throwable>> eventDispatchCF = ModList.gather(cfs).thenCompose(ModList::completableFutureFromExceptionList);
|
||||
final CompletableFuture<List<Throwable>> postEventDispatchCF = preSyncTaskCF.thenComposeAsync(n -> eventDispatchCF, parallelExecutor).thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return r;
|
||||
});
|
||||
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
|
||||
default BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
|
||||
return ModLoadingStage::nextState;
|
||||
}
|
||||
|
||||
private <T extends Event & IModBusEvent> EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor, final Executor parallelExecutor, final List<CompletableFuture<List<Throwable>>> completeableFutures, final EventGenerator<T> eventGenerator, BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState, final EventGenerator<T> nextGenerator) {
|
||||
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)preDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
|
||||
completeableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
|
||||
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)postDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
|
||||
return nextGenerator;
|
||||
}
|
||||
|
||||
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
|
||||
ThreadSelector threadSelector();
|
||||
BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook();
|
||||
|
||||
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
return fn::apply;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record NoopTransition() implements IModStateTransition {
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return Stream::of;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadSelector threadSelector() {
|
||||
return ThreadSelector.SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ISystemReportExtender extends Supplier<String>
|
||||
{
|
||||
String getLabel();
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class InterModComms
|
||||
{
|
||||
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #senderModId()}
|
||||
* @return The modid of the sender. This is supplied by the caller, or by the active mod container context.
|
||||
* Consider it unreliable.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getSenderModId() {
|
||||
return this.senderModId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #modId()}
|
||||
* @return The modid being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getModId() {
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #method()}
|
||||
* @return The method being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T> The type of the message.
|
||||
* @return A {@link Supplier} of the message.
|
||||
* Use {@link #messageSupplier()}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public final <T> Supplier<T> getMessageSupplier() {
|
||||
return (Supplier<T>) this.messageSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
|
||||
*
|
||||
* @param modId the mod id to send to
|
||||
* @param method the method name to send
|
||||
* @param thing the thing associated with the method name
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(ModLoadingContext.get().getActiveContainer().getModId(), modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send IMC to remote.
|
||||
*
|
||||
* @param senderModId the mod id you are sending from
|
||||
* @param modId the mod id to send to
|
||||
* @param method the method name to send
|
||||
* @param thing the thing associated with the method name
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String senderModId, final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(senderModId, modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve pending messages for your modid. Use the predicate to filter the method name.
|
||||
*
|
||||
* @param modId the modid you are querying for
|
||||
* @param methodMatcher a predicate for the method you are interested in
|
||||
* @return All messages passing the supplied method predicate
|
||||
*/
|
||||
public static Stream<IMCMessage> getMessages(final String modId, final Predicate<String> methodMatcher) {
|
||||
ConcurrentLinkedQueue<IMCMessage> queue = containerQueues.get(modId);
|
||||
if (queue == null) return Stream.empty();
|
||||
return StreamSupport.stream(new QueueFilteringSpliterator(queue, methodMatcher), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all message for your modid.
|
||||
*
|
||||
* @param modId the modid you are querying for
|
||||
* @return All messages
|
||||
*/
|
||||
public static Stream<IMCMessage> getMessages(final String modId) {
|
||||
return getMessages(modId, s->Boolean.TRUE);
|
||||
}
|
||||
|
||||
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
|
||||
{
|
||||
private final ConcurrentLinkedQueue<IMCMessage> queue;
|
||||
private final Predicate<String> methodFilter;
|
||||
private final Iterator<IMCMessage> iterator;
|
||||
|
||||
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
|
||||
this.queue = queue;
|
||||
this.iterator = queue.iterator();
|
||||
this.methodFilter = methodFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int characteristics() {
|
||||
return Spliterator.CONCURRENT | Spliterator.NONNULL | Spliterator.ORDERED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateSize() {
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(final Consumer<? super IMCMessage> action)
|
||||
{
|
||||
IMCMessage next;
|
||||
do
|
||||
{
|
||||
if (!iterator.hasNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
next = this.iterator.next();
|
||||
}
|
||||
while (!methodFilter.test(next.method));
|
||||
action.accept(next);
|
||||
this.iterator.remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<IMCMessage> trySplit() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LoadingFailedException extends RuntimeException {
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
|
||||
public LoadingFailedException(final List<ModLoadingException> loadingExceptions) {
|
||||
this.loadingExceptions = loadingExceptions;
|
||||
}
|
||||
|
||||
public List<ModLoadingException> getErrors() {
|
||||
return this.loadingExceptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Loading errors encountered: " + this.loadingExceptions.stream().map(ModLoadingException::getMessage).collect(Collectors.joining(",\n\t", "[\n\t", "\n]"));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
public class Logging
|
||||
{
|
||||
// Lots of markers
|
||||
public static final Marker CORE = MarkerManager.getMarker("CORE");
|
||||
public static final Marker LOADING = MarkerManager.getMarker("LOADING");
|
||||
public static final Marker SCAN = MarkerManager.getMarker("SCAN");
|
||||
public static final Marker SPLASH = MarkerManager.getMarker("SPLASH");
|
||||
public static final Marker CAPABILITIES = MarkerManager.getMarker("CAPABILITIES");
|
||||
public static final Marker MODELLOADING = MarkerManager.getMarker("MODELLOADING");
|
||||
public static final Marker FORGEMOD = MarkerManager.getMarker("FORGEMOD").addParents(LOADING);
|
||||
|
||||
// --log CORE:+DEBUG,SCAN:-OFF
|
||||
// forge log debug 5
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
public enum LogicalSide
|
||||
{
|
||||
CLIENT, SERVER;
|
||||
|
||||
/**
|
||||
* @return if the logical side is a server.
|
||||
*/
|
||||
public boolean isServer()
|
||||
{
|
||||
return !isClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the logical side is a client.
|
||||
*/
|
||||
public boolean isClient()
|
||||
{
|
||||
return this == CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The container that wraps around mods in the system.
|
||||
* <p>
|
||||
* The philosophy is that individual mod implementation technologies should not
|
||||
* impact the actual loading and management of mod code. This class provides
|
||||
* a mechanism by which we can wrap actual mod code so that the loader and other
|
||||
* facilities can treat mods at arms length.
|
||||
* </p>
|
||||
*
|
||||
* @author cpw
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class ModContainer
|
||||
{
|
||||
protected final String modId;
|
||||
protected final String namespace;
|
||||
protected final IModInfo modInfo;
|
||||
protected ModLoadingStage modLoadingStage;
|
||||
protected Supplier<?> contextExtension;
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new HashMap<>();
|
||||
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
|
||||
|
||||
public ModContainer(IModInfo info)
|
||||
{
|
||||
this.modId = info.getModId();
|
||||
// TODO: Currently not reading namespace from configuration..
|
||||
this.namespace = this.modId;
|
||||
this.modInfo = info;
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
|
||||
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest")
|
||||
.orElseGet(() -> (Boolean) info.getOwningFile().getFileProperties().getOrDefault(ModFileInfo.CLIENT_SIDE_ONLY_PROP, Boolean.FALSE) ? "IGNORE_ALL_VERSION" : "MATCH_VERSION");
|
||||
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
|
||||
case "MATCH_VERSION" -> // default displaytest checks for version string match
|
||||
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
|
||||
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
|
||||
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
|
||||
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
|
||||
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
|
||||
IExtensionPoint.DisplayTest.IGNORE_ALL_VERSION;
|
||||
case "NONE" -> null; // NO display test at all - use this if you're going to do your own display test
|
||||
default -> // any other value throws an exception
|
||||
throw new IllegalArgumentException("Invalid displayTest value supplied in mods.toml");
|
||||
};
|
||||
if (displayTestSupplier != null)
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTestSupplier);
|
||||
else
|
||||
extensionPoints.remove(IExtensionPoint.DisplayTest.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Errored container state, used for filtering. Does nothing.
|
||||
*/
|
||||
ModContainer()
|
||||
{
|
||||
this.modLoadingStage = ModLoadingStage.ERROR;
|
||||
modId = "BROKEN";
|
||||
namespace = "BROKEN";
|
||||
modInfo = null;
|
||||
}
|
||||
/**
|
||||
* @return the modid for this mod
|
||||
*/
|
||||
public final String getModId()
|
||||
{
|
||||
return modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the resource prefix for the mod
|
||||
*/
|
||||
public final String getNamespace()
|
||||
{
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current loading stage for this mod
|
||||
*/
|
||||
public ModLoadingStage getCurrentState()
|
||||
{
|
||||
return modLoadingStage;
|
||||
}
|
||||
|
||||
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
|
||||
final ModContainer target,
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
|
||||
final Executor executor) {
|
||||
return CompletableFuture
|
||||
.runAsync(() -> {
|
||||
ModLoadingContext.get().setActiveContainer(target);
|
||||
target.activityMap.getOrDefault(target.modLoadingStage, ()->{}).run();
|
||||
target.acceptEvent(eventGenerator.apply(target));
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
}
|
||||
|
||||
public IModInfo getModInfo()
|
||||
{
|
||||
return modInfo;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Record> Optional<T> getCustomExtension(Class<? extends IExtensionPoint<T>> point) {
|
||||
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
|
||||
}
|
||||
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension)
|
||||
{
|
||||
extensionPoints.put(point, extension);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> displayTest);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTest);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void addConfig(final ModConfig modConfig) {
|
||||
configs.put(modConfig.getType(), modConfig);
|
||||
}
|
||||
|
||||
public void dispatchConfigEvent(IConfigEvent event) {
|
||||
configHandler.ifPresent(configHandler->configHandler.accept(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this mod match the supplied mod?
|
||||
*
|
||||
* @param mod to compare
|
||||
* @return if the mod matches
|
||||
*/
|
||||
public abstract boolean matches(Object mod);
|
||||
|
||||
/**
|
||||
* @return the mod object instance
|
||||
*/
|
||||
public abstract Object getMod();
|
||||
|
||||
/**
|
||||
* Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container.
|
||||
* @param e Event to accept
|
||||
*/
|
||||
protected <T extends Event & IModBusEvent> void acceptEvent(T e) {}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Master list of all mods - game-side version. This is classloaded in the game scope and
|
||||
* can dispatch game level events as a result.
|
||||
*/
|
||||
public class ModList
|
||||
{
|
||||
private static Logger LOGGER = LogManager.getLogger();
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private List<ModContainer> mods;
|
||||
private Map<String, ModContainer> indexedMods;
|
||||
private List<ModFileScanData> modFileScanData;
|
||||
|
||||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).map(ModFileInfo.class::cast).collect(Collectors.toList());
|
||||
this.sortedList = sortedList.stream().
|
||||
map(ModInfo.class::cast).
|
||||
collect(Collectors.toList());
|
||||
this.fileById = this.modFiles.stream().map(IModFileInfo::getMods).flatMap(Collection::stream).
|
||||
map(ModInfo.class::cast).
|
||||
collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
|
||||
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
|
||||
}
|
||||
|
||||
private String getModContainerState(String modId) {
|
||||
return getModContainerById(modId).map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE");
|
||||
}
|
||||
|
||||
private String fileToLine(IModFile mf) {
|
||||
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
|
||||
mf.getModInfos().get(0).getDisplayName(),
|
||||
mf.getModInfos().get(0).getModId(),
|
||||
mf.getModInfos().get(0).getVersion(),
|
||||
getModContainerState(mf.getModInfos().get(0).getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
private String crashReport() {
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
|
||||
{
|
||||
INSTANCE = new ModList(modFiles, sortedList);
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static ModList get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
thread.setName("modloading-worker-" + thread.getPoolIndex());
|
||||
// The default sets it to the SystemClassloader, so copy the current one.
|
||||
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return thread;
|
||||
}
|
||||
|
||||
public List<IModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public IModFileInfo getModFileById(String modid)
|
||||
{
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<List<Throwable>>> futureVisitor(
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
|
||||
return executor -> gather(
|
||||
this.mods.stream()
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, stateChange, executor))
|
||||
.collect(Collectors.toList()))
|
||||
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
static CompletionStage<List<Throwable>> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
|
||||
if (t.stream().noneMatch(e->e.getValue()!=null)) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
} else {
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
|
||||
CompletableFuture<List<Throwable>> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
throwables.forEach(exception -> {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length!=0) {
|
||||
Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed);
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
});
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
|
||||
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
|
||||
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
|
||||
futures.forEach(future -> {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
|
||||
});
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
void setLoadedMods(final List<ModContainer> modContainers)
|
||||
{
|
||||
this.mods = modContainers;
|
||||
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getModObjectById(String modId)
|
||||
{
|
||||
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId)
|
||||
{
|
||||
return Optional.ofNullable(this.indexedMods.get(modId));
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
|
||||
{
|
||||
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
|
||||
}
|
||||
|
||||
public List<IModInfo> getMods()
|
||||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public boolean isLoaded(String modTarget)
|
||||
{
|
||||
return this.indexedMods.containsKey(modTarget);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
public List<ModFileScanData> getAllScanData()
|
||||
{
|
||||
if (modFileScanData == null)
|
||||
{
|
||||
modFileScanData = this.sortedList.stream().
|
||||
map(IModInfo::getOwningFile).
|
||||
filter(Objects::nonNull).
|
||||
map(IModFileInfo::getFile).
|
||||
distinct().
|
||||
map(IModFile::getScanResult).
|
||||
collect(Collectors.toList());
|
||||
}
|
||||
return modFileScanData;
|
||||
|
||||
}
|
||||
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer)
|
||||
{
|
||||
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
|
||||
return modFiles.stream().map(IModFileInfo::getFile).map(function);
|
||||
}
|
||||
|
||||
public void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
|
||||
indexedMods.forEach(modContainerConsumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
return indexedMods.values().stream().map(function);
|
||||
}
|
||||
|
||||
private static class UncaughtModLoadingException extends ModLoadingException {
|
||||
public UncaughtModLoadingException(ModLoadingStage stage, Throwable originalException) {
|
||||
super(null, stage, "fml.modloading.uncaughterror", originalException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
/**
|
||||
* Loads mods.
|
||||
*
|
||||
* Dispatch cycle is seen in {@code #loadMods()} and {@code #finishMods()}
|
||||
*
|
||||
* Overall sequence for loadMods is:
|
||||
* <dl>
|
||||
* <dt>CONSTRUCT</dt>
|
||||
* <dd>Constructs the mod instance. Mods can typically setup basic environment such as Event listeners
|
||||
* and Configuration specifications here.</dd>
|
||||
* <dt>Automated dispatches</dt>
|
||||
* <dd>Dispatches automated elements : {@code net.minecraftforge.fml.common.Mod.EventBusSubscriber},
|
||||
* {@code net.minecraftforge.event.RegistryEvent}, {@code net.minecraftforge.common.capabilities.CapabilityInject}
|
||||
* and others</dd>
|
||||
* <dt>CONFIG_LOAD</dt>
|
||||
* <dd>Dispatches ConfigLoadEvent to mods</dd>
|
||||
* <dt>COMMON_SETUP</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent} to mods</dd>
|
||||
* <dt>SIDED_SETUP</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent} or
|
||||
* {@code net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent} to mods</dd>
|
||||
* </dl>
|
||||
*
|
||||
* Overall sequence for finishMods is:
|
||||
* <dl>
|
||||
* <dt>ENQUEUE_IMC</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent} to mods,
|
||||
* for enqueuing {@link InterModComms} messages for other mods to receive subsequently</dd>
|
||||
* <dt>PROCESS_IMC</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.InterModProcessEvent} to mods,
|
||||
* for processing {@link InterModComms} messages received from other mods prior to this event</dd>
|
||||
* <dt>COMPLETE</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent} to mods,
|
||||
* and completes the mod loading sequence.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class ModLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static ModLoader INSTANCE;
|
||||
private final LoadingModList loadingModList;
|
||||
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
private final List<ModLoadingWarning> loadingWarnings;
|
||||
private final ModStateManager stateManager;
|
||||
private boolean loadingStateValid;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
|
||||
private ModList modList;
|
||||
|
||||
private ModLoader()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.collect(Collectors.toList());
|
||||
this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
FMLLoader.getLoadingModList().getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense) //Search for files with missing licenses
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.forEach(this.loadingExceptions::add);
|
||||
this.stateManager = new ModStateManager();
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList);
|
||||
}
|
||||
|
||||
private String computeLanguageList() {
|
||||
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
private String computeModLauncherServiceList() {
|
||||
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
|
||||
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
|
||||
" "+mod.getOrDefault("name", "missing")+
|
||||
" "+mod.getOrDefault("type","NOTYPE")+
|
||||
" "+mod.getOrDefault("description", "")).
|
||||
collect(Collectors.joining("\n\t\t","\t\t",""));
|
||||
}
|
||||
|
||||
public static ModLoader get()
|
||||
{
|
||||
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run on the primary starting thread by ClientModLoader and ServerModLoader
|
||||
* @param syncExecutor An executor to run tasks on the main thread
|
||||
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
|
||||
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
|
||||
*/
|
||||
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
loadingStateValid = true;
|
||||
statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete"));
|
||||
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
|
||||
statusConsumer.ifPresent(c->c.accept("Loading mods"));
|
||||
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
|
||||
loadingModList.getMods());
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept("Building Mod List"));
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
.<ModContainer>mapMulti(Iterable::forEach)
|
||||
.toList();
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
modList.setLoadedMods(modContainers);
|
||||
this.modList = modList;
|
||||
statusConsumer.ifPresent(c->c.accept("Dispatching gathering events"));
|
||||
stateManager.getStates(ModLoadingPhase.GATHER).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept("Gathering phase complete"));
|
||||
}
|
||||
|
||||
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
statusConsumer.ifPresent(c->c.accept("Performing load phase"));
|
||||
stateManager.getStates(ModLoadingPhase.LOAD).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept("Mod setup complete"));
|
||||
}
|
||||
|
||||
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
statusConsumer.ifPresent(c->c.accept("Performing completion phase"));
|
||||
stateManager.getStates(ModLoadingPhase.COMPLETE).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
|
||||
}
|
||||
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
|
||||
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
|
||||
state.buildTransition(syncExecutor, parallelExecutor).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
}
|
||||
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
|
||||
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
}
|
||||
|
||||
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<List<Throwable>> transition) {
|
||||
while (!transition.isDone()) {
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
try {
|
||||
transition.join();
|
||||
} catch (CompletionException e) {
|
||||
loadingStateValid = false;
|
||||
Throwable t = e.getCause();
|
||||
final List<Throwable> notModLoading = Arrays.stream(t.getSuppressed())
|
||||
.filter(obj -> !(obj instanceof ModLoadingException))
|
||||
.collect(Collectors.toList());
|
||||
if (!notModLoading.isEmpty()) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
|
||||
.filter(ModLoadingException.class::isInstance)
|
||||
.map(ModLoadingException.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final IModFile modFile)
|
||||
{
|
||||
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
|
||||
|
||||
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
|
||||
final List<ModContainer> containers = modFile.getScanResult().getTargets()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
modFile.getFilePath(),
|
||||
containers.size(), containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList()),
|
||||
modInfoMap.size(), modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList()));
|
||||
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
}
|
||||
// remove errored mod containers
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
|
||||
try {
|
||||
final String modId = idToProviderEntry.getKey();
|
||||
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
|
||||
IModInfo info = Optional.ofNullable(modInfoMap.get(modId)).
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
|
||||
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
|
||||
} catch (ModLoadingException mle) {
|
||||
// exceptions are caught and added to the error list for later handling
|
||||
loadingExceptions.add(mle);
|
||||
// return an errored container instance here, because we tried and failed building a container.
|
||||
return new ErroredModContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the current mod loading state is valid. Use if you interact with vanilla systems directly during loading
|
||||
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
|
||||
*/
|
||||
public static boolean isLoadingStateValid() {
|
||||
return get().loadingStateValid;
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(generator.apply(mc)));
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void postEvent(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e));
|
||||
}
|
||||
|
||||
public List<ModLoadingWarning> getWarnings()
|
||||
{
|
||||
return ImmutableList.copyOf(this.loadingWarnings);
|
||||
}
|
||||
|
||||
public void addWarning(ModLoadingWarning warning)
|
||||
{
|
||||
this.loadingWarnings.add(warning);
|
||||
}
|
||||
|
||||
private static boolean runningDataGen = false;
|
||||
|
||||
public static boolean isDataGenRunning () {
|
||||
return runningDataGen;
|
||||
}
|
||||
|
||||
private static class ErroredModContainer extends ModContainer {
|
||||
public ErroredModContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(final Object mod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getMod() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext
|
||||
{
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private ModContainer activeContainer;
|
||||
|
||||
public void setActiveContainer(final ModContainer container) {
|
||||
this.activeContainer = container;
|
||||
this.languageExtension = container == null ? null : container.contextExtension.get();
|
||||
}
|
||||
|
||||
public ModContainer getActiveContainer() {
|
||||
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
|
||||
}
|
||||
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link IExtensionPoint} with the mod container.
|
||||
* @param point The extension point to register
|
||||
* @param extension An extension operator
|
||||
* @param <T> The type signature of the extension operator
|
||||
*/
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
|
||||
getActiveContainer().registerExtensionPoint(point, extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
|
||||
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)}.</p>
|
||||
* @param displayTest The {@link IExtensionPoint.DisplayTest} to register
|
||||
*/
|
||||
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
|
||||
getActiveContainer().registerDisplayTest(() -> displayTest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
|
||||
* <p>A shorthand for registering a DisplayTest supplier with {@link #registerExtensionPoint(Class, Supplier)}.</p>
|
||||
* @param displayTest The {@link Supplier<IExtensionPoint.DisplayTest>} to register
|
||||
*/
|
||||
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
|
||||
getActiveContainer().registerDisplayTest(displayTest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
|
||||
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
|
||||
* creates the DisplayTest instance for you using the provided parameters.</p>
|
||||
* @see IExtensionPoint.DisplayTest#DisplayTest(String, BiPredicate)
|
||||
*/
|
||||
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
|
||||
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
|
||||
* creates the DisplayTest instance for you using the provided parameters.</p>
|
||||
* @see IExtensionPoint.DisplayTest#DisplayTest(Supplier, BiPredicate)
|
||||
*/
|
||||
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T extension() {
|
||||
return (T)languageExtension;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* General purpose mod loading error message
|
||||
*/
|
||||
public class ModLoadingException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 2048947398536935507L;
|
||||
/**
|
||||
* Mod Info for mod with issue
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this error was encountered
|
||||
*/
|
||||
private final ModLoadingStage errorStage;
|
||||
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingException(final IModInfo modInfo, final ModLoadingStage errorStage, final String i18nMessage, final Throwable originalException, Object... context) {
|
||||
super("Mod Loading Exception", originalException);
|
||||
this.modInfo = modInfo;
|
||||
this.errorStage = errorStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
static Stream<ModLoadingException> fromEarlyException(final EarlyLoadingException e) {
|
||||
return e.getAllData().stream().map(ed->new ModLoadingException(ed.getModInfo(), ModLoadingStage.VALIDATE, ed.getI18message(), e.getCause(), ed.getArgs()));
|
||||
}
|
||||
|
||||
public String getI18NMessage() {
|
||||
return i18nMessage;
|
||||
}
|
||||
|
||||
public Object[] getContext() {
|
||||
return context.toArray();
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, errorStage, getCause()), context.stream()).toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return formatToString();
|
||||
}
|
||||
|
||||
public IModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
public String getCleanMessage() {
|
||||
return Bindings.getMessageParser().get().stripControlCodes(formatToString());
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
public enum ModLoadingPhase {
|
||||
ERROR, // Special state for error handling
|
||||
GATHER,
|
||||
LOAD,
|
||||
COMPLETE,
|
||||
DONE;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
public enum ModLoadingStage
|
||||
{
|
||||
ERROR,
|
||||
VALIDATE,
|
||||
CONSTRUCT,
|
||||
COMMON_SETUP,
|
||||
SIDED_SETUP,
|
||||
ENQUEUE_IMC,
|
||||
PROCESS_IMC,
|
||||
COMPLETE,
|
||||
DONE;
|
||||
|
||||
private final DeferredWorkQueue deferredWorkQueue;
|
||||
|
||||
ModLoadingStage() {
|
||||
deferredWorkQueue = new DeferredWorkQueue(this);
|
||||
}
|
||||
|
||||
ModLoadingStage nextState(Throwable exception) {
|
||||
return exception != null ? ERROR : values()[this.ordinal()+1];
|
||||
}
|
||||
|
||||
public ModLoadingStage currentState(Throwable exception) {
|
||||
return exception != null ? ERROR : this;
|
||||
}
|
||||
|
||||
public DeferredWorkQueue getDeferredWorkQueue() {
|
||||
return deferredWorkQueue;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public record ModLoadingState(String name, String previous, Function<ModList, String> message, ModLoadingPhase phase, Optional<Consumer<ModList>> inlineRunnable, Optional<IModStateTransition> transition) implements IModLoadingState {
|
||||
@Override
|
||||
public <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
return transition.map(t->t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return new ModLoadingState(name, previous, ml->"", phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase, IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, ml->"Processing transition "+name, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final Function<ModList, String> message, final ModLoadingPhase phase, IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase, Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, previous, ml->"Processing work "+name, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ModLoadingWarning
|
||||
{
|
||||
/**
|
||||
* Mod Info for mod with warning
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this warning was encountered
|
||||
*/
|
||||
private final ModLoadingStage warningStage;
|
||||
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
|
||||
this.modInfo = modInfo;
|
||||
this.warningStage = warningStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ModStateManager {
|
||||
static ModStateManager INSTANCE;
|
||||
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
|
||||
public ModStateManager() {
|
||||
INSTANCE = this;
|
||||
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
|
||||
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
|
||||
.map(IModStateProvider::getAllStates)
|
||||
.<IModLoadingState>mapMulti(Iterable::forEach)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
public List<IModLoadingState> getStates(final ModLoadingPhase phase) {
|
||||
var nodes = stateMap.get(phase);
|
||||
var lookup = nodes.stream().collect(Collectors.toMap(IModLoadingState::name, Function.identity()));
|
||||
|
||||
var graph = GraphBuilder.directed().allowsSelfLoops(false).<IModLoadingState>build();
|
||||
var dummy = ModLoadingState.empty("", "", phase);
|
||||
nodes.forEach(graph::addNode);
|
||||
graph.addNode(dummy);
|
||||
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
|
||||
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
public class ModWorkManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
|
||||
public interface DrivenExecutor extends Executor {
|
||||
boolean selfDriven();
|
||||
boolean driveOne();
|
||||
|
||||
default void drive(Runnable ticker) {
|
||||
if (!selfDriven()) {
|
||||
while (driveOne()) {
|
||||
ticker.run();
|
||||
}
|
||||
} else {
|
||||
// park for a bit so other threads can schedule
|
||||
LockSupport.parkNanos(PARK_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class SyncExecutor implements DrivenExecutor {
|
||||
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
final Runnable task = tasks.pollFirst();
|
||||
if (task != null) {
|
||||
task.run();
|
||||
}
|
||||
return task != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
tasks.addLast(command);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappingExecutor implements DrivenExecutor {
|
||||
private final Executor wrapped;
|
||||
|
||||
public WrappingExecutor(final Executor executor) {
|
||||
this.wrapped = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
wrapped.execute(command);
|
||||
}
|
||||
}
|
||||
|
||||
private static SyncExecutor syncExecutor;
|
||||
|
||||
public static DrivenExecutor syncExecutor() {
|
||||
if (syncExecutor == null)
|
||||
syncExecutor = new SyncExecutor();
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
public static DrivenExecutor wrappedExecutor(Executor executor) {
|
||||
return new WrappingExecutor(executor);
|
||||
}
|
||||
|
||||
private static ForkJoinPool parallelThreadPool;
|
||||
public static Executor parallelExecutor() {
|
||||
if (parallelThreadPool == null) {
|
||||
final int loadingThreadCount = FMLConfig.loadingThreadCount();
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
return parallelThreadPool;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
thread.setName("modloading-worker-" + thread.getPoolIndex());
|
||||
// The default sets it to the SystemClassloader, so copy the current one.
|
||||
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return thread;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class OptionalMod<T>
|
||||
{
|
||||
private final String modId;
|
||||
private T value;
|
||||
private boolean searched;
|
||||
|
||||
public static <M> OptionalMod<M> of(final String modId) {
|
||||
return new OptionalMod<>(modId);
|
||||
}
|
||||
|
||||
private static OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static <T> OptionalMod<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
|
||||
return t;
|
||||
}
|
||||
|
||||
private OptionalMod(boolean searched) {
|
||||
this.searched = searched;
|
||||
this.modId = "";
|
||||
}
|
||||
|
||||
private OptionalMod(String modId)
|
||||
{
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
private T getValue()
|
||||
{
|
||||
if (!searched) {
|
||||
this.value = ModList.get().<T>getModObjectById(this.modId).orElse(null);
|
||||
searched = true;
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod is present in this {@code OptionalMod}, returns the value,
|
||||
* otherwise throws {@code NoSuchElementException}.
|
||||
*
|
||||
* @return the modobject held by this {@code OptionalMod}
|
||||
* @throws NoSuchElementException if there is no modobject present
|
||||
*
|
||||
* @see Optional#isPresent()
|
||||
*/
|
||||
public T get() {
|
||||
if (getValue() == null) {
|
||||
throw new NoSuchElementException("No value present");
|
||||
}
|
||||
return getValue();
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if there is a mod object present, otherwise {@code false}.
|
||||
*
|
||||
* @return {@code true} if there is a mod object present, otherwise {@code false}
|
||||
*/
|
||||
public boolean isPresent() {
|
||||
return getValue() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, invoke the specified consumer with the object,
|
||||
* otherwise do nothing.
|
||||
*
|
||||
* @param consumer block to be executed if a mod object is present
|
||||
* @throws NullPointerException if mod object is present and {@code consumer} is
|
||||
* null
|
||||
*/
|
||||
public void ifPresent(Consumer<? super T> consumer) {
|
||||
if (getValue() != null)
|
||||
consumer.accept(getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, and the mod object matches the given predicate,
|
||||
* return an {@code OptionalMod} describing the value, otherwise return an
|
||||
* empty {@code OptionalMod}.
|
||||
*
|
||||
* @param predicate a predicate to apply to the mod object, if present
|
||||
* @return an {@code OptionalMod} describing the value of this {@code OptionalMod}
|
||||
* if a mod object is present and the mod object matches the given predicate,
|
||||
* otherwise an empty {@code OptionalMod}
|
||||
* @throws NullPointerException if the predicate is null
|
||||
*/
|
||||
public OptionalMod<T> filter(Predicate<? super T> predicate) {
|
||||
Objects.requireNonNull(predicate);
|
||||
if (!isPresent())
|
||||
return this;
|
||||
else
|
||||
return predicate.test(getValue()) ? this : empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, apply the provided mapping function to it,
|
||||
* and if the result is non-null, return an {@code Optional} describing the
|
||||
* result. Otherwise return an empty {@code Optional}.
|
||||
*
|
||||
* @apiNote This method supports post-processing on optional values, without
|
||||
* the need to explicitly check for a return status.
|
||||
*
|
||||
* @param <U> The type of the result of the mapping function
|
||||
* @param mapper a mapping function to apply to the mod object, if present
|
||||
* @return an {@code Optional} describing the result of applying a mapping
|
||||
* function to the mod object of this {@code OptionalMod}, if a mod object is present,
|
||||
* otherwise an empty {@code Optional}
|
||||
* @throws NullPointerException if the mapping function is null
|
||||
*/
|
||||
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
if (!isPresent())
|
||||
return Optional.empty();
|
||||
else {
|
||||
return Optional.ofNullable(mapper.apply(getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a value is present, apply the provided {@code Optional}-bearing
|
||||
* mapping function to it, return that result, otherwise return an empty
|
||||
* {@code Optional}. This method is similar to {@link #map(Function)},
|
||||
* but the provided mapper is one whose result is already an {@code Optional},
|
||||
* and if invoked, {@code flatMap} does not wrap it with an additional
|
||||
* {@code Optional}.
|
||||
*
|
||||
* @param <U> The type parameter to the {@code Optional} returned by
|
||||
* @param mapper a mapping function to apply to the mod object, if present
|
||||
* the mapping function
|
||||
* @return the result of applying an {@code Optional}-bearing mapping
|
||||
* function to the value of this {@code Optional}, if a value is present,
|
||||
* otherwise an empty {@code Optional}
|
||||
* @throws NullPointerException if the mapping function is null or returns
|
||||
* a null result
|
||||
*/
|
||||
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
if (!isPresent())
|
||||
return Optional.empty();
|
||||
else {
|
||||
return Objects.requireNonNull(mapper.apply(getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mod object if present, otherwise return {@code other}.
|
||||
*
|
||||
* @param other the mod object to be returned if there is no mod object present, may
|
||||
* be null
|
||||
* @return the mod object, if present, otherwise {@code other}
|
||||
*/
|
||||
public T orElse(T other) {
|
||||
return getValue() != null ? getValue() : other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mod object if present, otherwise invoke {@code other} and return
|
||||
* the result of that invocation.
|
||||
*
|
||||
* @param other a {@code Supplier} whose result is returned if no mod object
|
||||
* is present
|
||||
* @return the mod object if present otherwise the result of {@code other.get()}
|
||||
* @throws NullPointerException if mod object is not present and {@code other} is
|
||||
* null
|
||||
*/
|
||||
public T orElseGet(Supplier<? extends T> other) {
|
||||
return getValue() != null ? getValue() : other.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contained mod object, if present, otherwise throw an exception
|
||||
* to be created by the provided supplier.
|
||||
*
|
||||
* @apiNote A method reference to the exception constructor with an empty
|
||||
* argument list can be used as the supplier. For example,
|
||||
* {@code IllegalStateException::new}
|
||||
*
|
||||
* @param <X> Type of the exception to be thrown
|
||||
* @param exceptionSupplier The supplier which will return the exception to
|
||||
* be thrown
|
||||
* @return the present mod object
|
||||
* @throws X if there is no mod object present
|
||||
* @throws NullPointerException if no mod object is present and
|
||||
* {@code exceptionSupplier} is null
|
||||
*/
|
||||
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
|
||||
if (getValue() != null) {
|
||||
return getValue();
|
||||
} else {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof OptionalMod) {
|
||||
return Objects.equals(((OptionalMod)obj).modId, modId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(modId);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StartupMessageManager {
|
||||
public static void addModMessage(final String message) {
|
||||
net.minecraftforge.fml.loading.progress.StartupMessageManager.addModMessage(message);
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> modLoaderConsumer() {
|
||||
return net.minecraftforge.fml.loading.progress.StartupMessageManager.modLoaderConsumer();
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> mcLoaderConsumer() {
|
||||
return net.minecraftforge.fml.loading.progress.StartupMessageManager.mcLoaderConsumer();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
public enum ThreadSelector implements BinaryOperator<Executor> {
|
||||
SYNC((sync, parallel) -> sync),
|
||||
PARALLEL((sync, parallel) -> parallel);
|
||||
|
||||
private final BinaryOperator<Executor> selector;
|
||||
|
||||
ThreadSelector(final BinaryOperator<Executor> selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor apply(final Executor sync, final Executor parallel) {
|
||||
return this.selector.apply(sync, parallel);
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static net.minecraftforge.fml.VersionChecker.Status.*;
|
||||
|
||||
public class VersionChecker
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final int MAX_HTTP_REDIRECTS = Integer.getInteger("http.maxRedirects", 20);
|
||||
private static final int HTTP_TIMEOUT_SECS = Integer.getInteger("http.timeoutSecs", 15);
|
||||
|
||||
public enum Status
|
||||
{
|
||||
PENDING(),
|
||||
FAILED(),
|
||||
UP_TO_DATE(),
|
||||
OUTDATED(3, true),
|
||||
AHEAD(),
|
||||
BETA(),
|
||||
BETA_OUTDATED(6, true);
|
||||
|
||||
final int sheetOffset;
|
||||
final boolean draw, animated;
|
||||
|
||||
Status()
|
||||
{
|
||||
this(0, false, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset)
|
||||
{
|
||||
this(sheetOffset, true, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean animated)
|
||||
{
|
||||
this(sheetOffset, true, animated);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean draw, boolean animated)
|
||||
{
|
||||
this.sheetOffset = sheetOffset;
|
||||
this.draw = draw;
|
||||
this.animated = animated;
|
||||
}
|
||||
|
||||
public int getSheetOffset()
|
||||
{
|
||||
return sheetOffset;
|
||||
}
|
||||
|
||||
public boolean shouldDraw()
|
||||
{
|
||||
return draw;
|
||||
}
|
||||
|
||||
public boolean isAnimated()
|
||||
{
|
||||
return animated;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public record CheckResult(VersionChecker.Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url) {}
|
||||
|
||||
public static void startVersionCheck()
|
||||
{
|
||||
new Thread("Forge Version Check")
|
||||
{
|
||||
private HttpClient client;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (!FMLConfig.runVersionCheck())
|
||||
{
|
||||
LOGGER.info("Global Forge version check system disabled, no further processing.");
|
||||
return;
|
||||
}
|
||||
|
||||
client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS)).build();
|
||||
gatherMods().forEach(this::process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response body as a String for the given URL while following redirects
|
||||
*/
|
||||
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
|
||||
URL currentUrl = url;
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
|
||||
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
|
||||
sb.append(mod.getModId()).append('/').append(mod.getVersion());
|
||||
final String userAgent = sb.toString();
|
||||
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
|
||||
{
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(currentUrl.toURI())
|
||||
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
|
||||
.setHeader("Accept-Encoding", "gzip")
|
||||
.setHeader("User-Agent", userAgent)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
final HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
if (responseCode >= 300 && responseCode <= 399)
|
||||
{
|
||||
String newLocation = response.headers().firstValue("Location")
|
||||
.orElseThrow(() -> new IOException("Got a 3xx response code but Location header was null while trying to fetch " + url));
|
||||
currentUrl = new URL(currentUrl, newLocation);
|
||||
continue;
|
||||
}
|
||||
|
||||
final boolean isGzipEncoded = response.headers().firstValue("Content-Encoding").orElse("").equals("gzip");
|
||||
|
||||
final String bodyStr;
|
||||
try (InputStream inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body())
|
||||
{
|
||||
try (var bufferedReader = new BufferedReader(new InputStreamReader(inStream)))
|
||||
{
|
||||
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
return bodyStr;
|
||||
}
|
||||
throw new IOException("Too many redirects while trying to fetch " + url);
|
||||
}
|
||||
|
||||
private void process(IModInfo mod)
|
||||
{
|
||||
Status status = PENDING;
|
||||
ComparableVersion target = null;
|
||||
Map<ComparableVersion, String> changes = null;
|
||||
String display_url = null;
|
||||
try
|
||||
{
|
||||
if (mod.getUpdateURL().isEmpty()) return;
|
||||
URL url = mod.getUpdateURL().get();
|
||||
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
|
||||
|
||||
String data = openUrlString(url, mod);
|
||||
|
||||
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> json = new Gson().fromJson(data, Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> promos = (Map<String, String>)json.get("promos");
|
||||
display_url = (String)json.get("homepage");
|
||||
|
||||
var mcVersion = FMLLoader.versionInfo().mcVersion();
|
||||
String rec = promos.get(mcVersion + "-recommended");
|
||||
String lat = promos.get(mcVersion + "-latest");
|
||||
ComparableVersion current = new ComparableVersion(mod.getVersion().toString());
|
||||
|
||||
if (rec != null)
|
||||
{
|
||||
ComparableVersion recommended = new ComparableVersion(rec);
|
||||
int diff = recommended.compareTo(current);
|
||||
|
||||
if (diff == 0)
|
||||
status = UP_TO_DATE;
|
||||
else if (diff < 0)
|
||||
{
|
||||
status = AHEAD;
|
||||
if (lat != null)
|
||||
{
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0)
|
||||
{
|
||||
status = OUTDATED;
|
||||
target = latest;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = OUTDATED;
|
||||
target = recommended;
|
||||
}
|
||||
}
|
||||
else if (lat != null)
|
||||
{
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0)
|
||||
status = BETA_OUTDATED;
|
||||
else
|
||||
status = BETA;
|
||||
target = latest;
|
||||
}
|
||||
else
|
||||
status = BETA;
|
||||
|
||||
LOGGER.info("[{}] Found status: {} Current: {} Target: {}", mod.getModId(), status, current, target);
|
||||
|
||||
changes = new LinkedHashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> tmp = (Map<String, String>)json.get(mcVersion);
|
||||
if (tmp != null)
|
||||
{
|
||||
List<ComparableVersion> ordered = new ArrayList<>();
|
||||
for (String key : tmp.keySet())
|
||||
{
|
||||
ComparableVersion ver = new ComparableVersion(key);
|
||||
if (ver.compareTo(current) > 0 && (target == null || ver.compareTo(target) < 1))
|
||||
{
|
||||
ordered.add(ver);
|
||||
}
|
||||
}
|
||||
Collections.sort(ordered);
|
||||
|
||||
for (ComparableVersion ver : ordered)
|
||||
{
|
||||
changes.put(ver, tmp.get(ver.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.warn("Failed to process update information", e);
|
||||
status = FAILED;
|
||||
}
|
||||
results.put(mod, new CheckResult(status, target, changes, display_url));
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
// Gather a list of mods that have opted in to this update system by providing a URL.
|
||||
private static List<IModInfo> gatherMods()
|
||||
{
|
||||
List<IModInfo> ret = new LinkedList<>();
|
||||
for (IModInfo info : ModList.get().getMods()) {
|
||||
if (info.getUpdateURL().isPresent())
|
||||
ret.add(info);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Map<IModInfo, CheckResult> results = new ConcurrentHashMap<>();
|
||||
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
|
||||
|
||||
public static CheckResult getResult(IModInfo mod)
|
||||
{
|
||||
return results.getOrDefault(mod, PENDING_CHECK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.ConfigFormat;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileWatcher;
|
||||
import com.electronwill.nightconfig.core.io.ParsingException;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
|
||||
|
||||
public class ConfigFileTypeHandler {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
|
||||
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath());
|
||||
|
||||
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
|
||||
return (c) -> {
|
||||
final Path configPath = configBasePath.resolve(c.getFileName());
|
||||
final CommentedFileConfig configData = CommentedFileConfig.builder(configPath).sync().
|
||||
preserveInsertionOrder().
|
||||
autosave().
|
||||
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
|
||||
writingMode(WritingMode.REPLACE).
|
||||
build();
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
}
|
||||
catch (ParsingException ex)
|
||||
{
|
||||
throw new ConfigLoadingException(c, ex);
|
||||
}
|
||||
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
|
||||
try {
|
||||
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
|
||||
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't watch config file", e);
|
||||
}
|
||||
return configData;
|
||||
};
|
||||
}
|
||||
|
||||
public void unload(Path configBasePath, ModConfig config) {
|
||||
Path configPath = configBasePath.resolve(config.getFileName());
|
||||
try {
|
||||
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
Files.createDirectories(file.getParent());
|
||||
Path p = defaultConfigPath.resolve(modConfig.getFileName());
|
||||
if (Files.exists(p)) {
|
||||
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
|
||||
Files.copy(p, file);
|
||||
} else {
|
||||
Files.createFile(file);
|
||||
conf.initEmptyFile(file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void backUpConfig(final CommentedFileConfig commentedFileConfig)
|
||||
{
|
||||
backUpConfig(commentedFileConfig, 5); //TODO: Think of a way for mods to set their own preference (include a sanity check as well, no disk stuffing)
|
||||
}
|
||||
|
||||
public static void backUpConfig(final CommentedFileConfig commentedFileConfig, final int maxBackups)
|
||||
{
|
||||
Path bakFileLocation = commentedFileConfig.getNioPath().getParent();
|
||||
String bakFileName = FilenameUtils.removeExtension(commentedFileConfig.getFile().getName());
|
||||
String bakFileExtension = FilenameUtils.getExtension(commentedFileConfig.getFile().getName()) + ".bak";
|
||||
Path bakFile = bakFileLocation.resolve(bakFileName + "-1" + "." + bakFileExtension);
|
||||
try
|
||||
{
|
||||
for(int i = maxBackups; i > 0; i--)
|
||||
{
|
||||
Path oldBak = bakFileLocation.resolve(bakFileName + "-" + i + "." + bakFileExtension);
|
||||
if(Files.exists(oldBak))
|
||||
{
|
||||
if(i >= maxBackups)
|
||||
Files.delete(oldBak);
|
||||
else
|
||||
Files.move(oldBak, bakFileLocation.resolve(bakFileName + "-" + (i + 1) + "." + bakFileExtension));
|
||||
}
|
||||
}
|
||||
Files.copy(commentedFileConfig.getNioPath(), bakFile);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
LOGGER.warn(CONFIG, "Failed to back up config file {}", commentedFileConfig.getNioPath(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfigWatcher implements Runnable {
|
||||
private final ModConfig modConfig;
|
||||
private final CommentedFileConfig commentedFileConfig;
|
||||
private final ClassLoader realClassLoader;
|
||||
|
||||
ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig, final ClassLoader classLoader) {
|
||||
this.modConfig = modConfig;
|
||||
this.commentedFileConfig = commentedFileConfig;
|
||||
this.realClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Force the regular classloader onto the special thread
|
||||
Thread.currentThread().setContextClassLoader(realClassLoader);
|
||||
if (!this.modConfig.getSpec().isCorrecting()) {
|
||||
try
|
||||
{
|
||||
this.commentedFileConfig.load();
|
||||
if(!this.modConfig.getSpec().isCorrect(commentedFileConfig))
|
||||
{
|
||||
LOGGER.warn(CONFIG, "Configuration file {} is not correct. Correcting", commentedFileConfig.getFile().getAbsolutePath());
|
||||
ConfigFileTypeHandler.backUpConfig(commentedFileConfig);
|
||||
this.modConfig.getSpec().correct(commentedFileConfig);
|
||||
commentedFileConfig.save();
|
||||
}
|
||||
}
|
||||
catch (ParsingException ex)
|
||||
{
|
||||
throw new ConfigLoadingException(modConfig, ex);
|
||||
}
|
||||
LOGGER.debug(CONFIG, "Config file {} changed, sending notifies", this.modConfig.getFileName());
|
||||
this.modConfig.getSpec().afterReload();
|
||||
this.modConfig.fireEvent(IConfigEvent.reloading(this.modConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfigLoadingException extends RuntimeException
|
||||
{
|
||||
public ConfigLoadingException(ModConfig config, Exception cause)
|
||||
{
|
||||
super("Failed loading config file " + config.getFileName() + " of type " + config.getType() + " for modid " + config.getModId(), cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConfigTracker {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
static final Marker CONFIG = MarkerManager.getMarker("CONFIG");
|
||||
public static final ConfigTracker INSTANCE = new ConfigTracker();
|
||||
private final ConcurrentHashMap<String, ModConfig> fileMap;
|
||||
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
|
||||
private final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> configsByMod;
|
||||
|
||||
private ConfigTracker() {
|
||||
this.fileMap = new ConcurrentHashMap<>();
|
||||
this.configSets = new EnumMap<>(ModConfig.Type.class);
|
||||
this.configsByMod = new ConcurrentHashMap<>();
|
||||
this.configSets.put(ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
this.configSets.put(ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>());
|
||||
this.configSets.put(ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
}
|
||||
|
||||
void trackConfig(final ModConfig config) {
|
||||
if (this.fileMap.containsKey(config.getFileName())) {
|
||||
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId());
|
||||
throw new RuntimeException("Config conflict detected!");
|
||||
}
|
||||
this.fileMap.put(config.getFileName(), config);
|
||||
this.configSets.get(config.getType()).add(config);
|
||||
this.configsByMod.computeIfAbsent(config.getModId(), (k)->new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
|
||||
LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId());
|
||||
}
|
||||
|
||||
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Loading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
|
||||
}
|
||||
|
||||
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
|
||||
}
|
||||
|
||||
private void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
|
||||
config.setConfigData(configData);
|
||||
config.fireEvent(IConfigEvent.loading(config));
|
||||
config.save();
|
||||
}
|
||||
|
||||
private void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
if (config.getConfigData() != null) {
|
||||
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
config.save();
|
||||
config.getHandler().unload(configBasePath, config);
|
||||
config.setConfigData(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadDefaultServerConfigs() {
|
||||
configSets.get(ModConfig.Type.SERVER).forEach(modConfig -> {
|
||||
final CommentedConfig commentedConfig = CommentedConfig.inMemory();
|
||||
modConfig.getSpec().correct(commentedConfig);
|
||||
modConfig.setConfigData(commentedConfig);
|
||||
modConfig.fireEvent(IConfigEvent.loading(modConfig));
|
||||
});
|
||||
}
|
||||
|
||||
public String getConfigFileName(String modId, ModConfig.Type type) {
|
||||
return Optional.ofNullable(configsByMod.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
|
||||
map(ModConfig::getFullPath).map(Object::toString).orElse(null);
|
||||
}
|
||||
|
||||
public Map<ModConfig.Type, Set<ModConfig>> configSets() {
|
||||
return configSets;
|
||||
}
|
||||
|
||||
public ConcurrentHashMap<String, ModConfig> fileMap() {
|
||||
return fileMap;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.Bindings;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface IConfigEvent {
|
||||
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading) {}
|
||||
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
|
||||
|
||||
static IConfigEvent reloading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.reloading().apply(modConfig);
|
||||
}
|
||||
static IConfigEvent loading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.loading().apply(modConfig);
|
||||
}
|
||||
ModConfig getConfig();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends Event & IConfigEvent> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 2834 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user