mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0717f5765 | ||
|
|
b3703dac1d | ||
|
|
11e74bf8d3 | ||
|
|
dc0da958e0 | ||
|
|
defeedd11d | ||
|
|
756731a61f | ||
|
|
fd1b206cf2 | ||
|
|
dfe1f9240c | ||
|
|
f2c46fc2c1 | ||
|
|
9cb47ed7b7 | ||
|
|
1f321b8bd2 | ||
|
|
b1c37c25b5 | ||
|
|
f1a52f7326 | ||
|
|
9e54667efa | ||
|
|
8383a0ebac | ||
|
|
43850be0f6 | ||
|
|
729cc6ab55 | ||
|
|
92998b1c32 | ||
|
|
babb17f2ad | ||
|
|
85d0c98e6b | ||
|
|
dfcdb4f010 | ||
|
|
85c3ffffd1 | ||
|
|
f090e53e55 | ||
|
|
c1e29d2dfc | ||
|
|
b2d42fa577 | ||
|
|
1b5c118ec2 | ||
|
|
d399884186 | ||
|
|
091cc791b6 | ||
|
|
c921aeec50 | ||
|
|
f4aea1d8df | ||
|
|
ad271df245 | ||
|
|
39dc348f8b | ||
|
|
75eedc04a1 | ||
|
|
81f99b0116 | ||
|
|
f0a7a457bb | ||
|
|
cf454b7da8 | ||
|
|
afcce7d4e7 | ||
|
|
d4a2fbffa3 | ||
|
|
4b61de2088 | ||
|
|
bc2d0d1b25 | ||
|
|
e2c1da98be | ||
|
|
0f6d11b1ee | ||
|
|
340d332e19 | ||
|
|
56f19dd53d | ||
|
|
1fe420e4ab | ||
|
|
945209c283 | ||
|
|
3e5c3cabd5 | ||
|
|
b0f5a13f52 | ||
|
|
15fe8441f0 | ||
|
|
babef71616 | ||
|
|
1dd18ca27c | ||
|
|
65c7390874 | ||
|
|
fe3ad3b8f2 | ||
|
|
d05e76f7c4 | ||
|
|
bfa97bf097 | ||
|
|
469e9a6c1a | ||
|
|
58456c85af | ||
|
|
7fe5b02ef3 | ||
|
|
184392e02e | ||
|
|
d25a5466db | ||
|
|
b5073c10ef | ||
|
|
16bc7cf783 | ||
|
|
2d714f034f | ||
|
|
617ecb7ad3 | ||
|
|
dd8239c255 | ||
|
|
bdddc8c5d4 | ||
|
|
c1c619a109 | ||
|
|
f7eea35cb9 | ||
|
|
a2434e1607 | ||
|
|
b806ecd0ff | ||
|
|
8358b800e9 | ||
|
|
b25a092cf1 | ||
|
|
d812fdd0a3 | ||
|
|
2d3ee8a228 | ||
|
|
97a9ca65f1 | ||
|
|
f043fbbc51 | ||
|
|
a19ab3a8d4 | ||
|
|
bc0517a404 | ||
|
|
dc9c0d7e35 | ||
|
|
1a2207b3b2 | ||
|
|
84ab9764e3 | ||
|
|
bef68991f0 | ||
|
|
526c13d662 | ||
|
|
67ffb78c23 | ||
|
|
8f407c3a22 | ||
|
|
8980252e16 | ||
|
|
3baee80485 | ||
|
|
ca9599bb4d | ||
|
|
051db7907a | ||
|
|
bdba5ef2d3 | ||
|
|
838b9c0eda | ||
|
|
be584c54aa | ||
|
|
1b3640b42d | ||
|
|
be5dab5801 | ||
|
|
aae628cd18 | ||
|
|
e7bd08649f | ||
|
|
e1017a4c7e | ||
|
|
6f8ba9d295 | ||
|
|
ac94478ae7 | ||
|
|
515c6082e0 | ||
|
|
a753598113 | ||
|
|
bc6c4be4e4 | ||
|
|
98823a8f07 | ||
|
|
ecd8f1e54d | ||
|
|
b0caea8e20 | ||
|
|
e0477f8776 | ||
|
|
94e1e54b53 | ||
|
|
9ce72331b7 | ||
|
|
1c31f42dae | ||
|
|
0978a94358 | ||
|
|
e956a9c9f3 | ||
|
|
82026d11a4 | ||
|
|
6d233ac75c | ||
|
|
73c1934e7e | ||
|
|
bf1194ec75 | ||
|
|
22d2740725 | ||
|
|
02f26dc7a1 | ||
|
|
db14cbb0e0 | ||
|
|
a2b35cae92 | ||
|
|
aab0e4c82e | ||
|
|
6609708d14 | ||
|
|
ed198f062c | ||
|
|
4e2d656e9d | ||
|
|
2a4230868f | ||
|
|
cf778b1759 | ||
|
|
1b6daddd40 | ||
|
|
d7b137d144 | ||
|
|
b5d5855559 | ||
|
|
c0aa8dd808 | ||
|
|
4f2f5c02a0 | ||
|
|
7a6843fa4a | ||
|
|
f0ecf16c6b | ||
|
|
8998fa71b2 | ||
|
|
4e677a7558 | ||
|
|
07254b36d7 | ||
|
|
a51b8d256d | ||
|
|
75fcd9448e | ||
|
|
6b45999702 | ||
|
|
a4f52f4057 | ||
|
|
b8b3201372 | ||
|
|
41deadc1e2 | ||
|
|
82e8d06e54 | ||
|
|
51db5f2ff7 | ||
|
|
2ec9e515ff | ||
|
|
0579572148 | ||
|
|
83f4e6353c | ||
|
|
130953e519 | ||
|
|
7981093591 | ||
|
|
79112d6208 | ||
|
|
a3eb6263f4 | ||
|
|
d91bc9c73d | ||
|
|
c807665ab9 | ||
|
|
7b97c181b9 | ||
|
|
c42b0847d2 | ||
|
|
f182cb00c1 | ||
|
|
3181b5da23 | ||
|
|
983c119477 | ||
|
|
874ff1bba2 | ||
|
|
06d9d89f98 | ||
|
|
c713bad8c3 | ||
|
|
fe19491822 | ||
|
|
c80e8a1aca | ||
|
|
6845c094ab | ||
|
|
3c1126a7b4 | ||
|
|
326e0329b9 | ||
|
|
f9d0daa91c | ||
|
|
094200e687 | ||
|
|
bb4818630c | ||
|
|
7af3155b39 | ||
|
|
d988b4a811 | ||
|
|
18f5bdb7ca | ||
|
|
f41d493552 | ||
|
|
8cd2c9c63f | ||
|
|
427b335cac | ||
|
|
2049096c6f | ||
|
|
98d4fa6dc2 | ||
|
|
5306471aec | ||
|
|
aa8818b049 | ||
|
|
2d86f17363 | ||
|
|
3a36a01913 | ||
|
|
47d70a7f31 | ||
|
|
d5b5e6f4db | ||
|
|
e76d814439 | ||
|
|
b8b40e1f54 | ||
|
|
5e8c786406 | ||
|
|
5b8224f839 | ||
|
|
cd621eb081 | ||
|
|
b1113f0363 | ||
|
|
daf5933b79 | ||
|
|
47fd2e8604 | ||
|
|
05297fd72f | ||
|
|
f20cf19f94 | ||
|
|
061cd3da53 | ||
|
|
03202a8ab4 | ||
|
|
8a67be4afd | ||
|
|
4c1ab67f33 | ||
|
|
7e06a2105f | ||
|
|
4a0c8be742 | ||
|
|
0c030ee5e1 | ||
|
|
1134fac54c | ||
|
|
ac44d72530 | ||
|
|
12468bd8d2 | ||
|
|
a98eeb9d08 | ||
|
|
b558b132cd | ||
|
|
c102633566 | ||
|
|
cce002ad29 | ||
|
|
b1ed321438 | ||
|
|
c7c2478cb0 | ||
|
|
b70d99489b | ||
|
|
57f45eaca0 | ||
|
|
52fcc4d649 | ||
|
|
959ab9abc9 | ||
|
|
c3a91872fe | ||
|
|
4d074f0a1e | ||
|
|
c2225ab1da | ||
|
|
bb03b1a7d1 | ||
|
|
be7275443f | ||
|
|
dc1c973684 | ||
|
|
06d568408c | ||
|
|
2256e88fb4 | ||
|
|
9b962c8ff0 | ||
|
|
7656b75992 | ||
|
|
58a5449c2f | ||
|
|
702abe0665 | ||
|
|
2070b77fd4 | ||
|
|
fd00822015 | ||
|
|
17565c0ea2 | ||
|
|
22668ad2c2 | ||
|
|
0dcc3d700d | ||
|
|
2ab3d9b8a3 | ||
|
|
d578d8f6df | ||
|
|
fd61016a7e | ||
|
|
7f77d24f45 | ||
|
|
3ebdcb34d1 | ||
|
|
05240fecf7 | ||
|
|
d5860d7cfa | ||
|
|
7bb97dafda | ||
|
|
ca90babc4a | ||
|
|
4b813e4319 | ||
|
|
fa84c22662 | ||
|
|
2ae640b530 | ||
|
|
4f95a20c5e | ||
|
|
915d570b17 | ||
|
|
5979636ee5 | ||
|
|
5acfc6ae3c | ||
|
|
48341120fb | ||
|
|
ff45bb4c27 | ||
|
|
eabba46595 | ||
|
|
683bf99fe4 | ||
|
|
2a05c7c01d | ||
|
|
e386229b66 | ||
|
|
59b8ec5625 | ||
|
|
50ae5db357 | ||
|
|
3aa99a1250 | ||
|
|
00c8513129 | ||
|
|
9c1404fecc | ||
|
|
c11686ab6c | ||
|
|
2818984f6c | ||
|
|
db225ed04a | ||
|
|
0ec4c34a34 | ||
|
|
597ebc3267 | ||
|
|
e16f54eebe | ||
|
|
27480c15fa | ||
|
|
df0dea59f3 | ||
|
|
d7bc564512 | ||
|
|
e6a73ef36c | ||
|
|
1ba7c6aecd | ||
|
|
7c5e95222e | ||
|
|
38df3d2a15 | ||
|
|
3beaa817f3 | ||
|
|
3ebd7ba0ef | ||
|
|
1ba9c34650 | ||
|
|
2f9e726aea | ||
|
|
ade2b8cc80 | ||
|
|
9b144f4541 | ||
|
|
0c35436217 | ||
|
|
0e66b7b217 | ||
|
|
4ed9117799 | ||
|
|
270e778ac5 | ||
|
|
9528fd8bcc | ||
|
|
5a75314bb3 | ||
|
|
cceb8db74e | ||
|
|
8e7b08b96d | ||
|
|
194f11069f | ||
|
|
5ef0d667e0 | ||
|
|
8a1c1d7e53 | ||
|
|
1353c1d9fa | ||
|
|
70a8663605 | ||
|
|
f179eed971 | ||
|
|
d4c8708229 | ||
|
|
4008057803 | ||
|
|
997d8e0aa2 | ||
|
|
15bd5f0d30 | ||
|
|
ccaa20b006 | ||
|
|
c48698f680 | ||
|
|
4285c931e7 | ||
|
|
03c43cf3da | ||
|
|
8d14797416 | ||
|
|
e4837f65a3 | ||
|
|
ee46af66e5 | ||
|
|
2bec354317 | ||
|
|
727c7780b3 | ||
|
|
5410f9b532 | ||
|
|
4c09d8d774 | ||
|
|
aded6fb847 | ||
|
|
c2ad951336 | ||
|
|
7fb5dfa3e6 | ||
|
|
0f8c2fa454 | ||
|
|
dfc2aa9c6a | ||
|
|
8865092bb2 | ||
|
|
abc3353375 | ||
|
|
e7a8da4d0b | ||
|
|
f938051373 | ||
|
|
9db0d72232 | ||
|
|
9e6e789b73 | ||
|
|
bc0d7179cc | ||
|
|
aafa34439a | ||
|
|
168a3bc6fc | ||
|
|
2fb8b2eb18 | ||
|
|
da82705e86 | ||
|
|
a237a39d52 | ||
|
|
c042dc5542 | ||
|
|
1493d06b5a | ||
|
|
9a11201a9e | ||
|
|
c34094ea50 | ||
|
|
c531b2b5c7 | ||
|
|
8ade7bbaeb | ||
|
|
6347071976 | ||
|
|
17bd0c4f91 | ||
|
|
bafaf90329 | ||
|
|
fcb81aa660 | ||
|
|
eb8fe02a5e | ||
|
|
c1874bb5ab | ||
|
|
f0b90dc35d | ||
|
|
eed24f017f | ||
|
|
7d79f0d581 | ||
|
|
94ce098056 | ||
|
|
fbaf92105e | ||
|
|
1e6845669c | ||
|
|
796cb29904 | ||
|
|
42472a8ab8 | ||
|
|
954db14250 | ||
|
|
56976e4d20 | ||
|
|
c38e0de8f5 | ||
|
|
d33f24ff33 | ||
|
|
87c4f90fb2 | ||
|
|
1f031ad2c3 | ||
|
|
8f0d3da1e4 | ||
|
|
10ca83e90d | ||
|
|
e5d4e5d3ab | ||
|
|
c7e4e60052 | ||
|
|
093acab825 | ||
|
|
c3bd365baa | ||
|
|
02fd859d5c | ||
|
|
349e12f939 | ||
|
|
9d3eb9df8f | ||
|
|
3c39a36c26 | ||
|
|
b0a64ff8ac | ||
|
|
e1ad29edd0 | ||
|
|
5ac711839f | ||
|
|
415cdd0588 | ||
|
|
7f23b3b525 | ||
|
|
de1050e420 | ||
|
|
ad75cb8644 | ||
|
|
30f3a7dbba | ||
|
|
9d74a3520f | ||
|
|
57d6f31adb | ||
|
|
9bf227afeb | ||
|
|
72f5b3f84d | ||
|
|
df3c7ce19a | ||
|
|
6cb444455d | ||
|
|
64ded22276 | ||
|
|
41c88f6e52 | ||
|
|
cdd91968b4 | ||
|
|
898696d770 | ||
|
|
3dcb4a2c9c | ||
|
|
c458ff17fd | ||
|
|
7009c1aa89 | ||
|
|
4311edd4cd | ||
|
|
735382be03 | ||
|
|
14bca0d757 | ||
|
|
425eff39ff | ||
|
|
34291c1fb2 | ||
|
|
4011c3fb07 | ||
|
|
104daf8ef0 | ||
|
|
d9a04b881a | ||
|
|
ae62362d49 | ||
|
|
2f8c3ea738 | ||
|
|
f0c2554b1c | ||
|
|
bb547a5748 | ||
|
|
5dd3842b77 | ||
|
|
2ab4685149 | ||
|
|
edbc07ed22 | ||
|
|
e225dbc8d1 | ||
|
|
117be913da | ||
|
|
512d4d7271 | ||
|
|
a9237cff75 | ||
|
|
bc24487d0e | ||
|
|
c3e0b071a2 | ||
|
|
21787234f4 | ||
|
|
a9958b19db | ||
|
|
b25d9562f2 | ||
|
|
2765c0d8d9 | ||
|
|
82a20911cb | ||
|
|
f19f7e4670 | ||
|
|
e10f5c637e | ||
|
|
4bfe562f14 | ||
|
|
5e64ef9ecb | ||
|
|
dda1be91ea | ||
|
|
4f4849b60c | ||
|
|
3b754fef5e | ||
|
|
5e295f2485 | ||
|
|
5c9258491f | ||
|
|
af87677516 | ||
|
|
a68c886ea2 | ||
|
|
b129e385cc | ||
|
|
fdc921923c | ||
|
|
476e20ae96 | ||
|
|
d63516f2ac | ||
|
|
fd522d38b0 | ||
|
|
5f0d4abf23 | ||
|
|
8e3e8b2120 | ||
|
|
72fd6808d8 | ||
|
|
2c35d04495 | ||
|
|
f704d65d5a | ||
|
|
6c6a4817e7 | ||
|
|
ef3408c1eb | ||
|
|
0ea593c50e | ||
|
|
643b972e97 | ||
|
|
93ce9e72f7 | ||
|
|
f1bafce275 | ||
|
|
75408c1a70 | ||
|
|
00fafb07ce | ||
|
|
e49a347aea | ||
|
|
2c8ca313a0 | ||
|
|
fe7dcdf637 | ||
|
|
15daa2955d | ||
|
|
03af8a53a2 | ||
|
|
c7e4e4b5b4 | ||
|
|
d477425f70 | ||
|
|
a3449f2866 | ||
|
|
2a1f98aec2 | ||
|
|
bc9bd40d03 | ||
|
|
58344b0b15 | ||
|
|
edcc8b3c97 | ||
|
|
d494ba9be4 | ||
|
|
b92f56503e | ||
|
|
5a9be79785 | ||
|
|
225c2125c6 | ||
|
|
e2a932108e | ||
|
|
ec38717702 | ||
|
|
3009f5350c | ||
|
|
a24dd3c8ee | ||
|
|
3fae26a470 | ||
|
|
a44ac01aee | ||
|
|
b09f8d0fc8 | ||
|
|
57a916bf09 | ||
|
|
15d3df2818 | ||
|
|
63a42bad98 | ||
|
|
28d3c491a0 | ||
|
|
551296813e | ||
|
|
62b99be117 | ||
|
|
6683e9e660 | ||
|
|
3b9df7df1b | ||
|
|
a9a8d7ed8c | ||
|
|
80770898c6 | ||
|
|
335cb5edc4 | ||
|
|
7259887176 | ||
|
|
0320c185b5 | ||
|
|
2f5f696b68 | ||
|
|
d33e9c8251 | ||
|
|
21c50da8ec | ||
|
|
e2a205d2e7 | ||
|
|
950e7b032a | ||
|
|
d1a55a147e | ||
|
|
1fd8b1a4b8 | ||
|
|
41ea5b195b | ||
|
|
14e152579b | ||
|
|
dcf7bb1af2 | ||
|
|
456a48b12a | ||
|
|
aceb878a64 | ||
|
|
f451e21f39 | ||
|
|
c6ac740d31 | ||
|
|
dbf98d3888 | ||
|
|
1b7bdc3ec6 | ||
|
|
d414e31f08 | ||
|
|
b2036e9d23 | ||
|
|
abe4afe152 | ||
|
|
e7ad43c993 | ||
|
|
7631600b53 | ||
|
|
530a1e8eb0 | ||
|
|
54b2c18fcf | ||
|
|
eb1a9b57c6 | ||
|
|
5b6d54555e | ||
|
|
40a174310d | ||
|
|
be0a6819ac | ||
|
|
82a7ee1416 | ||
|
|
b4feee8046 | ||
|
|
df8954869a | ||
|
|
8a552136a2 | ||
|
|
34ec627457 | ||
|
|
28dd76ebbf | ||
|
|
b639bcb5c7 | ||
|
|
7538cf5501 | ||
|
|
1112819fc8 | ||
|
|
cdd38270e9 | ||
|
|
20e309c339 | ||
|
|
5f87bde00e | ||
|
|
a4e574fa6b | ||
|
|
ec7cb00f86 | ||
|
|
ebe375dac0 | ||
|
|
4a2572d545 | ||
|
|
55db597baa | ||
|
|
c24d05173f | ||
|
|
d711982a1c | ||
|
|
8a38416e0f | ||
|
|
a784bdb0d0 | ||
|
|
dc9a7f6ad5 | ||
|
|
417798f2eb | ||
|
|
394e76eed3 | ||
|
|
9e476551be | ||
|
|
1dc421e6a4 | ||
|
|
e431bb1caa | ||
|
|
8bd517ca91 | ||
|
|
69ddf58600 | ||
|
|
1e040c9f1f | ||
|
|
11bdf592c8 | ||
|
|
f4a827ab01 | ||
|
|
827ca83cfd | ||
|
|
e9f8acd191 | ||
|
|
febfd5ab62 | ||
|
|
4ca2a8058f | ||
|
|
4acafcccfd | ||
|
|
937b719e24 | ||
|
|
2b5bfd923c | ||
|
|
249aaa5806 | ||
|
|
46f57e6a9a | ||
|
|
cfd79130de | ||
|
|
6a25c99097 | ||
|
|
95be0d87d9 | ||
|
|
6125ec3bc8 | ||
|
|
9944a8252a | ||
|
|
fd544dd414 | ||
|
|
c00777e6ea | ||
|
|
b2cea78006 | ||
|
|
79272361f0 | ||
|
|
fe4be54774 | ||
|
|
eecb8cc783 | ||
|
|
5e2e404828 | ||
|
|
96303f6570 | ||
|
|
166feefc02 | ||
|
|
cd67762bee | ||
|
|
347a0b3c0d | ||
|
|
be1698bb15 | ||
|
|
a3e9d7a397 | ||
|
|
b2dbe9de51 | ||
|
|
a31adc2823 | ||
|
|
30a33f5c3e | ||
|
|
b01a217f45 | ||
|
|
69136fce19 | ||
|
|
ee21224fff | ||
|
|
804f390064 | ||
|
|
ca2023fa80 | ||
|
|
4e562664f4 | ||
|
|
c8d3ad1d1c | ||
|
|
b7379953d5 | ||
|
|
9d7f5dd32a | ||
|
|
049ed79e7b | ||
|
|
675bf8bde2 | ||
|
|
8dc58ad469 | ||
|
|
827c963d7e | ||
|
|
2ac616b293 | ||
|
|
dcfbc08504 | ||
|
|
19f470ee8c | ||
|
|
a96d078a7a | ||
|
|
b375da6962 | ||
|
|
d17f10d80c | ||
|
|
a336e443f7 | ||
|
|
c5a1c90366 | ||
|
|
95c0aa1e98 | ||
|
|
559b801cc4 | ||
|
|
aa97ddad8b | ||
|
|
d612dc3a6d | ||
|
|
9165d4c3a1 | ||
|
|
12d0f96fc2 | ||
|
|
bec9d6f534 | ||
|
|
892c3669e0 | ||
|
|
ce13efbac9 | ||
|
|
1018b077dc | ||
|
|
9746db5129 | ||
|
|
c4de1f33ba | ||
|
|
a8a4623d24 | ||
|
|
10b51f257f | ||
|
|
52d7df06da | ||
|
|
fcc819584a | ||
|
|
4721d4c461 | ||
|
|
dbdc345d4a | ||
|
|
14c6fc3388 | ||
|
|
33419ed5b1 | ||
|
|
9ec1e24dc5 | ||
|
|
06804675be | ||
|
|
2f43f9910d | ||
|
|
ea9a72af85 | ||
|
|
9a92363e15 | ||
|
|
94389d0efd | ||
|
|
9080886c1a | ||
|
|
13ca55f34d | ||
|
|
5dbea18e30 | ||
|
|
30cfd848d8 | ||
|
|
80d08dbf3a | ||
|
|
7d4c238165 | ||
|
|
0ea9401d90 | ||
|
|
4bdfd8674d | ||
|
|
3e9e0a6447 | ||
|
|
76da41dd46 | ||
|
|
6ddf56ef15 | ||
|
|
eeec737188 | ||
|
|
119ac8f4ab | ||
|
|
bdf0d9cf22 | ||
|
|
bd58ced0e4 | ||
|
|
5af6649435 | ||
|
|
695e1fbb94 | ||
|
|
b027a92dd2 | ||
|
|
be5446b91c | ||
|
|
064ae6961b | ||
|
|
22d609ed6c | ||
|
|
315bbb74ff | ||
|
|
4e4d31b68c | ||
|
|
1a17fa6129 | ||
|
|
ca9df21258 | ||
|
|
93d7fa54fc | ||
|
|
f235d80d9f | ||
|
|
be34dd89a9 | ||
|
|
6663f23e13 | ||
|
|
f9f7ef2580 | ||
|
|
86352e9c19 | ||
|
|
8c8b31435b | ||
|
|
16b3e2780e | ||
|
|
1121f18413 | ||
|
|
96804fb4df | ||
|
|
03a6dbb405 | ||
|
|
77e8c72589 | ||
|
|
043e49a694 | ||
|
|
5169f1f6fe | ||
|
|
2bd83b26aa | ||
|
|
e6c1814122 | ||
|
|
7f2892de7e | ||
|
|
439ba462d6 | ||
|
|
7c6ae3e61e | ||
|
|
f9b894ecbf | ||
|
|
df53db351e | ||
|
|
9aa04c8036 | ||
|
|
626496cec0 | ||
|
|
022482a3bd | ||
|
|
dd228a9ac1 | ||
|
|
a6d1646591 | ||
|
|
39f9f393db | ||
|
|
d35784eb14 | ||
|
|
31fb15c88d | ||
|
|
29f175743b | ||
|
|
33499db93f | ||
|
|
84381553a9 | ||
|
|
2a7dde3d2e | ||
|
|
aa4f24c915 | ||
|
|
1d87831773 | ||
|
|
65e99f9b58 | ||
|
|
b98991dc70 | ||
|
|
435e65eb18 | ||
|
|
8211a5048a | ||
|
|
167f14f4a1 | ||
|
|
f332642409 | ||
|
|
77d41023b5 | ||
|
|
da2594289c | ||
|
|
6f3da80b5a | ||
|
|
474b47ea51 | ||
|
|
b6ce4fb066 | ||
|
|
1406f2bf1f | ||
|
|
5125775992 | ||
|
|
929c7b7a0b | ||
|
|
7bf02e7dcd | ||
|
|
9d0d249eaf | ||
|
|
c4b1df1fc7 | ||
|
|
b3f253df61 | ||
|
|
dc099069fa | ||
|
|
e80c785563 | ||
|
|
1f9611204f | ||
|
|
0120f6274d | ||
|
|
c0e74bc8a8 | ||
|
|
80feb0aeb2 | ||
|
|
f47429717d | ||
|
|
0239a736ec | ||
|
|
5c488c9007 | ||
|
|
53c039680c | ||
|
|
7379e8b9a4 | ||
|
|
2a0d9e8dac | ||
|
|
aacba8e38e | ||
|
|
f4fd1c2f16 | ||
|
|
813f3d630d | ||
|
|
76cea5a7da | ||
|
|
e33f794298 | ||
|
|
72d05d17a9 | ||
|
|
19f9b309a6 | ||
|
|
5b53616a47 | ||
|
|
5f9e6f3b56 | ||
|
|
0ad8e2c27f | ||
|
|
55f29764f8 | ||
|
|
a4f7087b92 | ||
|
|
a95b968c13 | ||
|
|
802298d99d | ||
|
|
5363dc5d99 | ||
|
|
9602c22083 | ||
|
|
17500032ae | ||
|
|
65bc41281f | ||
|
|
ba15737fe3 | ||
|
|
58f93263e2 | ||
|
|
d97f28141c | ||
|
|
0340664dbf | ||
|
|
d2cfda3fb4 | ||
|
|
48225e1bea | ||
|
|
37ea725749 | ||
|
|
733b812c7b | ||
|
|
9ab29ea8fe | ||
|
|
fe26b77e3b | ||
|
|
b8eca88f75 | ||
|
|
e039531497 | ||
|
|
9017e24b40 | ||
|
|
75570079ae | ||
|
|
e2167140db | ||
|
|
a89ce2d6f1 | ||
|
|
007cd42ec6 | ||
|
|
d16a8d6d5d | ||
|
|
69321a363e | ||
|
|
0e1ee4b1d2 | ||
|
|
cc1a9be2b4 | ||
|
|
1d7da4c595 | ||
|
|
1ac432c594 | ||
|
|
276871d5a3 | ||
|
|
8460227ddf | ||
|
|
f450d4025d | ||
|
|
93a815957d | ||
|
|
84b7cdc1cd | ||
|
|
83fee0b3d4 | ||
|
|
aeaebf5785 | ||
|
|
d88e660cbb | ||
|
|
ab949c7cca | ||
|
|
7728dde36b | ||
|
|
004ab59e6f | ||
|
|
7bfd2b8f34 | ||
|
|
f169c99348 | ||
|
|
d3d3bdc769 | ||
|
|
0a0f3232ea | ||
|
|
50d75a27e1 | ||
|
|
19f8d2a793 | ||
|
|
f173bde812 | ||
|
|
dd10933ab0 | ||
|
|
9ffa44f254 | ||
|
|
d8f41adf39 | ||
|
|
c3e84646db | ||
|
|
2094f99640 | ||
|
|
94f3bdaa69 | ||
|
|
7220e9d0b0 | ||
|
|
020102d87f | ||
|
|
f101700969 | ||
|
|
c4b148b84b | ||
|
|
b6a9274870 | ||
|
|
830d9699e3 | ||
|
|
fc68b81b21 | ||
|
|
2904772b16 | ||
|
|
e120b57032 | ||
|
|
b86b56260d | ||
|
|
b222b23557 | ||
|
|
8ec4253068 | ||
|
|
74ca88b7be | ||
|
|
a1119cd77a | ||
|
|
545723dc8c | ||
|
|
b64f4780c5 | ||
|
|
1c98b29087 | ||
|
|
33880331d2 | ||
|
|
74a43877e4 | ||
|
|
7542fbea48 | ||
|
|
2d9a1bc6f9 | ||
|
|
a57a240c13 | ||
|
|
b7fb6484d7 | ||
|
|
1a539ffebc | ||
|
|
27220a5ad8 | ||
|
|
e975fc8252 | ||
|
|
d85ea5a3b0 | ||
|
|
ccb7345084 | ||
|
|
93c00b67d9 | ||
|
|
cd393bbfdc | ||
|
|
fd045d4628 | ||
|
|
47e076071b | ||
|
|
a73e003dd3 | ||
|
|
949984268d | ||
|
|
114acfca3b | ||
|
|
56e538e8a9 | ||
|
|
fa46f3dd49 | ||
|
|
6ec5a8f2c1 | ||
|
|
49b596dda3 | ||
|
|
0c5ced1bbf | ||
|
|
4a09a2420d | ||
|
|
0ff8a596fc | ||
|
|
bca20ace4e | ||
|
|
f3b53001c9 | ||
|
|
15bac0eeaa | ||
|
|
1782973a29 | ||
|
|
9a4a501a95 | ||
|
|
9bbfbd5479 | ||
|
|
8c68731838 | ||
|
|
0d7f507f4e | ||
|
|
f78b943bdf | ||
|
|
160f3f88f9 | ||
|
|
45e38859ed | ||
|
|
38a5400a8c | ||
|
|
acd18455c2 | ||
|
|
5a7facc20f |
No files matched your search
@@ -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
|
||||
@@ -0,0 +1,42 @@
|
||||
name: 'Spam label'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
discussion:
|
||||
types: [labeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
if: ${{ (github.event.label.name == 'spam') && (github.event.action == 'labeled') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: peter-evans/close-issue@v2
|
||||
name: Close Issue
|
||||
with:
|
||||
close-reason: 'not_planned'
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
- run: gh pr close ${{ github.event.pull_request.html_url }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Close PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- uses: maxkomarychev/[email protected]
|
||||
name: Lock PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
issue_number: ${{ github.event.number }}
|
||||
- uses: OSDKDev/[email protected]
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
name: Lock issue
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Support Requests
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
support:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v2
|
||||
with:
|
||||
support-label: Forum
|
||||
issue-comment: >
|
||||
:wave: 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](https://forums.minecraftforge.net/) or
|
||||
[Forge Discord](https://discord.gg/UvedJ9m). Please create a new topic on the support forum
|
||||
with this issue or ask in the `#tech-support` channel in the Discord server, and the
|
||||
conversation can continue there.
|
||||
lock-issue: true
|
||||
+10
-10
@@ -9,29 +9,36 @@
|
||||
|
||||
#idea
|
||||
/.idea
|
||||
/*/out
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/.gradle
|
||||
/mdk/.gradle
|
||||
/mdk/build
|
||||
/*/build
|
||||
/*/.gradle
|
||||
|
||||
#occupational hazards
|
||||
# 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
|
||||
/repo/
|
||||
src/generated/resources/.cache/
|
||||
src/generated_test/resources/.cache/
|
||||
|
||||
#Patch rejects
|
||||
/patches-/
|
||||
*.patch.rej
|
||||
/projects/*/rejects/
|
||||
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
@@ -39,10 +46,3 @@ changelog.txt
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
gradle.properties
|
||||
!/mdk/gradle.properties
|
||||
/projects/forge/rejects/
|
||||
|
||||
# Srg2Source causes import bugs on these patches. If you intentionnally change these, remove the ignore.
|
||||
/patches/minecraft/net/minecraft/client/renderer/ViewFrustum.java.patch
|
||||
/patches/minecraft/net/minecraft/data/BlockModelDefinition.java.patch
|
||||
Vendored
+104
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<name>MinecraftForge_Forge Config DSL Script</name>
|
||||
<groupId>MinecraftForge_Forge</groupId>
|
||||
<artifactId>MinecraftForge_Forge_dsl</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jetbrains-all</id>
|
||||
<url>https://download.jetbrains.com/teamcity-repository</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>teamcity-server</id>
|
||||
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>JetBrains</id>
|
||||
<url>https://download.jetbrains.com/teamcity-repository</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${basedir}</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<version>${kotlin.version}</version>
|
||||
|
||||
<configuration/>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>process-test-sources</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>teamcity-configs-maven-plugin</artifactId>
|
||||
<version>${teamcity.dsl.version}</version>
|
||||
<configuration>
|
||||
<format>kotlin</format>
|
||||
<dstDir>target/generated-configs</dstDir>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin</artifactId>
|
||||
<version>${teamcity.dsl.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin-plugins</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-script-runtime</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
|
||||
import jetbrains.buildServer.configs.kotlin.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
||||
|
||||
/*
|
||||
The settings script is an entry point for defining a TeamCity
|
||||
project hierarchy. The script should contain a single call to the
|
||||
project() function with a Project instance or an init function as
|
||||
an argument.
|
||||
|
||||
VcsRoots, BuildTypes, Templates, and subprojects can be
|
||||
registered inside the project using the vcsRoot(), buildType(),
|
||||
template(), and subProject() methods respectively.
|
||||
|
||||
To debug settings scripts in command-line, run the
|
||||
|
||||
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
|
||||
|
||||
command and attach your debugger to the port 8000.
|
||||
|
||||
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
|
||||
-> Tool Windows -> Maven Projects), find the generate task node
|
||||
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
|
||||
'Debug' option is available in the context menu for the task.
|
||||
*/
|
||||
|
||||
version = "2021.2"
|
||||
|
||||
project {
|
||||
|
||||
buildType(Build)
|
||||
buildType(BuildSecondaryBranches)
|
||||
buildType(PullRequests)
|
||||
buildType(PullRequestChecks)
|
||||
buildType(PullRequestCompatibility)
|
||||
|
||||
params {
|
||||
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("docker_gradle_version", "7.4", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("git_main_branch", "1.19.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("git_branch_spec", """
|
||||
+:refs/heads/(main*)
|
||||
+:refs/heads/(master*)
|
||||
+:refs/heads/(develop|release|staging|main|master)
|
||||
+:refs/heads/(1.*)
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_FML_ARTIFACT_ID", "fmlonly", label = "Published fmlonly artifact id", description = "The maven coordinate artifact id for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION", "0.0.0-SNAPSHOT", label = "Published fmlonly artifact version", description = "The version for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
//These are references and not actually keys
|
||||
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
|
||||
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
|
||||
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
}
|
||||
|
||||
features {
|
||||
githubIssues {
|
||||
id = "MinecraftForge_MinecraftForge__IssueTracker"
|
||||
displayName = "MinecraftForge/MinecraftForge"
|
||||
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Build : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
|
||||
id("MinecraftForge_MinecraftForge__Build")
|
||||
name = "Build"
|
||||
description = "Builds and Publishes the main branches of the project."
|
||||
|
||||
features {
|
||||
feature {
|
||||
id = "trigger_fml_only_files_generator"
|
||||
type = "triggerBuildFeature"
|
||||
param("triggers", "MinecraftForge_FilesGenerator_GeneratePages")
|
||||
param("parameters", """
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_ID~env.PUBLISHED_JAVA_ARTIFACT_ID
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION~env.PUBLISHED_JAVA_ARTIFACT_VERSION
|
||||
env.PUBLISHED_JAVA_GROUP
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
object BuildSecondaryBranches : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
|
||||
name = "Build - Secondary Branches"
|
||||
description = "Builds and Publishes the secondary branches of the project."
|
||||
params {
|
||||
text("git_branch_spec", """
|
||||
+:refs/heads/(*)
|
||||
-:refs/heads/noci*
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
}
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequests : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequests")
|
||||
name = "Pull Requests"
|
||||
description = "Builds pull requests for the project"
|
||||
|
||||
params {
|
||||
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
text(
|
||||
"gradle_build_task",
|
||||
"assemble",
|
||||
label = "Gradle build task to execute during build",
|
||||
description = "Determines the build task that is executed to build the project.",
|
||||
display = ParameterDisplay.HIDDEN,
|
||||
allowEmpty = false
|
||||
)
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestChecks : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestChecks")
|
||||
name = "Pull Requests (Checks)"
|
||||
description = "Checks pull requests for the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Check"
|
||||
id = "RUNNER_10_Check"
|
||||
|
||||
tasks = "checkAll"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestCompatibility : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
|
||||
name = "Pull Requests (Compatibility)"
|
||||
description = "Validates binary compatibility for pull requests made to the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Validate"
|
||||
id = "RUNNER_10_Compatibility"
|
||||
|
||||
tasks = "checkJarCompatibility"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
Vendored
-103
@@ -1,103 +0,0 @@
|
||||
@Library('forge-shared-library')_
|
||||
|
||||
pipeline {
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'gradle:jdk8'
|
||||
args '-v forgegc:/home/gradle/.gradle/'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
|
||||
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
|
||||
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
|
||||
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('notify_start') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Started",
|
||||
successful: true,
|
||||
result: 'ABORTED', //White border
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
stage('setup') {
|
||||
steps {
|
||||
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
|
||||
script {
|
||||
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "version:" | awk \'{print $2}\'').trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('changelog') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
writeChangelog(currentBuild, 'build/changelog.txt')
|
||||
}
|
||||
}
|
||||
stage('publish') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
environment {
|
||||
FORGE_MAVEN = credentials('forge-maven-forge-user')
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
KEYSTORE = credentials('forge-jenkins-keystore-old')
|
||||
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
}
|
||||
steps {
|
||||
sh './gradlew ${GRADLE_ARGS} :forge:publish -PforgeMavenUser=${FORGE_MAVEN_USR} -PforgeMavenPassword=${FORGE_MAVEN_PSW} -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
|
||||
sh 'curl --user ${FORGE_MAVEN} http://files.minecraftforge.net/maven/manage/promote/latest/net.minecraftforge.forge/${MYVERSION}'
|
||||
}
|
||||
}
|
||||
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
|
||||
when {
|
||||
changeRequest()
|
||||
}
|
||||
environment {
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
}
|
||||
steps {
|
||||
sh './gradlew ${GRADLE_ARGS} :forge:publish -PcrowdinKey=${CROWDIN}'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
|
||||
|
||||
if (env.CHANGE_ID == null) { // This is unset for non-PRs
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
|
||||
description: '```\n' + getChanges(currentBuild) + '\n```',
|
||||
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
|
||||
result: currentBuild.currentResult,
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-16
@@ -1,16 +1,2 @@
|
||||
Minecraft Forge
|
||||
Copyright (c) 2016-2020.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation version 2.1
|
||||
of the License.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Copyright (c) Forge Development LLC and contributors
|
||||
SPDX-License-Identifier: LGPL-2.1-only
|
||||
+1369
-667
File diff suppressed because it is too large.
Load diff
@@ -1,2 +1,3 @@
|
||||
/.gradle/
|
||||
/build/
|
||||
/out/
|
||||
@@ -1,10 +1,11 @@
|
||||
repositories {
|
||||
//mavenCentral() //TODO: Update Gradle to use HTTPS by default
|
||||
maven {
|
||||
name 'maven_central'
|
||||
url 'https://repo.maven.apache.org/maven2/'
|
||||
}
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:7.2'
|
||||
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.12.0'
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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.Internal
|
||||
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()
|
||||
// It should be fine to mark the output as internal as we want to control when we run it anyways.
|
||||
// This also shuts Gradle 8 up about implicit task dependencies.
|
||||
@Internal 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,20 +1,24 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.TreeMap
|
||||
import net.minecraftforge.srgutils.IMappingFile
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
public class CheckATs extends DefaultTask {
|
||||
@InputFile File inheritance
|
||||
@InputFiles File[] ats
|
||||
abstract class CheckATs extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def mappings = mappings.map { IMappingFile.load(it.asFile) }.getOrNull()
|
||||
Util.init()
|
||||
def parse = { line ->
|
||||
def idx = line.indexOf('#')
|
||||
@@ -41,7 +45,7 @@ public class CheckATs extends DefaultTask {
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
def json = inheritance.json()
|
||||
def json = inheritance.get().asFile.json()
|
||||
|
||||
ats.each { f ->
|
||||
TreeMap lines = [:]
|
||||
@@ -51,12 +55,12 @@ public class CheckATs extends DefaultTask {
|
||||
if (line.startsWith('#group ')) {
|
||||
def (modifier, cls, desc, comment, key) = parse.call(line.substring(7))
|
||||
|
||||
if (!desc.equals('*') && !desc.equals('*()') && !desc.equals('<init>'))
|
||||
if (desc != '*' && desc != '*()' && desc != '<init>')
|
||||
throw new IllegalStateException('Invalid group: ' + line)
|
||||
|
||||
group = [modifier: modifier, cls: cls, desc: desc, comment: comment,
|
||||
'existing': [] as Set,
|
||||
'children': [] as TreeSet,
|
||||
existing: [] as Set,
|
||||
children: [] as TreeSet,
|
||||
group: true
|
||||
]
|
||||
if (lines.containsKey(key))
|
||||
@@ -93,7 +97,7 @@ public class CheckATs extends DefaultTask {
|
||||
if (jcls == null) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group: ' + key)
|
||||
} else if ('*'.equals(entry['desc'])) {
|
||||
} else if ('*' == entry['desc']) {
|
||||
if (!jcls.containsKey('fields')) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group, Class has no fields: ' + key)
|
||||
@@ -114,13 +118,13 @@ public class CheckATs extends DefaultTask {
|
||||
}
|
||||
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('*()'.equals(entry['desc'])) {
|
||||
} else if ('*()' == entry['desc']) {
|
||||
if (!jcls.containsKey('methods')) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group, Class has no methods: ' + key)
|
||||
} else {
|
||||
jcls['methods'].each{ mtd, value ->
|
||||
if (mtd.startsWith('<clinit>'))
|
||||
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$'))
|
||||
return
|
||||
key = cls + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
|
||||
@@ -137,7 +141,7 @@ public class CheckATs extends DefaultTask {
|
||||
}
|
||||
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('<init>'.equals(entry['desc'])) { //Make all public non-abstract subclasses
|
||||
} else if ('<init>' == entry['desc']) { //Make all public non-abstract subclasses
|
||||
json.each{ tcls,value ->
|
||||
if (!value.containsKey('methods') || ((value['access'] & Opcodes.ACC_ABSTRACT) != 0))
|
||||
return
|
||||
@@ -182,7 +186,7 @@ public class CheckATs extends DefaultTask {
|
||||
lines.remove(key)
|
||||
println('Invalid: ' + key)
|
||||
} else if (entry['desc'] == '') {
|
||||
if (accessLevel.call(jcls['access']) >= accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
if (accessLevel.call(jcls['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
}
|
||||
@@ -192,7 +196,7 @@ public class CheckATs extends DefaultTask {
|
||||
println('Invalid: ' + key)
|
||||
} else {
|
||||
def value = jcls['fields'][entry['desc']]
|
||||
if (accessLevel.call(value['access']) >= accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
println(entry.comment)
|
||||
@@ -205,7 +209,7 @@ public class CheckATs extends DefaultTask {
|
||||
println('Invalid: ' + key)
|
||||
} else {
|
||||
def value = jcls['methods'][jdesc]
|
||||
if (accessLevel.call(value['access']) >= accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
}
|
||||
@@ -216,12 +220,44 @@ public class CheckATs extends DefaultTask {
|
||||
|
||||
|
||||
def data = []
|
||||
def remapComment = { entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
def comment = entry.comment?.substring(1)?.trim()
|
||||
def jsonCls = json.get(entry.cls.replaceAll('\\.', '/'))
|
||||
def mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (!mappingsClass) return entry.comment
|
||||
def idx = entry.desc.indexOf('(')
|
||||
def 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) {
|
||||
data.add(value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add('#group ' + value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
|
||||
value.children.each{ data.add(value.modifier + ' ' + it) }
|
||||
value.children.each {
|
||||
def line = value.modifier + ' ' + it
|
||||
def (modifier, cls, desc, comment) = parse.call(line)
|
||||
comment = remapComment.call([modifier: modifier, cls: cls, desc: desc, comment: comment])
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.TreeMap
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
@@ -17,17 +17,17 @@ import org.objectweb.asm.Type
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
public class CheckExcs extends DefaultTask {
|
||||
@InputFile File binary
|
||||
@InputFiles File[] excs
|
||||
abstract class CheckExcs extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getBinary()
|
||||
@InputFiles abstract ConfigurableFileCollection getExcs()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
def known = []
|
||||
binary.withInputStream { i ->
|
||||
binary.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
def visitor = new ClassVisitor(Opcodes.ASM7) {
|
||||
def visitor = new ClassVisitor(Opcodes.ASM9) {
|
||||
private String cls
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
@@ -68,8 +68,7 @@ public class CheckExcs extends DefaultTask {
|
||||
|
||||
def (key, value) = line.split('=', 2)
|
||||
if (!known.contains(key)) {
|
||||
println(key)
|
||||
println('Invalid: ' + line)
|
||||
println('Unknown: ' + line)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
@@ -9,19 +10,19 @@ import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public class CheckPatches extends DefaultTask {
|
||||
@InputDirectory File patchDir
|
||||
abstract class CheckPatches extends DefaultTask {
|
||||
@InputDirectory abstract DirectoryProperty getPatchDir()
|
||||
@Input boolean autoFix = false
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def hasS2SArtifact = [
|
||||
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewFrustum.java.patch"),
|
||||
Paths.get("patches/minecraft/net/minecraft/data/BlockModelDefinition.java.patch")
|
||||
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewArea.java.patch"),
|
||||
Paths.get("patches/minecraft/net/minecraft/data/models/blockstates/Variant.java.patch")
|
||||
]
|
||||
|
||||
def verified = true;
|
||||
project.fileTree(patchDir).each { patch ->
|
||||
patchDir.get().asFileTree.each { patch ->
|
||||
def patchPath = project.rootDir.toPath().relativize(patch.toPath())
|
||||
verified &= verifyPatch(patch, autoFix, patchPath.toString(), hasS2SArtifact.contains(patchPath))
|
||||
}
|
||||
@@ -31,7 +32,7 @@ public class CheckPatches extends DefaultTask {
|
||||
}
|
||||
|
||||
def verifyPatch(patch, fix, patchPath, hasS2SArtifact) {
|
||||
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,]* @@$')
|
||||
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
|
||||
def white_space_pattern = Pattern.compile('^[+\\-]\\s*$')
|
||||
def import_pattern = Pattern.compile('^[+\\-]\\s*import.*')
|
||||
def field_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.TreeMap
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
public class CheckSAS extends DefaultTask {
|
||||
@InputFile File inheritance
|
||||
@InputFiles File[] sass
|
||||
abstract class CheckSAS extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getSass()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
def json = inheritance.json()
|
||||
def json = inheritance.get().asFile.json()
|
||||
|
||||
sass.each { f ->
|
||||
def lines = []
|
||||
@@ -29,36 +28,80 @@ public class CheckSAS extends DefaultTask {
|
||||
}
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
def (cls, desc) = (line.trim() + ' ').split(' ', -1)
|
||||
def (cls, name, desc) = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
|
||||
cls = cls.replaceAll('\\.', '/')
|
||||
desc = desc.replace('(', ' (')
|
||||
|
||||
if (json[cls] == null || (!desc.isEmpty() && (json[cls]['methods'] == null || json[cls]['methods'][desc] == null))) {
|
||||
if (json[cls] == null) {
|
||||
println('Invalid: ' + line)
|
||||
return
|
||||
}
|
||||
|
||||
//Class SAS
|
||||
if (desc.isEmpty()) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
if (json[cls]['methods'] != null)
|
||||
(json[cls]['methods'] as TreeMap).each {
|
||||
findChildMethods(json, cls, it.key).each { lines.add('\t' + it) }
|
||||
} else if (name.isEmpty()) { //Class SAS
|
||||
def toAdd = []
|
||||
def clsSided = isSided(json[cls])
|
||||
def 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)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//Method SAS
|
||||
lines.add(cls + ' ' + desc.replace(' ', '') + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(json, cls, desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
*/
|
||||
if (json[cls]['methods'] != null) {
|
||||
for (entry in json[cls]['methods']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
|
||||
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
} else if (clsSided) {
|
||||
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sided) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
lines.addAll(toAdd.sort())
|
||||
} else
|
||||
println('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
|
||||
*/
|
||||
println('Invalid: ' + line)
|
||||
} else { // Methods
|
||||
if (json[cls]['methods'] == null || !isSided(json[cls]['methods'][name + ' ' + desc]))
|
||||
println('Invalid: ' + line)
|
||||
else {
|
||||
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(json, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
|
||||
findChildMethods(json, cls, name + ' ' + desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
f.text = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
protected static isSided(json) {
|
||||
if (json == null || json['annotations'] == null)
|
||||
return false
|
||||
for (ann in json['annotations']) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;'.equals(ann.desc))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected static findChildMethods(json, cls, desc)
|
||||
{
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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,65 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
import java.net.URL
|
||||
|
||||
public class CrowdinTask extends DefaultTask {
|
||||
@Input String id = 'minecraft-forge'
|
||||
@Input @Optional String key
|
||||
@Input boolean json = true
|
||||
@OutputFile output = project.file("build/${name}/output.zip")
|
||||
@OutputFile export = project.file("build/${name}/export.json")
|
||||
|
||||
CrowdinTask() {
|
||||
outputs.upToDateWhen{ false }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
if (output.exists())
|
||||
output.delete()
|
||||
|
||||
if (key == null)
|
||||
return
|
||||
|
||||
// Force an export
|
||||
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
|
||||
export.withOutputStream { it << i }
|
||||
}
|
||||
|
||||
if (!export.text.contains('success')) {
|
||||
throw new RuntimeException("Crowdin export failed, see ${export} for more info")
|
||||
}
|
||||
|
||||
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
output.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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,43 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
|
||||
import java.io.File
|
||||
import java.util.HashSet
|
||||
import java.util.Set
|
||||
import java.net.URL
|
||||
|
||||
public class DownloadLibrariesTask extends DefaultTask {
|
||||
@InputFile File input
|
||||
@OutputDirectory File output = project.file("build/${name}/")
|
||||
Set<File> libraries = new HashSet<>()
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
Util.init()
|
||||
|
||||
def json = input.json().libraries.each { lib ->
|
||||
//TODO: Thread?
|
||||
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
|
||||
artifacts.each{ art ->
|
||||
def target = new File(output, art.path)
|
||||
libraries.add(target)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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 + '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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
@@ -0,0 +1,57 @@
|
||||
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,5 +1,6 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
import java.io.File
|
||||
@@ -16,16 +17,31 @@ public class Util {
|
||||
}
|
||||
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 ->
|
||||
@@ -35,7 +51,7 @@ public class Util {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
public static def getArtifacts(project, config, classifiers) {
|
||||
def ret = [:]
|
||||
config.resolvedConfiguration.resolvedArtifacts.each {
|
||||
@@ -56,14 +72,7 @@ public class Util {
|
||||
def path = "${folder}${filename}"
|
||||
def url = "https://libraries.minecraft.net/${path}"
|
||||
if (!checkExists(url)) {
|
||||
url = "https://files.minecraftforge.net/maven/${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'
|
||||
art.version = "${art.version}-${art.classifier}"
|
||||
art.classifier = null
|
||||
art.extension = 'jar'
|
||||
path = "${art.group.replace('.', '/')}/${art.name}/${art.version}/${art.name}-${art.version}.jar"
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
ret[key] = [
|
||||
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
|
||||
@@ -80,6 +89,17 @@ public class Util {
|
||||
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) {
|
||||
@@ -99,7 +119,7 @@ public class Util {
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
|
||||
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
@@ -114,4 +134,9 @@ public class Util {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLatestForgeVersion(mcVersion) {
|
||||
def json = new JsonSlurper().parseText(new URL("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json").getText("UTF-8"))
|
||||
return json.promos["$mcVersion-latest"]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
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.
|
||||
1) Keep patches to Minecraft classes together. If you need a lot of things done, you may either add to relevant forge classes or make a new class. Try not to spread out your patches across multiple disjoint lines, as this makes maintenance of your patches difficult.
|
||||
|
||||
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.
|
||||
2) TODO: Test Mods
|
||||
|
||||
3) Follow Forge coding guidelines (braces on newlines, spaces instead of tabs, etc)
|
||||
3) Follow the code style of the class you're working in (braces on newlines & spaces instead of tabs in Forge classes, inline brackets in patches, etc).
|
||||
|
||||
For more information, refer to [the wiki](https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge)
|
||||
|
||||
@@ -15,8 +15,4 @@ 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.
|
||||
|
||||
Additionally, while 1.9.4 is being maintained, you will be asked to acknowledge these two additional clauses.
|
||||
- You grant Forge a license to use your code contributed to the primary codebase (everything **not** under patches) in Forge, under the MinecraftForge license.
|
||||
- You grant assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the MinecraftForge license.
|
||||
|
||||
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
|
||||
@@ -1,9 +0,0 @@
|
||||
# On the Contribution of an RF-lite Energy System: "Energy"
|
||||
Forge has acquired an Energy system, after many years of debate on the topic. The prevalence of the RF API has warranted the inclusion of a similar (but simpler) implementation to forge for 1.10.2 and onwards.
|
||||
Those who wish to use an RF like API, feel free to use it. If you don't want to think about Energy, but know you need it, you can use it *if you wish*. If you want to interoperate with other energy systems, feel free to use this to broker with them.
|
||||
|
||||
# On Diversity in Energy Systems
|
||||
Forge has always supported diversity in energy-like systems. If you have a burning desire to simulate electricity flow in all it's glory, we fully support you and look forward to playing your mod. If you wish to have pneumatic power, we have no objections, and would love to see your wonderful creations. If you have a power system of your own devising that is simply beyond our imagination, please, blow us away.
|
||||
|
||||
# On Modder Obligations
|
||||
As a modder, you are under *no* obligation to use or support the forge Energy system. If anyone, user, mod-pack maker or even us, tells you otherwise, refer them to this statement. The Forge energy system is not intended to supplant diversity in energy systems. It is not intended to be one-size-fits-all. The choice to support it, is the choice of the modder.
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
# FML
|
||||
|
||||
Entrypoint : ```FMLServiceProvider.onLoad```
|
||||
|
||||
- Verify environment
|
||||
-- Check all libraries are present: ```accesstransformer```, ```coremod```, ```deobfuscator```
|
||||
-- Check versions are suitable: ```modlauncher```, ```accesstransformer```, ```coremod```, ```deobfuscator```
|
||||
- Find deobfuscated and patched MC
|
||||
-- If not found, trigger job to generate that (loaded later at FMLLaunchProvider.launch)
|
||||
|
||||
Injected arguments:
|
||||
|
||||
- ```mods``` : CSV list of mod files to load
|
||||
- ```modlist``` : JSON formatted list of mod files in a maven-like repository format
|
||||
|
||||
Entrypoint : ```FMLServiceProvider.initializer```
|
||||
- Setup paths
|
||||
-- ```mods``` folder
|
||||
-- ```config``` folder
|
||||
- FML config file read (JSON?)
|
||||
- Trigger initial loading
|
||||
|
||||
Entrypoint : ```FMLLaunchProvider.launch```
|
||||
- Launches the patched and deobfuscated game
|
||||
- - If that wasn't found it'll be generated from the vanilla jar and patch/deobf files (see above)
|
||||
- Possible we'll use an alternative launch for the development environment
|
||||
|
||||
(A goal is that we pre-generate this JAR during forge installation or modpack installation)
|
||||
|
||||
## Loading
|
||||
|
||||
- First we discover all mods
|
||||
- - IModLocator instances present in system are found and queried to compile a master list of all mod artifacts of the three types
|
||||
- LANGPROVIDERs are added to the supported language system : IModLanguageProvider
|
||||
- The MODS from the list have their META-INF/mods.json file queried for mod instances to load
|
||||
- - MODS are cross-referenced for their language being available
|
||||
- - MODS are cross-referenced for their libraries being available
|
||||
- - MODS are cross-referenced for their MOD dependencies being available
|
||||
- - MODS with META-INF/coremods.json specification are loaded into the COREMOD system
|
||||
- - MODS with META-INF/accesstransformer.json are loaded into the ACCESSTRANSFORMER system
|
||||
- - MODS are enqueued to the background loading system
|
||||
- FMLLaunchProvider will be triggered from the ModLauncher to start the game
|
||||
|
||||
## Game loading
|
||||
|
||||
- Events triggered from loadGame will launch various phases of modloading as before
|
||||
-- Deprecation of old "FML" events in favour of Forge events
|
||||
-- If a mod hasn't completed scanning there might be a pause waiting for it to complete before loading (testing indicates this should be a very rare occurrence and isn't worse than existing status quo where all scanning and loading is on-thread)
|
||||
|
||||
### ```IModLocator```
|
||||
|
||||
Finds mods from various sources of various types
|
||||
manifest is queried for type: FMLModType
|
||||
|
||||
- MOD : this file will be scanned as a mod. This is default if nothing is present
|
||||
- LIBRARY : this file will be added to the classpath and no further processing is done
|
||||
- LANGPROVIDER : this file will be loaded as a language provider
|
||||
|
||||
#### ```ModsFolderLocator```
|
||||
|
||||
- scans for jar files from mods folder
|
||||
-- able to load all types
|
||||
-- loads in all environments
|
||||
-- maybe able to do runtime name transformation for dev time?
|
||||
|
||||
Standard mechanism to find and load mods
|
||||
|
||||
#### ```ExplodedDirectoryLocator```
|
||||
|
||||
- loads a directory as a mod
|
||||
-- only able to load mods
|
||||
-- should only load in dev env
|
||||
|
||||
Intended as a mechanism to allow development of mods easily by pointing at a compiled output directory
|
||||
|
||||
#### ```ArgumentModsLocator``` : NYI
|
||||
|
||||
- loads mods from the arguments on the command line
|
||||
-- able to load all types
|
||||
-- loads in all environments
|
||||
|
||||
### ```IModLanguageProvider```
|
||||
|
||||
Provides mod and language provider services
|
||||
|
||||
- Provides a means to transform a Mod File (from the IModLocator) into a list of modcontainer objects based on mods.json
|
||||
- Scanning work will be done in background threads
|
||||
- Language providers can be provided as JARs via the modlocator for ease of distribution
|
||||
|
||||
#### ```FMLModLanguageProvider```
|
||||
|
||||
This is the standard Java @Mod implementation provider
|
||||
|
||||
- provided by default (always supported)
|
||||
- All scanning for @Mod will be done in the background thread
|
||||
|
||||
#### ```ScalaModLanguageProvider```
|
||||
|
||||
This is the scala language provider. It'll be provided separately as a JAR mod download.
|
||||
|
||||
- API contract will need definition
|
||||
|
||||
Other languages? Kotlin? Alternative Scala versions? Javascript?
|
||||
|
||||
## Coremods
|
||||
|
||||
Coremods are now forcefully separated into _injection_ and _runtime_ phases.
|
||||
|
||||
### Injection
|
||||
|
||||
Injection is the act of intercepting the loading of a class and modifying the inbound class with alterations such as additions, replacements and deletions
|
||||
|
||||
Injections will be written using Javascript. Two functions are required: a function providing the list of injection sites, and the function to transform the injection site.
|
||||
|
||||
All injection sites will need to be pre-specified by the coremod in their metadata. Lookup services should be provided in the Javascript API.
|
||||
|
||||
Javascript API for common coremod injection tasks needs to be developed, to allow common best-practice tooling for these complex tasks.
|
||||
|
||||
Runtime coremod code (code that has been injected) is Java as usual.
|
||||
|
||||
## Background loading thread
|
||||
|
||||
Early in loading, a background thread is triggered which will perform scanning tasks for common resources, such as @Mod instances and other annotations. This thread will also be responsible for loading any cached resources, either those defined at build time or pre-computed from previous runs. It may also be tasked with writing those precomputed resources.
|
||||
|
||||
There will be a IPreLoaderJobProvider that will allow extension of the scope of these tasks
|
||||
|
||||
|
||||
+47
-23
@@ -2,25 +2,21 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[](https://files.minecraftforge.net)
|
||||
[](https://files.minecraftforge.net) [](https://discord.gg/UvedJ9m) [](https://www.patreon.com/LexManos)
|
||||
[](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.16.x | Active |
|
||||
| 1.15.x | LTS |
|
||||
| Version | Support |
|
||||
|--------------|-----------------------------|
|
||||
| All versions | [Supported][Support-Policy] |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
* [Discord]
|
||||
* [Documentation]
|
||||
|
||||
#### Notes:
|
||||
- Introduced in 1.13 was a new FML, information found [here](NewFML.md).
|
||||
|
||||
# Installing Forge
|
||||
|
||||
Go to [the Forge website](https://files.minecraftforge.net)
|
||||
@@ -32,37 +28,65 @@ You can download the installer for the *Recommended Build* or the
|
||||
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](https://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
|
||||
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)
|
||||
## Required Java Versions
|
||||
|
||||
We require the version of Java that Minecraft requires, which is currently:
|
||||
|
||||
| Minecraft | Java |
|
||||
|---------------|-------------------|
|
||||
| 1.20.6+ | [21][Adoptium-21] |
|
||||
| 1.18 - 1.20.4 | [17][Adoptium-17] |
|
||||
| 1.17.1 | [16][Adoptium-16] |
|
||||
| 1.16.5- | [8][Adoptium-8] |
|
||||
|
||||
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
|
||||
|
||||
# Creating Mods
|
||||
|
||||
[See the "Getting Started" section in the Forge Documentation](https://mcforge.readthedocs.io/en/latest/gettingstarted/).
|
||||
[See the "Getting Started" section in the Forge Documentation](https://docs.minecraftforge.net/en/1.19.2/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](http://mcforge.readthedocs.io/en/latest/forgedev/).
|
||||
|
||||
[See the guide to setting up a Forge workspace](https://docs.minecraftforge.net/en/1.19.2/forgedev/).
|
||||
|
||||
### Pull requests
|
||||
|
||||
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://mcforge.readthedocs.io/en/latest/forgedev/#making-changes-and-pull-requests).
|
||||
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://docs.minecraftforge.net/en/1.19.2/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
|
||||
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).*
|
||||
*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://www.minecraftforge.net/forum/
|
||||
[Discord]: https://discord.gg/UvedJ9m
|
||||
[Documentation]: https://mcforge.readthedocs.io
|
||||
|
||||
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
|
||||
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
|
||||
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
|
||||
[Adoptium-8]: https://adoptium.net/temurin/releases/?version=8
|
||||
[Jarfix]: https://johann.loefflmann.net/en/software/jarfix/index.html
|
||||
|
||||
[Forum]: https://forums.minecraftforge.net/
|
||||
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
|
||||
[Support-Policy]: https://forums.minecraftforge.net/topic/144690-new-tiered-support-policy
|
||||
|
||||
[Discord]: https://discord.minecraftforge.net/
|
||||
|
||||
[Documentation]: https://docs.minecraftforge.net/en/1.19.2/
|
||||
[Getting-Started]: https://docs.minecraftforge.net/en/1.19.2/gettingstarted/
|
||||
[ForgeDev]: https://docs.minecraftforge.net/en/1.19.2/forgedev/
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/1.19.2/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
+3
-2
@@ -76,7 +76,7 @@ If you're certain you've found an issue that is directly caused by Forge, you ha
|
||||
|
||||
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.*
|
||||
2. **Forge Discord Server:** You can find a lot of help on the official [Discord Server](https://discord.gg/forge), especially smaller issues that you quickly need help with. *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
|
||||
@@ -84,7 +84,8 @@ If you're certain you've found an issue that is directly caused by Forge, you ha
|
||||
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.
|
||||
- **Modrinth:** Some modders use [Modrinth](https://modrinth.com/mods) to host their mods, but be mindful that not all mods on this platform are for the Forge modloader.
|
||||
- **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), [GitLab](https://gitlab.com), and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
|
||||
|
||||
### Vanilla Support
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg enable-background="new 164.072 372.466 294.41 46.56" version="1.1" viewBox="164.072 372.466 294.41 46.56" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#DFA86A">
|
||||
<polygon points="266.16 417.99 277.12 417.99 277.12 401.05 295.09 401.05 295.09 390.78 277.12 390.78 277.12 383.77 296.18 383.77 296.18 373.5 266.16 373.5"/>
|
||||
<path d="m337.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-0.356 2e-3c-3.362 0-6.503 0.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 0.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.651h0.023c3.402-0.043 6.57-0.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-0.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-0.302 3.782-0.896 5.413-0.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 0.656-3.164 0.99-5.008 0.99-1.84 0-3.514-0.333-4.973-0.988-1.47-0.662-2.747-1.578-3.799-2.729-1.062-1.156-1.895-2.559-2.479-4.167-0.595-1.63-0.896-3.451-0.896-5.413 0-1.801 0.298-3.497 0.887-5.044 0.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-0.642 3.145-0.968 4.988-0.968 1.848 0 3.539 0.326 5.025 0.971 1.481 0.643 2.766 1.541 3.812 2.671 1.062 1.143 1.896 2.501 2.479 4.036 0.589 1.548 0.889 3.245 0.889 5.044z"/>
|
||||
<path d="m376.27 395.65c1.902-2.327 2.867-5.221 2.867-8.603 0-2.614-0.508-4.84-1.506-6.613-0.997-1.762-2.354-3.188-4.035-4.234-1.603-0.995-3.445-1.705-5.482-2.114-1.938-0.388-3.969-0.583-6.037-0.583h-16.021v44.487h10.961v-17.282h2.35l9.447 17.282h13.127l-11.199-18.646c2.262-0.728 4.116-1.964 5.528-3.694zm-19.254-12.223h4.426c0.844 0 1.73 0.054 2.636 0.16 0.784 0.094 1.503 0.283 2.132 0.568 0.496 0.225 0.888 0.553 1.193 0.998 0.283 0.415 0.428 1.031 0.428 1.836 0 0.931-0.16 1.643-0.459 2.06-0.34 0.472-0.771 0.813-1.32 1.046-0.686 0.289-1.459 0.477-2.301 0.554-0.975 0.089-1.963 0.136-2.938 0.136h-3.795l-2e-3 -7.358z"/>
|
||||
<path d="m394.82 386.38c1.05-1.132 2.325-2.03 3.793-2.674 1.464-0.642 3.144-0.968 4.988-0.968 1.843 0 3.611 0.311 5.26 0.921 1.609 0.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-0.789-5.987-1.189-9.247-1.189-3.361 0-6.503 0.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 0.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.25l0.979-0.522v-23.814h-19.313v10.27h8.354v6.639c-0.871 0.397-1.834 0.709-2.881 0.933-1.5 0.314-3.127 0.478-4.83 0.478-1.841 0-3.514-0.333-4.973-0.988-1.472-0.662-2.748-1.578-3.801-2.729-1.06-1.156-1.893-2.559-2.478-4.167-0.595-1.63-0.896-3.451-0.896-5.413 0-1.801 0.299-3.497 0.887-5.044 0.586-1.54 1.421-2.897 2.479-4.041z"/>
|
||||
<polygon points="437.69 407.72 437.69 400.36 456.41 400.36 456.41 390.09 437.69 390.09 437.69 383.77 457.44 383.77 457.44 373.5 426.72 373.5 426.72 417.99 458.48 417.99 458.48 407.72"/>
|
||||
</g>
|
||||
<path d="m243.63 379.09l-37.842-1.852 46.229-0.034v-3.727h-52.125l-1e-3 7.798v6.201c0 0.115-1.52-9.144-1.87-11.734h-4.098v13.029c0 0.123-1.753-10.888-1.939-12.264h-27.914c1.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 0.06 4.25 0.217 5.803 1.754 2.255 2.236 2.758 5.704 0.814 8.33-1.922 2.594-7.335 3.156-7.335 3.156l-4.509 5.531v6.424h10.251l0.319-6.348 8.863-6.285c-0.946 0.757-3.058 2.783-6.229 7.666-0.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-0.437-1.185-0.994-2.385-1.709-3.483-3.172-4.883-5.284-6.909-6.229-7.666l8.863 6.285 0.32 6.348h9.565v-6.424l-4.507-5.531s-6.675-0.425-8.423-3.156c-5.032-7.869 2.115-20.082 18.836-23.756z" fill="#1E2D41"/>
|
||||
<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: 4.0 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,73 @@
|
||||
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:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 net.minecraftforge.fml.loading.FMLLoader;
|
||||
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Bindings {
|
||||
private Bindings() {}
|
||||
|
||||
/**
|
||||
* The provider of the bindings
|
||||
* @implNote May also throw a {@link ServiceConfigurationError}
|
||||
*/
|
||||
private static final IBindingsProvider PROVIDER = ServiceLoader.load(FMLLoader.getGameLayer(), IBindingsProvider.class)
|
||||
.findFirst().orElseThrow(() -> new IllegalStateException("Could not find bindings provider"));
|
||||
|
||||
/**
|
||||
* @return A supplier of net.minecraftforge.common.MinecraftForge#EVENT_BUS
|
||||
*/
|
||||
public static Supplier<IEventBus> getForgeBus() {
|
||||
return PROVIDER.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
public static Supplier<I18NParser> getMessageParser() {
|
||||
return PROVIDER.getMessageParser();
|
||||
}
|
||||
|
||||
public static Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
|
||||
return PROVIDER.getConfigConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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) {}
|
||||
}
|
||||
+3
-17
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
@@ -46,7 +32,7 @@ public final class DistExecutor
|
||||
private DistExecutor() {}
|
||||
|
||||
/**
|
||||
* Run the callable in the supplier only on the specified {@link Side}.
|
||||
* 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.
|
||||
*
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An extension point for a mod container.
|
||||
*
|
||||
* <p>An extension point can be registered for a mod container using {@link ModContainer#registerExtensionPoint(Class, Supplier)}
|
||||
* and retrieved (if present) using {@link ModContainer#getCustomExtension(Class)}. An extension point allows a mod to
|
||||
* supply an arbitrary value as a record class to another mod or framework through their mod container class, avoiding
|
||||
* the use of {@link InterModComms} or other external frameworks to pass around these values.</p>
|
||||
*
|
||||
* <p>The usual way to declare an extension point is to implement this interface on a record class, with the type
|
||||
* parameter being a reference to the class itself. For example, {@code record MyExtension(...) extends
|
||||
* IExtensionPoint<MyExtension>} would declare an extension point which supplies a {@code MyExtension} object. However,
|
||||
* there is no hard requirement that an extension point's type parameter must be in reference to itself; the type
|
||||
* parameter may reference another record class instead.</p>
|
||||
*
|
||||
* @param <T> the type of the record which is held by the extension point
|
||||
*/
|
||||
@SuppressWarnings("unused") // Type parameter T
|
||||
public interface IExtensionPoint<T extends Record>
|
||||
{
|
||||
/**
|
||||
* Extension point for the compatibility display test used on the server selection screen.
|
||||
*
|
||||
* <em>Note: "server" and "client" refers to the {@linkplain net.minecraftforge.api.distmarker.Dist#DEDICATED_SERVER dedicated server}
|
||||
* and {@linkplain net.minecraftforge.api.distmarker.Dist#CLIENT game client} physical distributions, rather than the
|
||||
* {@linkplain LogicalSide logical server and client}.</em>
|
||||
*
|
||||
* <p>The {@link Supplier} provides the local compatibility version, which is sent from the server to the client
|
||||
* for multiplayer connections or stored to disk for the world save. The {@link BiPredicate} accepts the remote
|
||||
* compatibility version and a boolean indicating whether the remote version is from the server or a world save,
|
||||
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
|
||||
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
|
||||
*
|
||||
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
|
||||
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
|
||||
* However, the remote version test predicate must still accept this value as a remote version in order to display
|
||||
* as compatible if the mod is present on the client.</p>
|
||||
*
|
||||
* <p><strong>The compatibility display test does not necessarily indicate the success or failure of an actual
|
||||
* connection attempt.</strong> Factors such as display test extension misconfiguration, truncation of ping data,
|
||||
* difference of registry data or network channels between server and client, and others may cause the result of the
|
||||
* compatibility test to not reflect the actual likelihood of a connection successfully being established between
|
||||
* the server and the client.</p>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a regular mod (requires to be present on
|
||||
* server and client) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* String compatibilityVersion = "1"; // Could be linked with a network channel version or mod version
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* () -> compatibilityVersion,
|
||||
* (remoteVersion, isFromServer) -> remoteVersion.equals(compatibilityVersion)
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a <em>server-side-only</em> mod (does not
|
||||
* require to be present on the client) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* // Ignore this mod if not present on the client
|
||||
* () -> NetworkConstants.IGNORESERVERONLY,
|
||||
* // If present on the client, accept any version if from a server
|
||||
* (remoteVersion, isFromServer) -> isFromServer
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a <em>client-side-only</em> mod (does not
|
||||
* require to be present on the server) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* // Send any version from server to client, since we will be accepting any version as well
|
||||
* () -> "dQw4w9WgXcQ",
|
||||
* // Accept any version on the client, from server or from save
|
||||
* (remoteVersion, isFromServer) -> true
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* @see net.minecraftforge.network.ServerStatusPing
|
||||
* @see net.minecraftforge.client.ForgeHooksClient#processForgeListPingData(net.minecraft.network.protocol.status.ServerStatus, net.minecraft.client.multiplayer.ServerData)
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
|
||||
* grouped into various {@link ModLoadingPhase phases}.
|
||||
*
|
||||
* @see IModStateProvider
|
||||
*/
|
||||
public interface IModLoadingState {
|
||||
/**
|
||||
* {@return the name of this state}
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* {@return the name of the state immediately previous to this state} This may be a blank name, which indicates this
|
||||
* is either the first mod loading state or an exceptional mod loading state (such as a situation where errors
|
||||
* prevent the loading process from continuing normally).
|
||||
*/
|
||||
String previous();
|
||||
|
||||
/**
|
||||
* {@return the mod loading phase this state belongs to} For exceptional mod loading states, this should be
|
||||
* {@link ModLoadingPhase#ERROR}.
|
||||
*/
|
||||
ModLoadingPhase phase();
|
||||
|
||||
/**
|
||||
* {@return a function returning a human-friendly message for this state}
|
||||
*/
|
||||
Function<ModList, String> message();
|
||||
|
||||
/**
|
||||
* {@return an optional runnable, which runs before starting the transition from this state to the next}
|
||||
* @see #buildTransition(Executor, Executor, Function, Function)
|
||||
*/
|
||||
Optional<Consumer<ModList>> inlineRunnable();
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state with a blank pre-sync and post-sync task.
|
||||
*
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @return a transition task for this state
|
||||
* @see #buildTransition(Executor, Executor, Function, Function)
|
||||
*/
|
||||
default <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor) {
|
||||
return buildTransition(syncExecutor, parallelExecutor,
|
||||
e -> CompletableFuture.runAsync(() -> {}, e),
|
||||
e -> CompletableFuture.runAsync(() -> {}, e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
|
||||
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
|
||||
*
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @return a transition task for this state
|
||||
*/
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides a list of mod loading states which the mod loader may transition between.
|
||||
*
|
||||
* <p>There may be multiple mod state providers in a single application, where all states from each provider is
|
||||
* combined into a single list and ordered.</p>
|
||||
*/
|
||||
public interface IModStateProvider {
|
||||
/**
|
||||
* {@return the list of mod loading states known to this provider}
|
||||
*/
|
||||
List<IModLoadingState> getAllStates();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.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<Void> build(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
this.eventFunctionStream().get()
|
||||
.map(f -> (EventGenerator<T>) f)
|
||||
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, head, ModLoadingStage::currentState, tail))
|
||||
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, last, nextModLoadingStage(), null));
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return null;
|
||||
});
|
||||
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<Void>> completableFutures,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
|
||||
final EventGenerator<T> nextGenerator) {
|
||||
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
|
||||
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
completableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
|
||||
|
||||
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
|
||||
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
return nextGenerator;
|
||||
}
|
||||
|
||||
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
|
||||
ThreadSelector threadSelector();
|
||||
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> 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<Void>, CompletableFuture<Void>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
+12
-30
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
@@ -33,38 +19,32 @@ import java.util.stream.StreamSupport;
|
||||
|
||||
public class InterModComms
|
||||
{
|
||||
public static final class IMCMessage {
|
||||
private final String modId;
|
||||
private final String method;
|
||||
private final String senderModId;
|
||||
private final Supplier<?> thing;
|
||||
|
||||
IMCMessage(String senderModId, String modId, String method, Supplier<?> thing)
|
||||
{
|
||||
this.senderModId = senderModId;
|
||||
this.modId = modId;
|
||||
this.method = method;
|
||||
this.thing = thing;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -72,10 +52,12 @@ public class InterModComms
|
||||
/**
|
||||
* @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.thing;
|
||||
return (Supplier<T>) this.messageSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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]"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
/**
|
||||
* A logical side of the Minecraft game.
|
||||
* <p>
|
||||
* The {@linkplain Dist#CLIENT client distribution} has a copy of the logical client and the logical server, while the
|
||||
* {@linkplain Dist#DEDICATED_SERVER dedicated server distribution} only holds
|
||||
* the logical server.
|
||||
*
|
||||
* @see Dist
|
||||
*/
|
||||
public enum LogicalSide
|
||||
{
|
||||
/**
|
||||
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
|
||||
* viewpoint.
|
||||
* <p>
|
||||
* The logical client is only shipped with the client distribution of the game.
|
||||
*
|
||||
* @see Dist#CLIENT
|
||||
*/
|
||||
CLIENT,
|
||||
/**
|
||||
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
|
||||
* on the level.
|
||||
* <p>
|
||||
* The logical server is shipped with both client and dedicated server distributions. The client distribution runs
|
||||
* the logical server for singleplayer mode and LAN play.
|
||||
*
|
||||
* @see Dist#DEDICATED_SERVER
|
||||
*/
|
||||
SERVER;
|
||||
|
||||
/**
|
||||
* {@return if this logical side is the server}
|
||||
*/
|
||||
public boolean isServer()
|
||||
{
|
||||
return !isClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return if the logical side is the client}
|
||||
*/
|
||||
public boolean isClient()
|
||||
{
|
||||
return this == CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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) {}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.Comparator;
|
||||
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 List<ModContainer> sortedContainers;
|
||||
|
||||
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<Void>> 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<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
|
||||
if (t.stream().noneMatch(e->e.getValue()!=null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
|
||||
CompletableFuture<Void> 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.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
|
||||
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 void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* 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.FMLEnvironment;
|
||||
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.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
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.Collection;
|
||||
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)
|
||||
.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) {
|
||||
ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.SERVER, System.getProperty("java.version")));
|
||||
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("Validating features"));
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb.featureName(), fb.featureBound(), ForgeFeature.featureValue(fb)))
|
||||
.toList());
|
||||
}
|
||||
|
||||
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<Void> 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().forEachModInOrder(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().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
}
|
||||
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
pre.accept(mc, e);
|
||||
mc.acceptEvent(e);
|
||||
post.accept(mc, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private ModContainer activeContainer;
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
|
||||
/**
|
||||
* @deprecated Use the context provided by your language loader in your mod's constructor.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public void setActiveContainer(final ModContainer container) {
|
||||
this.activeContainer = container;
|
||||
this.languageExtension = container == null ? null : container.contextExtension.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Going to be moved to ForgeHooks for internal use.
|
||||
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public ModContainer getActiveContainer() {
|
||||
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
public ModContainer getContainer() {
|
||||
return getActiveContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
getContainer().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) {
|
||||
getContainer().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) {
|
||||
getContainer().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) {
|
||||
getContainer().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) {
|
||||
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
|
||||
return;
|
||||
}
|
||||
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer()));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T extension() {
|
||||
return (T)languageExtension;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
/**
|
||||
* Phases of mod loading, for grouping mod loading states.
|
||||
*/
|
||||
public enum ModLoadingPhase {
|
||||
/**
|
||||
* Special phase for exceptional situations and error handling, where mod loading cannot continue normally.
|
||||
*
|
||||
* <p>There is conventionally only one state with this phase.</p>
|
||||
*/
|
||||
ERROR, // Special state for error handling
|
||||
/**
|
||||
* Phase for discovering and gathering mods for loading.
|
||||
*/
|
||||
GATHER,
|
||||
/**
|
||||
* Phase for the loading of mods found from mod discovery.
|
||||
*
|
||||
* @see #GATHER
|
||||
*/
|
||||
LOAD,
|
||||
/**
|
||||
* Phase after mod loading has completed, for post-loading tasks.
|
||||
*
|
||||
* @see #LOAD
|
||||
*/
|
||||
COMPLETE,
|
||||
/**
|
||||
* Marker phase for the last state in the full mod loading process.
|
||||
*/
|
||||
DONE
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
/**
|
||||
* Mod loading stage of mod containers during the mod loading process. These will have a corresponding {@link ModLoadingState}
|
||||
* in the basic mod loading process provided by FML.
|
||||
*
|
||||
* <p>Each mod loading stage has a global {@link DeferredWorkQueue}, which is populated during the execution of the state
|
||||
* associated with this stage and emptied at the end of the state's execution.</p>
|
||||
*/
|
||||
public enum ModLoadingStage
|
||||
{
|
||||
/**
|
||||
* Special stage for exceptional situations and error handling.
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* Validation of the mod list.
|
||||
* TODO: figure out where this is used and why this exists instead of CONSTRUCT being the first normal stage
|
||||
*/
|
||||
VALIDATE,
|
||||
/**
|
||||
* Default stage of mod containers after construction.
|
||||
*/
|
||||
CONSTRUCT,
|
||||
/**
|
||||
* Common (non-side-specific) setup and initialization.
|
||||
*/
|
||||
COMMON_SETUP,
|
||||
/**
|
||||
* Side-specific setup and initialization.
|
||||
*
|
||||
* @see net.minecraftforge.api.distmarker.Dist
|
||||
*/
|
||||
SIDED_SETUP,
|
||||
/**
|
||||
* Stage for enqueuing {@link net.minecraftforge.fml.InterModComms} messages for later processing.
|
||||
*/
|
||||
ENQUEUE_IMC,
|
||||
/**
|
||||
* Stage for processing received messages though {@link net.minecraftforge.fml.InterModComms}.
|
||||
*/
|
||||
PROCESS_IMC,
|
||||
/**
|
||||
* Marks the completion of mod loading for this container.
|
||||
*/
|
||||
COMPLETE,
|
||||
/**
|
||||
* Marks the completion of the full mod loading process.
|
||||
*/
|
||||
DONE;
|
||||
|
||||
private final DeferredWorkQueue deferredWorkQueue;
|
||||
|
||||
ModLoadingStage() {
|
||||
deferredWorkQueue = new DeferredWorkQueue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the next stage after this stage, or {@link #ERROR} if the exception is not {@code null}}
|
||||
*
|
||||
* @param exception the exception that occurred during this stage, may be {@code null}
|
||||
*/
|
||||
ModLoadingStage nextState(Throwable exception) {
|
||||
return exception != null ? ERROR : values()[this.ordinal()+1];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return this stage, or {@link #ERROR} if the exception is not {@code null}}
|
||||
*
|
||||
* @param exception the exception that occurred during this stage, may be {@code null}
|
||||
*/
|
||||
public ModLoadingStage currentState(Throwable exception) {
|
||||
return exception != null ? ERROR : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the deferred work queue for this stage}
|
||||
*/
|
||||
public DeferredWorkQueue getDeferredWorkQueue() {
|
||||
return deferredWorkQueue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link IModLoadingState} interface.
|
||||
*
|
||||
* @param name the name of this state
|
||||
* @param previous the name of the state immediately previous to this state
|
||||
* @param message a function returning a human-friendly message for this state
|
||||
* @param phase the mod loading phase this state belongs to
|
||||
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @param transition optional state transition information
|
||||
*/
|
||||
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<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
return transition.map(t -> t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return an empty mod loading state} The mod loading state has a blank human-readable message, no inline runnable,
|
||||
* and no state transition information.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
*/
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return new ModLoadingState(name, previous, ml -> "", phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with state transition information and a default human-friendly message of
|
||||
* {@code Processing transition [name]}.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
* @return a mod loading state with state transition information and a default message
|
||||
*/
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with state transition information and a custom human-friendly message function.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param message a function returning a human-friendly message for this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
* @return a mod loading state with state transition information and a custom message
|
||||
*/
|
||||
public static ModLoadingState withTransition(final String name, final String previous,
|
||||
final Function<ModList, String> message, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with an inline runnable and a default human-friendly message of {@code Processing
|
||||
* work [name]}.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param inline an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @return a mod loading state with an inline runnable and default message
|
||||
*/
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
+2
-16
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
+2
-16
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.slf4j.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 = LogUtils.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConfigTracker {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final Marker CONFIG = MarkerFactory.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.UnmodifiableConfig;
|
||||
|
||||
public interface IConfigSpec<T extends IConfigSpec<T>> extends UnmodifiableConfig {
|
||||
@SuppressWarnings("unchecked")
|
||||
default T self() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
void acceptConfig(CommentedConfig data);
|
||||
|
||||
boolean isCorrecting();
|
||||
|
||||
boolean isCorrect(CommentedConfig commentedFileConfig);
|
||||
|
||||
int correct(CommentedConfig commentedFileConfig);
|
||||
|
||||
void afterReload();
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 com.electronwill.nightconfig.toml.TomlFormat;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class ModConfig
|
||||
{
|
||||
private final Type type;
|
||||
private final IConfigSpec<?> spec;
|
||||
private final String fileName;
|
||||
private final ModContainer container;
|
||||
private final ConfigFileTypeHandler configHandler;
|
||||
private CommentedConfig configData;
|
||||
private Callable<Void> saveHandler;
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
|
||||
this.type = type;
|
||||
this.spec = spec;
|
||||
this.fileName = fileName;
|
||||
this.container = container;
|
||||
this.configHandler = ConfigFileTypeHandler.TOML;
|
||||
ConfigTracker.INSTANCE.trackConfig(this);
|
||||
}
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer activeContainer) {
|
||||
this(type, spec, activeContainer, defaultConfigName(type, activeContainer.getModId()));
|
||||
}
|
||||
|
||||
private static String defaultConfigName(Type type, String modId) {
|
||||
// config file name would be "forge-client.toml" and "forge-server.toml"
|
||||
return String.format(Locale.ROOT, "%s-%s.toml", modId, type.extension());
|
||||
}
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public ConfigFileTypeHandler getHandler() {
|
||||
return configHandler;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends IConfigSpec<T>> IConfigSpec<T> getSpec() {
|
||||
return (IConfigSpec<T>) spec;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return container.getModId();
|
||||
}
|
||||
|
||||
public CommentedConfig getConfigData() {
|
||||
return this.configData;
|
||||
}
|
||||
|
||||
void setConfigData(final CommentedConfig configData) {
|
||||
this.configData = configData;
|
||||
this.spec.acceptConfig(this.configData);
|
||||
}
|
||||
|
||||
void fireEvent(final IConfigEvent configEvent) {
|
||||
this.container.dispatchConfigEvent(configEvent);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
((CommentedFileConfig)this.configData).save();
|
||||
}
|
||||
|
||||
public Path getFullPath() {
|
||||
return ((CommentedFileConfig)this.configData).getNioPath();
|
||||
}
|
||||
|
||||
public void acceptSyncedConfig(byte[] bytes) {
|
||||
setConfigData(TomlFormat.instance().createParser().parse(new ByteArrayInputStream(bytes)));
|
||||
fireEvent(IConfigEvent.reloading(this));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
/**
|
||||
* Common mod config for configuration that needs to be loaded on both environments.
|
||||
* Loaded on both servers and clients.
|
||||
* Stored in the global config directory.
|
||||
* Not synced.
|
||||
* Suffix is "-common" by default.
|
||||
*/
|
||||
COMMON,
|
||||
/**
|
||||
* Client config is for configuration affecting the ONLY client state such as graphical options.
|
||||
* Only loaded on the client side.
|
||||
* Stored in the global config directory.
|
||||
* Not synced.
|
||||
* Suffix is "-client" by default.
|
||||
*/
|
||||
CLIENT,
|
||||
// /**
|
||||
// * Player type config is configuration that is associated with a player.
|
||||
// * Preferences around machine states, for example.
|
||||
// */
|
||||
// PLAYER,
|
||||
/**
|
||||
* Server type config is configuration that is associated with a server instance.
|
||||
* Only loaded during server startup.
|
||||
* Stored in a server/save specific "serverconfig" directory.
|
||||
* Synced to clients during connection.
|
||||
* Suffix is "-server" by default.
|
||||
*/
|
||||
SERVER;
|
||||
|
||||
public String extension() {
|
||||
return StringUtils.toLowerCase(name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.event;
|
||||
|
||||
/**
|
||||
* Marker interface for events dispatched on the ModLifecycle event bus instead of the primary event bus
|
||||
*/
|
||||
public interface IModBusEvent {
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
public class CertificateHelper {
|
||||
|
||||
private static final String HEXES = "0123456789abcdef";
|
||||
|
||||
public static ImmutableList<String> getFingerprints(Certificate[] certificates)
|
||||
{
|
||||
int len = 0;
|
||||
if (certificates != null)
|
||||
{
|
||||
len = certificates.length;
|
||||
}
|
||||
ImmutableList.Builder<String> certBuilder = ImmutableList.builder();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
certBuilder.add(CertificateHelper.getFingerprint(certificates[i]));
|
||||
}
|
||||
return certBuilder.build();
|
||||
}
|
||||
|
||||
public static String getFingerprint(Certificate certificate)
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
return "NO VALID CERTIFICATE FOUND";
|
||||
}
|
||||
try
|
||||
{
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] der = certificate.getEncoded();
|
||||
md.update(der);
|
||||
byte[] digest = md.digest();
|
||||
return hexify(digest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return "CERTIFICATE FINGERPRINT EXCEPTION";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFingerprint(ByteBuffer buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(buffer);
|
||||
byte[] chksum = digest.digest();
|
||||
return hexify(chksum);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return "CERTIFICATE FINGERPRINT EXCEPTION";
|
||||
}
|
||||
}
|
||||
|
||||
private static String hexify(byte[] chksum)
|
||||
{
|
||||
final StringBuilder hex = new StringBuilder( 2 * chksum.length );
|
||||
for ( final byte b : chksum ) {
|
||||
hex.append(HEXES.charAt((b & 0xF0) >> 4))
|
||||
.append(HEXES.charAt((b & 0x0F)));
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+3
-17
@@ -1,23 +1,9 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common;
|
||||
package net.minecraftforge.fml.util;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util;
|
||||
|
||||
|
||||
public class LoaderException extends EnhancedRuntimeException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -5675297950958861378L;
|
||||
|
||||
public LoaderException(Throwable wrapped)
|
||||
{
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
public LoaderException()
|
||||
{
|
||||
}
|
||||
public LoaderException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
public LoaderException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override protected void printStackTrace(WrappedPrintStream stream){}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util;
|
||||
|
||||
/**
|
||||
* Prevent LoaderException from adding its own stack trace to the wrapped throwable's stack trace.
|
||||
*/
|
||||
public class LoaderExceptionModCrash extends LoaderException
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public LoaderExceptionModCrash(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+16
-31
@@ -1,31 +1,14 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common;
|
||||
package net.minecraftforge.fml.util;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import cpw.mods.modlauncher.api.INameMappingService;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -34,6 +17,8 @@ import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Some reflection helper code.
|
||||
@@ -58,7 +43,7 @@ public class ObfuscationReflectionHelper
|
||||
* @param name The name to try and remap.
|
||||
* @return The remapped name, or the original name if it couldn't be remapped.
|
||||
*/
|
||||
@Nonnull
|
||||
@NotNull
|
||||
public static String remapName(INameMappingService.Domain domain, String name)
|
||||
{
|
||||
return FMLLoader.getNameFunction("srg").map(f->f.apply(domain, name)).orElse(name);
|
||||
@@ -72,7 +57,7 @@ public class ObfuscationReflectionHelper
|
||||
*
|
||||
* @param classToAccess The class to find the field on.
|
||||
* @param instance The instance of the {@code classToAccess}.
|
||||
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
|
||||
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "f_46443_").
|
||||
* @param <T> The type of the value.
|
||||
* @param <E> The type of the {@code classToAccess}.
|
||||
* @return The value of the field with the specified name in the {@code classToAccess}.
|
||||
@@ -113,7 +98,7 @@ public class ObfuscationReflectionHelper
|
||||
* @throws UnableToFindFieldException If there was a problem getting the field.
|
||||
* @throws UnableToAccessFieldException If there was a problem setting the value of the field.
|
||||
*/
|
||||
public static <T, E> void setPrivateValue(@Nonnull final Class<? super T> classToAccess, @Nonnull final T instance, @Nullable final E value, @Nonnull final String fieldName)
|
||||
public static <T, E> void setPrivateValue(@NotNull final Class<? super T> classToAccess, @NotNull final T instance, @Nullable final E value, @NotNull final String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -138,7 +123,7 @@ public class ObfuscationReflectionHelper
|
||||
* Throws an exception if the method is not found.
|
||||
*
|
||||
* @param clazz The class to find the method on.
|
||||
* @param methodName The SRG (unmapped) name of the method to find (e.g. "func_12820_D").
|
||||
* @param methodName The SRG (unmapped) name of the method to find (e.g. "m_5776_").
|
||||
* @param parameterTypes The parameter types of the method to find.
|
||||
* @return The method with the specified name and parameters in the given class.
|
||||
* @throws NullPointerException If {@code clazz} is null.
|
||||
@@ -147,8 +132,8 @@ public class ObfuscationReflectionHelper
|
||||
* @throws NullPointerException If {@code parameterTypes} is null.
|
||||
* @throws UnableToFindMethodException If the method could not be found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Method findMethod(@Nonnull final Class<?> clazz, @Nonnull final String methodName, @Nonnull final Class<?>... parameterTypes)
|
||||
@NotNull
|
||||
public static Method findMethod(@NotNull final Class<?> clazz, @NotNull final String methodName, @NotNull final Class<?>... parameterTypes)
|
||||
{
|
||||
Preconditions.checkNotNull(clazz, "Class to find method on cannot be null.");
|
||||
Preconditions.checkNotNull(methodName, "Name of method to find cannot be null.");
|
||||
@@ -181,8 +166,8 @@ public class ObfuscationReflectionHelper
|
||||
* @throws NullPointerException If {@code parameterTypes} is null.
|
||||
* @throws UnknownConstructorException If the constructor could not be found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> Constructor<T> findConstructor(@Nonnull final Class<T> clazz, @Nonnull final Class<?>... parameterTypes)
|
||||
@NotNull
|
||||
public static <T> Constructor<T> findConstructor(@NotNull final Class<T> clazz, @NotNull final Class<?>... parameterTypes)
|
||||
{
|
||||
Preconditions.checkNotNull(clazz, "Class to find constructor on cannot be null.");
|
||||
Preconditions.checkNotNull(parameterTypes, "Parameter types of constructor to find cannot be null.");
|
||||
@@ -216,7 +201,7 @@ public class ObfuscationReflectionHelper
|
||||
* Throws an exception if the field is not found.
|
||||
*
|
||||
* @param clazz The class to find the field on.
|
||||
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
|
||||
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "f_46443_").
|
||||
* @param <T> The type.
|
||||
* @return The constructor with the specified parameters in the given class.
|
||||
* @throws NullPointerException If {@code clazz} is null.
|
||||
@@ -224,8 +209,8 @@ public class ObfuscationReflectionHelper
|
||||
* @throws IllegalArgumentException If {@code fieldName} is empty.
|
||||
* @throws UnableToFindFieldException If the field could not be found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> Field findField(@Nonnull final Class<? super T> clazz, @Nonnull final String fieldName)
|
||||
@NotNull
|
||||
public static <T> Field findField(@NotNull final Class<? super T> clazz, @NotNull final String fieldName)
|
||||
{
|
||||
Preconditions.checkNotNull(clazz, "Class to find field on cannot be null.");
|
||||
Preconditions.checkNotNull(fieldName, "Name of field to find cannot be null.");
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
public class EffectiveSide
|
||||
{
|
||||
public static LogicalSide get() {
|
||||
final ThreadGroup group = Thread.currentThread().getThreadGroup();
|
||||
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : LogicalSide.CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* A thread group and factory combination which belongs to a {@link LogicalSide}.
|
||||
*/
|
||||
public final class SidedThreadGroup extends ThreadGroup implements ThreadFactory
|
||||
{
|
||||
private final LogicalSide side;
|
||||
|
||||
SidedThreadGroup(final LogicalSide side)
|
||||
{
|
||||
super(side.name());
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the side this sided thread group belongs to.
|
||||
*
|
||||
* @return the side
|
||||
*/
|
||||
public LogicalSide getSide()
|
||||
{
|
||||
return this.side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NotNull final Runnable runnable)
|
||||
{
|
||||
return new Thread(this, runnable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
public final class SidedThreadGroups
|
||||
{
|
||||
public static final SidedThreadGroup CLIENT = new SidedThreadGroup(LogicalSide.CLIENT);
|
||||
public static final SidedThreadGroup SERVER = new SidedThreadGroup(LogicalSide.SERVER);
|
||||
|
||||
private SidedThreadGroups() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
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) }
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api("org.ow2.asm:asm:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
|
||||
api("net.minecraftforge:forgespi:${SPI_VERSION}")
|
||||
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
|
||||
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
|
||||
api("com.google.guava:guava:${GUAVA_VERSION}")
|
||||
api("com.google.code.gson:gson:${GSON_VERSION}")
|
||||
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
|
||||
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
|
||||
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
|
||||
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
|
||||
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
|
||||
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
|
||||
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
|
||||
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
|
||||
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
|
||||
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
|
||||
// implementation("org.jline:jline:${JLINE_VERSION}")
|
||||
|
||||
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
|
||||
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'/': [
|
||||
'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 ->
|
||||
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
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common.asm;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.signature.SignatureReader;
|
||||
import org.objectweb.asm.signature.SignatureVisitor;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
|
||||
/**
|
||||
* Implements getType() in CapabilityToken subclasses.
|
||||
*
|
||||
* Using the class's signature to determine the generic type of TypeToken, and then implements getType() using that value.
|
||||
* <pre>
|
||||
* Example:
|
||||
*
|
||||
* <code>new CapabilityToken<String>(){}</code>
|
||||
* Has the signature <code>"CapabilityToken<Ljava/lang/String;>"</code>
|
||||
*
|
||||
* Implements the method:
|
||||
* <code>public String getType() {
|
||||
* return "java/lang/String";
|
||||
* }</code>
|
||||
* </pre>
|
||||
*/
|
||||
public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
private final String FUNC_NAME = "getType";
|
||||
private final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "capability_token_subclass";
|
||||
}
|
||||
|
||||
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
if (CAP_INJECT.equals(classNode.name))
|
||||
{
|
||||
for (MethodNode mtd : classNode.methods)
|
||||
{
|
||||
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc))
|
||||
{
|
||||
mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated.
|
||||
}
|
||||
}
|
||||
return ComputeFlags.SIMPLE_REWRITE;
|
||||
}
|
||||
else if (CAP_INJECT.equals(classNode.superName))
|
||||
{
|
||||
Holder cls = new Holder();
|
||||
|
||||
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
|
||||
reader.accept(new SignatureVisitor(Opcodes.ASM9)
|
||||
{
|
||||
Deque<String> stack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void visitClassType(final String name)
|
||||
{
|
||||
stack.push(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClassType(final String name)
|
||||
{
|
||||
stack.push(stack.pop() + '$' + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd()
|
||||
{
|
||||
var val = stack.pop();
|
||||
if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek()))
|
||||
cls.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
if (cls.value == null)
|
||||
throw new IllegalStateException("Could not find signature for CapabilityToken on " + classNode.name + " from " + classNode.signature);
|
||||
|
||||
var mtd = classNode.visitMethod(Opcodes.ACC_PUBLIC, FUNC_NAME, FUNC_DESC, null, new String[0]);
|
||||
mtd.visitLdcInsn(cls.value);
|
||||
mtd.visitInsn(Opcodes.ARETURN);
|
||||
mtd.visitEnd();
|
||||
return ComputeFlags.COMPUTE_MAXS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Holder {
|
||||
String value;
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common.asm;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
|
||||
/**
|
||||
* Removes the final modifier from fields with the @ObjectHolder annotation, prevents the JITer from in lining them so our runtime replacements can work.
|
||||
* Will also de-finalize all fields in on class level annotations.
|
||||
*/
|
||||
public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
// Hardcoded map of vanilla classes that should have object holders for each field of the given registry type.
|
||||
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderRegistry. Duplicated cuz classloaders, yay!
|
||||
// Classnames are validated in ObjectHolderRegistry.
|
||||
private static final Map<String, VanillaObjectHolderData> VANILLA_OBJECT_HOLDERS = Stream.of(
|
||||
new VanillaObjectHolderData("net.minecraft.world.level.block.Blocks", "block", "net.minecraft.world.level.block.Block"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.item.Items", "item", "net.minecraft.world.item.Item"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.item.enchantment.Enchantments", "enchantment", "net.minecraft.world.item.enchantment.Enchantment"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
|
||||
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
|
||||
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
|
||||
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
|
||||
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
|
||||
.map(it -> it.replace('.', '/'))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "object_holder_definalize";
|
||||
}
|
||||
|
||||
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
// Let's skip processing classes that definitely don't have object holders
|
||||
String internalName = classType.getInternalName();
|
||||
|
||||
// Forge classes that aren't in the debug package (that package is used for tests)
|
||||
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug")
|
||||
&& !internalName.equals("net/minecraftforge/common/crafting/ConditionalRecipe"))
|
||||
return NAY;
|
||||
|
||||
// Vanilla classes that don't have object holders
|
||||
if (internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
|
||||
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private boolean hasHolder(List<AnnotationNode> lst) {
|
||||
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||
}
|
||||
|
||||
private String getValue(List<AnnotationNode> lst)
|
||||
{
|
||||
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
|
||||
if (ann.values != null)
|
||||
{
|
||||
for (int x = 0; x < ann.values.size() - 1; x += 2) {
|
||||
if (ann.values.get(x).equals("value")) {
|
||||
return (String)ann.values.get(x + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
final AtomicBoolean changes = new AtomicBoolean();
|
||||
//Must be public static finals, and non-array objects
|
||||
|
||||
//Fix Annotated Fields before injecting from class level
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL; //Strip final
|
||||
f.access |= Opcodes.ACC_SYNTHETIC; //Add Synthetic so we can check in runtime. ? Good idea?
|
||||
changes.compareAndSet(false, prev != f.access);
|
||||
});
|
||||
|
||||
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
|
||||
{
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL;
|
||||
f.access |= Opcodes.ACC_SYNTHETIC;
|
||||
/*if (!hasHolder(f.visibleAnnotations)) //Add field level annotation, doesn't do anything until after we figure out how ASMDataTable is gatherered
|
||||
{
|
||||
if (value == null)
|
||||
f.visitAnnotation(OBJECT_HOLDER, true);
|
||||
else
|
||||
f.visitAnnotation(OBJECT_HOLDER, true).visit("value", value + ":" + f.name.toLowerCase());
|
||||
}*/
|
||||
changes.compareAndSet(false, prev != f.access);
|
||||
});
|
||||
}
|
||||
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
|
||||
}
|
||||
+46
-35
@@ -1,32 +1,15 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.common.asm;
|
||||
package net.minecraftforge.fml.common.asm;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
@@ -36,6 +19,9 @@ import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
/**
|
||||
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
|
||||
@@ -43,7 +29,7 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
*/
|
||||
public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Type STRING = Type.getType(String.class);
|
||||
private final Type ENUM = Type.getType(Enum.class);
|
||||
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
|
||||
@@ -78,43 +64,49 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
|
||||
|
||||
|
||||
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
|
||||
//Static methods named "create" with first argument as a string
|
||||
List<MethodNode> candidates = classNode.methods.stream()
|
||||
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
|
||||
}
|
||||
|
||||
|
||||
candidates.forEach(mtd ->
|
||||
{
|
||||
Type[] args = Type.getArgumentTypes(mtd.desc);
|
||||
if (args.length == 0 || !args[0].equals(STRING)) {
|
||||
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
|
||||
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Enum has create method without String as first parameter:\n");
|
||||
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
|
||||
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
|
||||
}));
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
|
||||
}
|
||||
throw new IllegalStateException("Enum has create method without String as first parameter: " + mtd.name + mtd.desc);
|
||||
}
|
||||
|
||||
Type ret = Type.getReturnType(mtd.desc);
|
||||
if (!ret.equals(classType)) {
|
||||
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
|
||||
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Enum has create method with incorrect return type:\n");
|
||||
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
|
||||
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
|
||||
sb.append(" Found: ").append(ret.getClassName()).append(", Expected: ").append(classType.getClassName());
|
||||
}));
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
|
||||
}
|
||||
throw new IllegalStateException("Enum has create method with incorrect return type: " + mtd.name + mtd.desc);
|
||||
}
|
||||
|
||||
|
||||
Type[] ctrArgs = new Type[args.length + 1];
|
||||
ctrArgs[0] = STRING;
|
||||
ctrArgs[1] = Type.INT_TYPE;
|
||||
@@ -126,23 +118,29 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
MethodNode ctr = classNode.methods.stream().filter(m -> m.name.equals("<init>") && m.desc.equals(desc)).findFirst().orElse(null);
|
||||
if (ctr == null)
|
||||
{
|
||||
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
|
||||
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Enum has create method with no matching constructor:\n");
|
||||
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
|
||||
sb.append(" Candidate: ").append(mtd.desc).append("\n");
|
||||
sb.append(" Target: ").append(desc).append("\n");
|
||||
classNode.methods.stream().filter(m -> m.name.equals("<init>")).forEach(m -> sb.append(" : ").append(m.desc).append("\n"));
|
||||
}));
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
|
||||
}
|
||||
throw new IllegalStateException("Enum has create method with no matching constructor: " + desc);
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
|
||||
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Enum has create method but we could not find $VALUES. Found:\n");
|
||||
classNode.fields.stream().filter(f -> (f.access & Opcodes.ACC_STATIC) != 0).
|
||||
forEach(m -> sb.append(" ").append(m.name).append(" ").append(m.desc).append("\n"));
|
||||
}));
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
|
||||
}
|
||||
throw new IllegalStateException("Enum has create method but we could not find $VALUES");
|
||||
}
|
||||
|
||||
@@ -150,6 +148,19 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
|
||||
mtd.access |= Opcodes.ACC_SYNCHRONIZED;
|
||||
mtd.instructions.clear();
|
||||
mtd.localVariables.clear();
|
||||
if (mtd.tryCatchBlocks != null)
|
||||
{
|
||||
mtd.tryCatchBlocks.clear();
|
||||
}
|
||||
if (mtd.visibleLocalVariableAnnotations != null)
|
||||
{
|
||||
mtd.visibleLocalVariableAnnotations.clear();
|
||||
}
|
||||
if (mtd.invisibleLocalVariableAnnotations != null)
|
||||
{
|
||||
mtd.invisibleLocalVariableAnnotations.clear();
|
||||
}
|
||||
InstructionAdapter ins = new InstructionAdapter(mtd);
|
||||
|
||||
int vars = 0;
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BackgroundWaiter {
|
||||
private static ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
final Future<?> work = runner.submit(r);
|
||||
do {
|
||||
tick.run();
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} while (!work.isDone());
|
||||
try {
|
||||
runner.shutdown();
|
||||
work.get();
|
||||
} catch (ExecutionException ee) {
|
||||
throw new RuntimeException(ee.getCause());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class ClasspathLocatorUtils {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
|
||||
try {
|
||||
Path path;
|
||||
final URI uri = resource.toURI();
|
||||
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
|
||||
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
|
||||
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
|
||||
} else {
|
||||
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
|
||||
}
|
||||
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
|
||||
return path;
|
||||
} catch (NullPointerException | URISyntaxException e) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
|
||||
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(Path gameDirectory) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
|
||||
if (launchTarget != null && launchTarget.contains("dev")) {
|
||||
this.scan(gameDirectory);
|
||||
}
|
||||
return List.copyOf(found);
|
||||
}
|
||||
|
||||
private final static List<NamedPath> found = new ArrayList<>();
|
||||
|
||||
public static List<Path> allExcluded() {
|
||||
return found.stream().map(np->np.paths()[0]).toList();
|
||||
}
|
||||
|
||||
private void scan(final Path gameDirectory) {
|
||||
try {
|
||||
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
|
||||
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
|
||||
} catch (IOException e) {
|
||||
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void locateTransformers(String resource) throws IOException {
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
|
||||
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
continue;
|
||||
found.add(new NamedPath(path.toUri().toString(), path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Thrown during early loading phase, and collected by the LoadingModList for handoff to the client
|
||||
* or server.
|
||||
*/
|
||||
public class EarlyLoadingException extends RuntimeException {
|
||||
public static class ExceptionData {
|
||||
private final IModInfo modInfo;
|
||||
private final String i18message;
|
||||
private final Object[] args;
|
||||
|
||||
public ExceptionData(final String message, Object... args) {
|
||||
this(message, null, args);
|
||||
}
|
||||
|
||||
public ExceptionData(final String message, final IModInfo modInfo, Object... args) {
|
||||
this.i18message = message;
|
||||
this.modInfo = modInfo;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public String getI18message() {
|
||||
return i18message;
|
||||
}
|
||||
|
||||
public Object[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public IModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
}
|
||||
private final List<ExceptionData> errorMessages;
|
||||
|
||||
public List<ExceptionData> getAllData() {
|
||||
return errorMessages;
|
||||
}
|
||||
|
||||
public EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
|
||||
super(message, originalException);
|
||||
this.errorMessages = errorMessages;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.electronwill.nightconfig.core.ConfigSpec;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
|
||||
import com.electronwill.nightconfig.core.io.ParsingException;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLConfig
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static FMLConfig INSTANCE = new FMLConfig();
|
||||
private static ConfigSpec configSpec = new ConfigSpec();
|
||||
static {
|
||||
configSpec.define("splashscreen", Boolean.TRUE);
|
||||
configSpec.define("maxThreads", -1);
|
||||
configSpec.define("versionCheck", Boolean.TRUE);
|
||||
configSpec.define("defaultConfigPath", "defaultconfigs");
|
||||
configSpec.define("disableOptimizedDFU", Boolean.TRUE);
|
||||
}
|
||||
|
||||
private CommentedFileConfig configData;
|
||||
|
||||
private void loadFrom(final Path configFile)
|
||||
{
|
||||
configData = CommentedFileConfig.builder(configFile).sync()
|
||||
.onFileNotFound(FileNotFoundAction.copyData(Objects.requireNonNull(getClass().getResourceAsStream("/META-INF/defaultfmlconfig.toml"))))
|
||||
.autosave().autoreload()
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
}
|
||||
catch (ParsingException e)
|
||||
{
|
||||
throw new RuntimeException("Failed to load FML config from " + configFile.toString(), e);
|
||||
}
|
||||
if (!configSpec.isCorrect(configData)) {
|
||||
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
|
||||
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
|
||||
LOGGER.warn(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
|
||||
}
|
||||
configData.save();
|
||||
}
|
||||
|
||||
public static void load()
|
||||
{
|
||||
final Path configFile = FMLPaths.FMLCONFIG.get();
|
||||
INSTANCE.loadFrom(configFile);
|
||||
if (LOGGER.isTraceEnabled(CORE))
|
||||
{
|
||||
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
|
||||
LOGGER.trace(CORE, "Splash screen is {}", FMLConfig.splashScreenEnabled());
|
||||
LOGGER.trace(CORE, "Max threads for mod loading computed at {}", FMLConfig.loadingThreadCount());
|
||||
LOGGER.trace(CORE, "Version check is {}", FMLConfig.runVersionCheck());
|
||||
LOGGER.trace(CORE, "Default config paths at {}", FMLConfig.defaultConfigPath());
|
||||
}
|
||||
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.defaultConfigPath()), "default config directory");
|
||||
}
|
||||
|
||||
public static boolean splashScreenEnabled() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("splashscreen").orElse(Boolean.FALSE);
|
||||
}
|
||||
|
||||
public static int loadingThreadCount() {
|
||||
int val = INSTANCE.configData.<Integer>getOptional("maxThreads").orElse(-1);
|
||||
if (val <= 0) return Runtime.getRuntime().availableProcessors();
|
||||
return val;
|
||||
}
|
||||
|
||||
public static boolean runVersionCheck() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("versionCheck").orElse(Boolean.TRUE);
|
||||
}
|
||||
|
||||
public static String defaultConfigPath() {
|
||||
return INSTANCE.configData.<String>getOptional("defaultConfigPath").orElse("defaultconfigs");
|
||||
}
|
||||
|
||||
public static boolean isOptimizedDFUDisabled() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("disableOptimizedDFU").orElse(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.IEnvironment;
|
||||
import cpw.mods.modlauncher.api.TypesafeMap;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class FMLEnvironment
|
||||
{
|
||||
public static final Dist dist = FMLLoader.getDist();
|
||||
public static final String naming = FMLLoader.getNaming();
|
||||
public static final boolean production = FMLLoader.isProduction() || System.getProperties().containsKey("production");
|
||||
public static final boolean secureJarsEnabled = FMLLoader.isSecureJarEnabled();
|
||||
|
||||
static void setupInteropEnvironment(IEnvironment environment) {
|
||||
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), v->naming);
|
||||
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), v->dist);
|
||||
}
|
||||
|
||||
public static class Keys {
|
||||
public static final Supplier<TypesafeMap.Key<ClassLoader>> LOCATORCLASSLOADER = IEnvironment.buildKey("LOCATORCLASSLOADER",ClassLoader.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
|
||||
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.progress.EarlyProgressVisualization;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
|
||||
public class FMLLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ModDiscoverer modDiscoverer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static ILaunchPluginService eventBus;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
private static String naming;
|
||||
private static LoadingModList loadingModList;
|
||||
private static RuntimeDistCleaner runtimeDistCleaner;
|
||||
private static Path gamePath;
|
||||
private static VersionInfo versionInfo;
|
||||
private static String launchHandlerName;
|
||||
private static CommonLaunchHandler commonLaunchHandler;
|
||||
public static Runnable progressWindowTick;
|
||||
private static ModValidator modValidator;
|
||||
public static BackgroundScanHandler backgroundScanHandler;
|
||||
private static boolean production;
|
||||
private static IModuleLayerManager moduleLayerManager;
|
||||
|
||||
static void onInitialLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
final String version = LauncherVersion.getVersion();
|
||||
LOGGER.debug(CORE,"FML {} loading", version);
|
||||
final Package modLauncherPackage = ITransformationService.class.getPackage();
|
||||
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
|
||||
if (!modLauncherPackage.isCompatibleWith("4.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
|
||||
});
|
||||
|
||||
final Package atPackage = accessTransformer.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
|
||||
if (!atPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
|
||||
});
|
||||
|
||||
final Package eventBusPackage = eventBus.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
|
||||
if (!eventBusPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
|
||||
});
|
||||
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
|
||||
|
||||
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
|
||||
|
||||
if (coreModProviders.isEmpty()) {
|
||||
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
|
||||
throw new IncompatibleEnvironmentException("No coremod library found");
|
||||
} else if (coreModProviders.size() > 1) {
|
||||
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
|
||||
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
|
||||
}
|
||||
|
||||
coreModProvider = coreModProviders.get(0);
|
||||
final Package coremodPackage = coreModProvider.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", coremodPackage.getImplementationVersion());
|
||||
|
||||
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
|
||||
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
|
||||
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
|
||||
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOGGER.error(CORE, "Failed to load NightConfig");
|
||||
throw new IncompatibleEnvironmentException("Missing NightConfig");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupBlackboardKeys() {
|
||||
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
|
||||
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
|
||||
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
|
||||
{
|
||||
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
|
||||
arguments.put("launchTarget", launchTarget);
|
||||
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
|
||||
LOGGER.debug(CORE, "Using {} as launch service", launchTarget);
|
||||
if (launchHandler.isEmpty()) {
|
||||
LOGGER.error(CORE, "Missing LaunchHandler {}, cannot continue", launchTarget);
|
||||
throw new RuntimeException("Missing launch handler: " + launchTarget);
|
||||
}
|
||||
|
||||
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
|
||||
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
|
||||
throw new RuntimeException("Incompatible launch handler found");
|
||||
}
|
||||
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
|
||||
launchHandlerName = launchHandler.get().name();
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
|
||||
|
||||
naming = commonLaunchHandler.getNaming();
|
||||
dist = commonLaunchHandler.getDist();
|
||||
production = commonLaunchHandler.isProduction();
|
||||
|
||||
versionInfo = new VersionInfo(arguments);
|
||||
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Early Loading!"));
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
|
||||
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
|
||||
|
||||
runtimeDistCleaner.getExtension().accept(dist);
|
||||
}
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments)
|
||||
{
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
var pluginResources = modValidator.getPluginResources();
|
||||
return List.of(pluginResources);
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
|
||||
progressWindowTick = EarlyProgressVisualization.INSTANCE.accept(dist, commonLaunchHandler.isData(), versionInfo.mcVersion());
|
||||
moduleLayerManager = layerManager;
|
||||
languageLoadingProvider = new LanguageLoadingProvider();
|
||||
backgroundScanHandler = modValidator.stage2Validation();
|
||||
loadingModList = backgroundScanHandler.getLoadingModList();
|
||||
return List.of(modValidator.getModResources());
|
||||
}
|
||||
|
||||
public static ICoreModProvider getCoreModProvider() {
|
||||
return coreModProvider;
|
||||
}
|
||||
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider()
|
||||
{
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
static ModDiscoverer getModDiscoverer() {
|
||||
return modDiscoverer;
|
||||
}
|
||||
|
||||
public static CommonLaunchHandler getLaunchHandler() {
|
||||
return commonLaunchHandler;
|
||||
}
|
||||
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName)
|
||||
{
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
|
||||
accessTransformer.offerResource(atPath, modName.getFileName());
|
||||
}
|
||||
|
||||
public static Dist getDist()
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
|
||||
public static void beforeStart(ClassLoader launchClassLoader)
|
||||
{
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Launching minecraft"));
|
||||
progressWindowTick.run();
|
||||
}
|
||||
|
||||
public static LoadingModList getLoadingModList()
|
||||
{
|
||||
return loadingModList;
|
||||
}
|
||||
|
||||
public static Path getGamePath()
|
||||
{
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
public static String getNaming() {
|
||||
return naming;
|
||||
}
|
||||
|
||||
public static Optional<BiFunction<INameMappingService.Domain, String, String>> getNameFunction(final String naming) {
|
||||
return Launcher.INSTANCE.environment().findNameMapping(naming);
|
||||
}
|
||||
|
||||
public static String getLauncherInfo() {
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MLIMPL_VERSION.get()).orElse("MISSING");
|
||||
}
|
||||
|
||||
public static List<Map<String, String>> modLauncherModList() {
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElseGet(Collections::emptyList);
|
||||
}
|
||||
|
||||
public static String launcherHandlerName() {
|
||||
return launchHandlerName;
|
||||
}
|
||||
|
||||
public static boolean isProduction() {
|
||||
return production;
|
||||
}
|
||||
|
||||
public static boolean isSecureJarEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ModuleLayer getGameLayer() {
|
||||
return moduleLayerManager.getLayer(IModuleLayerManager.Layer.GAME).orElseThrow();
|
||||
}
|
||||
|
||||
public static VersionInfo versionInfo() {
|
||||
return versionInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.IEnvironment;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public enum FMLPaths
|
||||
{
|
||||
GAMEDIR(),
|
||||
MODSDIR("mods"),
|
||||
CONFIGDIR("config"),
|
||||
FMLCONFIG(false, CONFIGDIR, "fml.toml");
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Path relativePath;
|
||||
private final boolean isDirectory;
|
||||
private Path absolutePath;
|
||||
|
||||
FMLPaths() {
|
||||
this("");
|
||||
}
|
||||
|
||||
FMLPaths(String... path) {
|
||||
relativePath = computePath(path);
|
||||
this.isDirectory = true;
|
||||
}
|
||||
|
||||
FMLPaths(boolean isDir, FMLPaths parent, String... path) {
|
||||
this.relativePath = parent.relativePath.resolve(computePath(path));
|
||||
this.isDirectory = isDir;
|
||||
}
|
||||
|
||||
private Path computePath(String... path)
|
||||
{
|
||||
return Paths.get(path[0], Arrays.copyOfRange(path, 1, path.length));
|
||||
}
|
||||
|
||||
public static void setup(IEnvironment env) {
|
||||
final Path rootPath = env.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElseThrow(() -> new RuntimeException("No game path found"));
|
||||
|
||||
loadAbsolutePaths(rootPath);
|
||||
}
|
||||
|
||||
public static void loadAbsolutePaths(Path rootPath)
|
||||
{
|
||||
for (FMLPaths path : FMLPaths.values())
|
||||
{
|
||||
path.absolutePath = rootPath.resolve(path.relativePath).toAbsolutePath().normalize();
|
||||
if (path.isDirectory)
|
||||
{
|
||||
FileUtils.getOrCreateDirectory(path.absolutePath, path.name());
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(CORE))
|
||||
{
|
||||
LOGGER.debug(CORE, "Path {} is {}", path, path.absolutePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Path getOrCreateGameRelativePath(Path path, String name) {
|
||||
return FileUtils.getOrCreateDirectory(FMLPaths.GAMEDIR.get().resolve(path), name);
|
||||
}
|
||||
|
||||
public Path relative() {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
public Path get() {
|
||||
return absolutePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private ArgumentAcceptingOptionSpec<String> modsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> modListsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeGroupOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcpOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mappingsOption;
|
||||
private List<String> modsArgumentList;
|
||||
private List<String> modListsArgumentList;
|
||||
private List<String> mavenRootsArgumentList;
|
||||
private String targetForgeVersion;
|
||||
private String targetMcVersion;
|
||||
private String targetMcpVersion;
|
||||
private String targetMcpMappings;
|
||||
private String targetForgeGroup;
|
||||
private Map<String, Object> arguments;
|
||||
|
||||
public FMLServiceProvider()
|
||||
{
|
||||
final String markerselection = System.getProperty("forge.logging.markers", "");
|
||||
Arrays.stream(markerselection.split(",")).forEach(marker -> System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
return "fml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(IEnvironment environment) {
|
||||
LOGGER.debug(CORE, "Setting up basic FML game directories");
|
||||
FMLPaths.setup(environment);
|
||||
LOGGER.debug(CORE, "Loading configuration");
|
||||
FMLConfig.load();
|
||||
LOGGER.debug(CORE, "Preparing ModFile");
|
||||
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
|
||||
arguments = new HashMap<>();
|
||||
arguments.put("modLists", modListsArgumentList);
|
||||
arguments.put("mods", modsArgumentList);
|
||||
arguments.put("mavenRoots", mavenRootsArgumentList);
|
||||
arguments.put("forgeVersion", targetForgeVersion);
|
||||
arguments.put("forgeGroup", targetForgeGroup);
|
||||
arguments.put("mcVersion", targetMcVersion);
|
||||
arguments.put("mcpVersion", targetMcpVersion);
|
||||
arguments.put("mcpMappings", targetMcpMappings);
|
||||
LOGGER.debug(CORE, "Preparing launch handler");
|
||||
FMLLoader.setupLaunchHandler(environment, arguments);
|
||||
FMLEnvironment.setupInteropEnvironment(environment);
|
||||
Environment.build(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> beginScanning(final IEnvironment environment) {
|
||||
LOGGER.debug(CORE,"Initiating mod scan");
|
||||
return FMLLoader.beginModScan(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> completeScan(final IModuleLayerManager layerManager) {
|
||||
return FMLLoader.completeScan(layerManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
// LOGGER.debug("Injecting tracing printstreams for STDOUT/STDERR.");
|
||||
// System.setOut(new TracingPrintStream(LogManager.getLogger("STDOUT"), System.out));
|
||||
// System.setErr(new TracingPrintStream(LogManager.getLogger("STDERR"), System.err));
|
||||
FMLLoader.onInitialLoad(environment, otherServices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder)
|
||||
{
|
||||
forgeOption = argumentBuilder.apply("forgeVersion", "Forge Version number").withRequiredArg().ofType(String.class).required();
|
||||
forgeGroupOption = argumentBuilder.apply("forgeGroup", "Forge Group (for testing)").withRequiredArg().ofType(String.class).defaultsTo("net.minecraftforge");
|
||||
mcOption = argumentBuilder.apply("mcVersion", "Minecraft Version number").withRequiredArg().ofType(String.class).required();
|
||||
mcpOption = argumentBuilder.apply("mcpVersion", "MCP Version number").withRequiredArg().ofType(String.class).required();
|
||||
mappingsOption = argumentBuilder.apply("mcpMappings", "MCP Mappings Channel and Version").withRequiredArg().ofType(String.class);
|
||||
modsOption = argumentBuilder.apply("mods", "List of mods to add").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
modListsOption = argumentBuilder.apply("modLists", "JSON modlists").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
mavenRootsOption = argumentBuilder.apply("mavenRoots", "Maven root directories").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void argumentValues(OptionResult option)
|
||||
{
|
||||
modsArgumentList = option.values(modsOption);
|
||||
modListsArgumentList = option.values(modListsOption);
|
||||
mavenRootsArgumentList = option.values(mavenRootsOption);
|
||||
targetForgeVersion = option.value(forgeOption);
|
||||
targetForgeGroup = option.value(forgeGroupOption);
|
||||
targetMcVersion = option.value(mcOption);
|
||||
targetMcpVersion = option.value(mcpOption);
|
||||
targetMcpMappings = option.value(mappingsOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers()
|
||||
{
|
||||
LOGGER.debug(CORE, "Loading coremod transformers");
|
||||
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public static Path getOrCreateDirectory(Path dirPath, String dirLabel) {
|
||||
if (!Files.isDirectory(dirPath.getParent())) {
|
||||
getOrCreateDirectory(dirPath.getParent(), "parent of "+dirLabel);
|
||||
}
|
||||
if (!Files.isDirectory(dirPath))
|
||||
{
|
||||
LOGGER.debug(CORE, "Making {} directory : {}", dirLabel, dirPath);
|
||||
try {
|
||||
Files.createDirectory(dirPath);
|
||||
} catch (IOException e) {
|
||||
if (e instanceof FileAlreadyExistsException) {
|
||||
LOGGER.error(CORE, "Failed to create {} directory - there is a file in the way", dirLabel);
|
||||
} else {
|
||||
LOGGER.error(CORE, "Problem with creating {} directory (Permissions?)", dirLabel, e);
|
||||
}
|
||||
throw new RuntimeException("Problem creating directory", e);
|
||||
}
|
||||
LOGGER.debug(CORE, "Created {} directory : {}", dirLabel, dirPath);
|
||||
} else {
|
||||
LOGGER.debug(CORE, "Found existing {} directory : {}", dirLabel, dirPath);
|
||||
}
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
|
||||
public static String fileExtension(final Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
if (idx > -1) {
|
||||
return fileName.substring(idx+1);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean matchFileName(String path, String... matches) {
|
||||
// Extract file name from path
|
||||
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
|
||||
// Check if it contains any of the desired keywords
|
||||
for (String match : matches) {
|
||||
if (name.contains(match)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Finds Version data from a package, with possible default values
|
||||
*/
|
||||
public class JarVersionLookupHandler {
|
||||
public static Optional<String> getImplementationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
public static Optional<String> getSpecificationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getSpecificationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
public static Optional<String> getImplementationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
public static Optional<String> getImplementationTitle(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationTitle();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
|
||||
public static Optional<String> getSpecificationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getSpecificationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public class LanguageLoadingProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
|
||||
private final ServiceLoader<IModLanguageProvider> serviceLoader;
|
||||
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
|
||||
private List<Path> languagePaths = new ArrayList<>();
|
||||
|
||||
public void forEach(final Consumer<IModLanguageProvider> consumer)
|
||||
{
|
||||
languageProviders.forEach(consumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEach(final Function<IModLanguageProvider, T> function) {
|
||||
return languageProviders.stream().map(function);
|
||||
}
|
||||
|
||||
private static class ModLanguageWrapper {
|
||||
private final IModLanguageProvider modLanguageProvider;
|
||||
|
||||
private final ArtifactVersion version;
|
||||
public ModLanguageWrapper(IModLanguageProvider modLanguageProvider, ArtifactVersion version)
|
||||
{
|
||||
this.modLanguageProvider = modLanguageProvider;
|
||||
this.version = version;
|
||||
}
|
||||
public ArtifactVersion getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public IModLanguageProvider getModLanguageProvider()
|
||||
{
|
||||
return modLanguageProvider;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
LanguageLoadingProvider() {
|
||||
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm->lm.getLayer(IModuleLayerManager.Layer.PLUGIN)).orElseThrow();
|
||||
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
|
||||
loadLanguageProviders();
|
||||
}
|
||||
private void loadLanguageProviders() {
|
||||
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
|
||||
serviceLoader.forEach(languageProviders::add);
|
||||
|
||||
languageProviders.forEach(lp -> {
|
||||
final Path lpPath;
|
||||
try {
|
||||
lpPath = Paths.get(lp.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Huh?", e);
|
||||
}
|
||||
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
|
||||
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
|
||||
if (impl == null) {
|
||||
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
|
||||
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
|
||||
}
|
||||
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Loaded language provider "+lp.name()+ " " + impl));
|
||||
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
|
||||
});
|
||||
}
|
||||
|
||||
void addForgeLanguage(final Path forgePath) {
|
||||
if (!languageProviderMap.containsKey("javafml")) {
|
||||
LOGGER.debug(CORE,"Adding forge as a language from {}", forgePath.toString());
|
||||
addLanguagePaths(Stream.of(forgePath));
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
} else {
|
||||
LOGGER.debug(CORE, "Skipping adding forge jar - javafml is already present");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguagePaths(final Stream<Path> langPaths) {
|
||||
languageProviders.clear();
|
||||
languageProviderMap.clear();
|
||||
// langPaths.peek(languagePaths::add).map(Path::toFile).map(File::toURI).map(rethrowFunction(URI::toURL)).forEach(languageClassLoader::addURL);
|
||||
}
|
||||
|
||||
public void addAdditionalLanguages(List<ModFile> modFiles)
|
||||
{
|
||||
if (modFiles==null) return;
|
||||
Stream<Path> langPaths = modFiles.stream().map(ModFile::getFilePath);
|
||||
addLanguagePaths(langPaths);
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
}
|
||||
|
||||
Stream<Path> getLibraries() {
|
||||
return languagePaths.stream();
|
||||
}
|
||||
|
||||
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
|
||||
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
|
||||
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
|
||||
if (mlw == null) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
|
||||
throw new EarlyLoadingException("Missing language "+modLoader, null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, "null")));
|
||||
}
|
||||
if (!VersionSupportMatrix.testVersionSupportMatrix(modLoaderVersion, modLoader, "languageloader", (llid, range) -> range.containsVersion(mlw.getVersion()))) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}, found {}", modLoader, modLoaderVersion, languageFileName, mlw.getVersion());
|
||||
throw new EarlyLoadingException("Missing language "+ modLoader + " matching range "+modLoaderVersion + " found "+mlw.getVersion(), null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, mlw.getVersion())));
|
||||
}
|
||||
|
||||
return mlw.getModLanguageProvider();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class LauncherVersion {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final String launcherVersion;
|
||||
|
||||
static {
|
||||
String vers = JarVersionLookupHandler.getImplementationVersion(LauncherVersion.class).orElse(System.getenv("LAUNCHER_VERSION"));
|
||||
if (vers == null) throw new RuntimeException("Missing FMLLauncher version, cannot continue");
|
||||
launcherVersion = vers;
|
||||
LOGGER.debug(CORE, "Found FMLLauncher version {}", launcherVersion);
|
||||
}
|
||||
|
||||
public static String getVersion()
|
||||
{
|
||||
return launcherVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LibraryFinder {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Path libsPath;
|
||||
static Path findLibsPath() {
|
||||
if (libsPath == null) {
|
||||
libsPath = Path.of(System.getProperty("libraryDirectory","crazysnowmannonsense/cheezwhizz"));
|
||||
if (!Files.isDirectory(libsPath)) {
|
||||
throw new IllegalStateException("Missing libraryDirectory system property, cannot continue");
|
||||
}
|
||||
}
|
||||
return libsPath;
|
||||
}
|
||||
|
||||
static Path getForgeLibraryPath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
|
||||
Path forgePath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", "universal", mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE, "Found forge path {} is {}", forgePath, pathStatus(forgePath));
|
||||
return forgePath;
|
||||
}
|
||||
|
||||
static String pathStatus(final Path path) {
|
||||
return Files.exists(path) ? "present" : "missing";
|
||||
}
|
||||
|
||||
static Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup, final String type) {
|
||||
Path srgMcPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "srg", mcVersion+"-"+mcpVersion));
|
||||
Path mcExtrasPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "extra", mcVersion+"-"+mcpVersion));
|
||||
Path patchedBinariesPath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", type, mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE,"SRG MC at {} is {}", srgMcPath.toString(), pathStatus(srgMcPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"MC Extras at {} is {}", mcExtrasPath.toString(), pathStatus(mcExtrasPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"Forge patches at {} is {}", patchedBinariesPath.toString(), pathStatus(patchedBinariesPath));
|
||||
return new Path[] { patchedBinariesPath, mcExtrasPath, srgMcPath };
|
||||
}
|
||||
|
||||
public static Path findPathForMaven(final String group, final String artifact, final String extension, final String classifier, final String version) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(group, artifact, extension, classifier, version));
|
||||
}
|
||||
public static Path findPathForMaven(final String maven) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(maven));
|
||||
}
|
||||
}
|
||||
+13
-23
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
@@ -24,11 +10,18 @@ import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
|
||||
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 java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -41,10 +34,8 @@ public class LoadingModList
|
||||
private final List<ModFileInfo> modFiles;
|
||||
private final List<ModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
@SuppressWarnings("unused")
|
||||
private BackgroundScanHandler scanner;
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
private List<ModFile> brokenFiles;
|
||||
private List<IModFile> brokenFiles;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
@@ -94,7 +85,6 @@ public class LoadingModList
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
|
||||
{
|
||||
this.scanner = backgroundScanHandler;
|
||||
backgroundScanHandler.setLoadingModList(this);
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
@@ -171,11 +161,11 @@ public class LoadingModList
|
||||
return preLoadErrors;
|
||||
}
|
||||
|
||||
public void setBrokenFiles(final List<ModFile> brokenFiles) {
|
||||
public void setBrokenFiles(final List<IModFile> brokenFiles) {
|
||||
this.brokenFiles = brokenFiles;
|
||||
}
|
||||
|
||||
public List<ModFile> getBrokenFiles() {
|
||||
public List<IModFile> getBrokenFiles() {
|
||||
return this.brokenFiles;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 3309 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user