Skip to content

Commit b6d9699

Browse files
committed
feat(detector): support sponge detection
1 parent afb846b commit b6d9699

4 files changed

Lines changed: 519 additions & 0 deletions

File tree

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package detector
2+
3+
import (
4+
"archive/zip"
5+
"bufio"
6+
"os"
7+
"regexp"
8+
"strings"
9+
10+
"github.com/mclucy/lucy/input"
11+
"github.com/mclucy/lucy/types"
12+
)
13+
14+
var (
15+
spongeVanillaVersionPattern = regexp.MustCompile(
16+
`^(\d+\.\d+(?:\.\d+)?)-(\d+(?:\.\d+)+)$`,
17+
)
18+
spongeHybridVersionPattern = regexp.MustCompile(
19+
`^(\d+\.\d+(?:\.\d+)?)-(\d+(?:\.\d+)+)-(\d+(?:\.\d+)+)$`,
20+
)
21+
spongeUniversalJarPattern = regexp.MustCompile(
22+
`(?i)^sponge(?:vanilla|forge|neo)-.+\.jar$`,
23+
)
24+
)
25+
26+
type spongeFlavor int
27+
28+
const (
29+
spongeFlavorUnknown spongeFlavor = iota
30+
spongeFlavorVanilla
31+
spongeFlavorForge
32+
spongeFlavorNeo
33+
)
34+
35+
type spongeServerDetector struct{}
36+
37+
func (d *spongeServerDetector) Name() string {
38+
return "sponge server"
39+
}
40+
41+
func (d *spongeServerDetector) Detect(
42+
filePath string,
43+
zipReader *zip.Reader,
44+
fileHandle *os.File,
45+
) (*ExecutableEvidence, error) {
46+
_ = fileHandle
47+
48+
if !spongeUniversalJarPattern.MatchString(strings.ToLower(zipBaseName(filePath))) {
49+
return nil, nil
50+
}
51+
52+
manifest, ok, err := readArchiveEntry(zipReader, "META-INF/MANIFEST.MF")
53+
if err != nil {
54+
return nil, err
55+
}
56+
if !ok {
57+
return nil, nil
58+
}
59+
60+
signals := parseSpongeManifest(manifest)
61+
if !signals.valid() {
62+
return nil, nil
63+
}
64+
65+
return buildSpongeExecutableEvidence(filePath, signals), nil
66+
}
67+
68+
type spongeManifestSignals struct {
69+
title string
70+
vendor string
71+
version string
72+
launchTarget string
73+
flavor spongeFlavor
74+
gameVersion types.BareVersion
75+
loaderVersion types.BareVersion
76+
spongeVersion types.BareVersion
77+
}
78+
79+
func (s spongeManifestSignals) valid() bool {
80+
if !strings.EqualFold(strings.TrimSpace(s.vendor), "SpongePowered") {
81+
return false
82+
}
83+
if s.flavor == spongeFlavorUnknown {
84+
return false
85+
}
86+
if !hasConcreteVersion(s.gameVersion) {
87+
return false
88+
}
89+
if !hasConcreteVersion(s.spongeVersion) {
90+
return false
91+
}
92+
if s.flavor != spongeFlavorVanilla && !hasConcreteVersion(s.loaderVersion) {
93+
return false
94+
}
95+
96+
return true
97+
}
98+
99+
func parseSpongeManifest(data []byte) spongeManifestSignals {
100+
var signals spongeManifestSignals
101+
scanner := bufio.NewScanner(strings.NewReader(string(data)))
102+
inNamedSection := false
103+
for scanner.Scan() {
104+
line := scanner.Text()
105+
if strings.HasPrefix(line, "Name: ") {
106+
inNamedSection = true
107+
continue
108+
}
109+
if strings.TrimSpace(line) == "" {
110+
inNamedSection = false
111+
continue
112+
}
113+
if inNamedSection {
114+
continue
115+
}
116+
117+
switch {
118+
case strings.HasPrefix(line, "Specification-Title: "):
119+
signals.title = strings.TrimSpace(
120+
strings.TrimPrefix(line, "Specification-Title: "),
121+
)
122+
case strings.HasPrefix(line, "Specification-Vendor: "):
123+
if signals.vendor == "" {
124+
signals.vendor = strings.TrimSpace(
125+
strings.TrimPrefix(line, "Specification-Vendor: "),
126+
)
127+
}
128+
case strings.HasPrefix(line, "Implementation-Title: "):
129+
if signals.title == "" {
130+
signals.title = strings.TrimSpace(
131+
strings.TrimPrefix(line, "Implementation-Title: "),
132+
)
133+
}
134+
case strings.HasPrefix(line, "Implementation-Vendor: "):
135+
if signals.vendor == "" {
136+
signals.vendor = strings.TrimSpace(
137+
strings.TrimPrefix(line, "Implementation-Vendor: "),
138+
)
139+
}
140+
case strings.HasPrefix(line, "Implementation-Version: "):
141+
if signals.version == "" {
142+
signals.version = strings.TrimSpace(
143+
strings.TrimPrefix(line, "Implementation-Version: "),
144+
)
145+
}
146+
case strings.HasPrefix(line, "Launch-Target: "):
147+
signals.launchTarget = strings.TrimSpace(
148+
strings.TrimPrefix(line, "Launch-Target: "),
149+
)
150+
}
151+
}
152+
153+
signals.flavor, signals.gameVersion, signals.loaderVersion, signals.spongeVersion = parseSpongeImplementationVersion(signals.title, signals.version)
154+
155+
return signals
156+
}
157+
158+
func parseSpongeImplementationVersion(
159+
title string,
160+
version string,
161+
) (
162+
flavor spongeFlavor,
163+
gameVersion types.BareVersion,
164+
loaderVersion types.BareVersion,
165+
spongeVersion types.BareVersion,
166+
) {
167+
version = strings.TrimSpace(version)
168+
switch strings.TrimSpace(title) {
169+
case "SpongeVanilla":
170+
match := spongeVanillaVersionPattern.FindStringSubmatch(version)
171+
if match == nil {
172+
return spongeFlavorUnknown, types.VersionUnknown, types.VersionUnknown, types.VersionUnknown
173+
}
174+
return spongeFlavorVanilla,
175+
types.BareVersion(match[1]),
176+
types.VersionUnknown,
177+
types.BareVersion(match[2])
178+
case "SpongeForge":
179+
match := spongeHybridVersionPattern.FindStringSubmatch(version)
180+
if match == nil {
181+
return spongeFlavorUnknown, types.VersionUnknown, types.VersionUnknown, types.VersionUnknown
182+
}
183+
return spongeFlavorForge,
184+
types.BareVersion(match[1]),
185+
types.BareVersion(match[2]),
186+
types.BareVersion(match[3])
187+
case "SpongeNeo":
188+
match := spongeHybridVersionPattern.FindStringSubmatch(version)
189+
if match == nil {
190+
return spongeFlavorUnknown, types.VersionUnknown, types.VersionUnknown, types.VersionUnknown
191+
}
192+
return spongeFlavorNeo,
193+
types.BareVersion(match[1]),
194+
types.BareVersion(match[2]),
195+
types.BareVersion(match[3])
196+
default:
197+
return spongeFlavorUnknown, types.VersionUnknown, types.VersionUnknown, types.VersionUnknown
198+
}
199+
}
200+
201+
func buildSpongeExecutableEvidence(
202+
filePath string,
203+
signals spongeManifestSignals,
204+
) *ExecutableEvidence {
205+
identities := []types.VersionedPackageRef{
206+
{
207+
PackageRef: types.PackageRef{
208+
Platform: types.PlatformSponge,
209+
Name: input.ToProjectName("sponge"),
210+
},
211+
Version: signals.spongeVersion,
212+
},
213+
{
214+
PackageRef: types.PackageRef{
215+
Platform: types.PlatformMinecraft,
216+
Name: input.ToProjectName("minecraft"),
217+
},
218+
Version: signals.gameVersion,
219+
},
220+
}
221+
222+
spongeNode := types.RuntimeNode{
223+
ID: types.RuntimeNodeSponge,
224+
Role: types.RuntimeRolePluginCore,
225+
Capabilities: []types.RuntimeCapability{
226+
types.CapabilitySpongePlugins,
227+
},
228+
}
229+
230+
nodes := []types.RuntimeNode{spongeNode}
231+
232+
switch signals.flavor {
233+
case spongeFlavorForge:
234+
spongeNode.Role = types.RuntimeRoleHybrid
235+
spongeNode.Capabilities = append(
236+
[]types.RuntimeCapability{types.CapabilitySpongePlugins},
237+
types.CapabilityForgeMods.Populate()...,
238+
)
239+
nodes[0] = spongeNode
240+
identities = append(identities, types.VersionedPackageRef{
241+
PackageRef: types.PackageRef{
242+
Platform: types.PlatformForge,
243+
Name: input.ToProjectName("forge"),
244+
},
245+
Version: signals.loaderVersion,
246+
})
247+
case spongeFlavorNeo:
248+
spongeNode.Role = types.RuntimeRoleHybrid
249+
spongeNode.Capabilities = append(
250+
[]types.RuntimeCapability{types.CapabilitySpongePlugins},
251+
types.CapabilityNeoforgeMods.Populate()...,
252+
)
253+
nodes[0] = spongeNode
254+
identities = append(identities, types.VersionedPackageRef{
255+
PackageRef: types.PackageRef{
256+
Platform: types.PlatformNeoforge,
257+
Name: input.ToProjectName("neoforge"),
258+
},
259+
Version: signals.loaderVersion,
260+
})
261+
}
262+
263+
return &ExecutableEvidence{
264+
PrimaryEntrance: filePath,
265+
GameVersion: signals.gameVersion,
266+
RuntimeIdentities: identities,
267+
Topology: &types.RuntimeTopology{
268+
PrimaryNode: types.RuntimeNodeSponge,
269+
Nodes: nodes,
270+
},
271+
}
272+
}
273+
274+
func zipBaseName(filePath string) string {
275+
base := filePath
276+
if i := strings.LastIndexAny(base, `/\`); i >= 0 {
277+
base = base[i+1:]
278+
}
279+
return base
280+
}
281+
282+
func init() {
283+
registerExecutableDetector(&spongeServerDetector{})
284+
}

0 commit comments

Comments
 (0)