Skip to content

Commit a10dae7

Browse files
committed
First Commit (Pre-Alpha)
1 parent 33551c3 commit a10dae7

File tree

2 files changed

+348
-1
lines changed

2 files changed

+348
-1
lines changed

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
# sm64coopdx-mods-explorer
2-
More Informations soon
2+
This is a pre-alpha version of a Mod-Explorer for SM64COOPDX.
3+
More infos soon...
4+
5+
Credits:
6+
- eeliottheking (https://github.com/eeliottheking)
7+
- iZePlayz (https://github.com/iZePlayzYT)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package de.izeplayz;
2+
3+
import javax.swing.*;
4+
import java.awt.*;
5+
import java.awt.event.ActionEvent;
6+
import java.awt.event.ActionListener;
7+
import java.io.*;
8+
import java.net.HttpURLConnection;
9+
import java.net.URL;
10+
import java.nio.file.*;
11+
import java.util.List;
12+
import java.util.*;
13+
import java.util.concurrent.*;
14+
import java.util.regex.*;
15+
import java.util.stream.Collectors;
16+
17+
public class SM64CoopDXModsDumperGUI extends JFrame {
18+
19+
private static final String BASE_URL = "https://mods.sm64coopdx.com/mods/";
20+
private static final String PAGE_URL = BASE_URL + "?page=";
21+
private static final Pattern LINK_PATTERN = Pattern.compile("href=\"(/mods/[^/]+/)\"");
22+
private static final Pattern DOWNLOAD_LINK_PATTERN = Pattern.compile("href=\"(/mods/[^/]+/download\\?file=[^\"]+)\"");
23+
private static final int MAX_PAGES_WITHOUT_NEW_URLS = 3;
24+
private static final int MAX_THREADS = 10;
25+
private static final List<String> VALID_EXTENSIONS = Arrays.asList(".lua", ".rar", ".7z", ".zip");
26+
private Path downloadPath;
27+
28+
private JTextArea console;
29+
private JButton startButton;
30+
private JButton selectFolderButton;
31+
private JFileChooser folderChooser;
32+
33+
public SM64CoopDXModsDumperGUI() {
34+
setTitle("SM64CoopDX Mods Dumper");
35+
setSize(1280, 720);
36+
setDefaultCloseOperation(EXIT_ON_CLOSE);
37+
setLocationRelativeTo(null);
38+
setupUI();
39+
}
40+
41+
private void setupUI() {
42+
JPanel panel = new JPanel(new BorderLayout());
43+
44+
console = new JTextArea();
45+
console.setEditable(false);
46+
console.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
47+
JScrollPane scrollPane = new JScrollPane(console);
48+
panel.add(scrollPane, BorderLayout.CENTER);
49+
50+
JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
51+
selectFolderButton = new JButton("Select Output Folder");
52+
startButton = new JButton("Start Download");
53+
startButton.setEnabled(false);
54+
controlPanel.add(selectFolderButton);
55+
controlPanel.add(startButton);
56+
panel.add(controlPanel, BorderLayout.SOUTH);
57+
58+
add(panel);
59+
60+
folderChooser = new JFileChooser();
61+
folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
62+
63+
selectFolderButton.addActionListener(new ActionListener() {
64+
@Override
65+
public void actionPerformed(ActionEvent e) {
66+
int result = folderChooser.showOpenDialog(SM64CoopDXModsDumperGUI.this);
67+
if (result == JFileChooser.APPROVE_OPTION) {
68+
downloadPath = Paths.get(folderChooser.getSelectedFile().getAbsolutePath(), "mods-dump-" + System.currentTimeMillis());
69+
startButton.setEnabled(true);
70+
}
71+
}
72+
});
73+
74+
startButton.addActionListener(new ActionListener() {
75+
@Override
76+
public void actionPerformed(ActionEvent e) {
77+
startButton.setEnabled(false);
78+
selectFolderButton.setEnabled(false);
79+
new Thread(() -> {
80+
dumpMods();
81+
SwingUtilities.invokeLater(() -> {
82+
JOptionPane.showMessageDialog(SM64CoopDXModsDumperGUI.this, "Download completed!", "Done", JOptionPane.INFORMATION_MESSAGE);
83+
try {
84+
Desktop.getDesktop().open(downloadPath.toFile());
85+
} catch (IOException ioException) {
86+
ioException.printStackTrace();
87+
}
88+
System.exit(0);
89+
});
90+
}).start();
91+
}
92+
});
93+
}
94+
95+
private void dumpMods() {
96+
Set<String> modUrls = new HashSet<>();
97+
int currentPage = 0;
98+
int pagesWithoutNewUrls = 0;
99+
100+
while (pagesWithoutNewUrls < MAX_PAGES_WITHOUT_NEW_URLS) {
101+
String url = currentPage == 0 ? BASE_URL : PAGE_URL + currentPage;
102+
logToConsole("Scanning URL: " + url);
103+
104+
Set<String> newUrls = scanPage(url);
105+
if (newUrls.isEmpty() || modUrls.containsAll(newUrls)) {
106+
pagesWithoutNewUrls++;
107+
logToConsole("No new URLs on page " + currentPage + ". Pages without new URLs: " + pagesWithoutNewUrls);
108+
} else {
109+
pagesWithoutNewUrls = 0;
110+
modUrls.addAll(newUrls);
111+
logToConsole("Found " + newUrls.size() + " new URLs on page " + currentPage);
112+
}
113+
114+
logToConsole("Total URLs found: " + modUrls.size());
115+
currentPage++;
116+
}
117+
118+
saveUrlsToFile(modUrls);
119+
downloadMods(modUrls);
120+
}
121+
122+
private Set<String> scanPage(String urlString) {
123+
Set<String> urls = new HashSet<>();
124+
try {
125+
URL url = new URL(urlString);
126+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
127+
connection.setRequestMethod("GET");
128+
connection.connect();
129+
130+
if (connection.getResponseCode() == 200) {
131+
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
132+
String line;
133+
while ((line = reader.readLine()) != null) {
134+
Matcher matcher = LINK_PATTERN.matcher(line);
135+
while (matcher.find()) {
136+
String relativeUrl = matcher.group(1);
137+
String fullUrl = "https://mods.sm64coopdx.com" + relativeUrl;
138+
urls.add(fullUrl);
139+
}
140+
}
141+
reader.close();
142+
} else {
143+
logToConsole("Failed to connect to URL: " + urlString);
144+
}
145+
146+
connection.disconnect();
147+
} catch (IOException e) {
148+
logToConsole("Error scanning page: " + e.getMessage());
149+
}
150+
return urls;
151+
}
152+
153+
private void saveUrlsToFile(Set<String> urls) {
154+
try {
155+
Files.createDirectories(downloadPath);
156+
157+
String fileName = "mod-urls.txt";
158+
Path filePath = downloadPath.resolve(fileName);
159+
Files.write(filePath, urls.stream().sorted().collect(Collectors.toList()));
160+
logToConsole("URLs saved to file: " + filePath);
161+
} catch (IOException e) {
162+
logToConsole("Error saving URLs to file: " + e.getMessage());
163+
}
164+
}
165+
166+
private void downloadMods(Set<String> urls) {
167+
Path modsFolderPath = downloadPath.resolve("mods");
168+
169+
try {
170+
Files.createDirectories(modsFolderPath);
171+
} catch (IOException e) {
172+
logToConsole("Error creating mods directory: " + e.getMessage());
173+
return;
174+
}
175+
176+
ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
177+
Set<String> downloadedFiles = Collections.synchronizedSet(new HashSet<>());
178+
179+
List<Callable<Void>> tasks = new ArrayList<>();
180+
for (String url : urls) {
181+
tasks.add(() -> {
182+
processMod(url, modsFolderPath, downloadedFiles);
183+
return null;
184+
});
185+
}
186+
187+
try {
188+
List<Future<Void>> futures = executor.invokeAll(tasks);
189+
for (Future<Void> future : futures) {
190+
future.get();
191+
}
192+
} catch (InterruptedException | ExecutionException e) {
193+
logToConsole("Error during download: " + e.getMessage());
194+
} finally {
195+
executor.shutdown();
196+
}
197+
}
198+
199+
private void processMod(String url, Path modsFolderPath, Set<String> downloadedFiles) {
200+
try {
201+
List<String> downloadUrls = getDownloadUrls(url);
202+
203+
for (String downloadUrl : downloadUrls) {
204+
String fileName = getFileNameFromURL(downloadUrl);
205+
if (fileName == null) {
206+
logToConsole("Failed to get file name from URL: " + downloadUrl);
207+
continue;
208+
}
209+
210+
Path filePath = modsFolderPath.resolve(fileName);
211+
212+
if (downloadedFiles.contains(fileName)) {
213+
logToConsole("Duplicate file: " + fileName + " from URL: " + downloadUrl + ". Skipping.");
214+
return;
215+
}
216+
217+
if (isValidExtension(fileName)) {
218+
logToConsole("Downloading: " + fileName + " from URL: " + downloadUrl);
219+
try (InputStream in = new URL(downloadUrl).openStream()) {
220+
Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING);
221+
downloadedFiles.add(fileName);
222+
logToConsole("Downloaded: " + fileName);
223+
} catch (IOException e) {
224+
logToConsole("Failed to download: " + fileName + " from URL: " + downloadUrl + " (" + e.getMessage() + ")");
225+
}
226+
} else {
227+
logToConsole("File " + fileName + " does not have a valid extension. Checking sub versions.");
228+
List<String> subversionUrls = getDownloadUrls(url);
229+
for (String subversionUrl : subversionUrls) {
230+
String subversionFileName = getFileNameFromURL(subversionUrl);
231+
if (subversionFileName == null) {
232+
logToConsole("Failed to get file name from subversion URL: " + subversionUrl);
233+
continue;
234+
}
235+
236+
Path subversionFilePath = modsFolderPath.resolve(subversionFileName);
237+
238+
if (downloadedFiles.contains(subversionFileName)) {
239+
logToConsole("Duplicate subversion file: " + subversionFileName + " from URL: " + subversionUrl + ". Skipping.");
240+
continue;
241+
}
242+
243+
logToConsole("Downloading subversion: " + subversionFileName + " from URL: " + subversionUrl);
244+
try (InputStream in = new URL(subversionUrl).openStream()) {
245+
Files.copy(in, subversionFilePath, StandardCopyOption.REPLACE_EXISTING);
246+
downloadedFiles.add(subversionFileName);
247+
logToConsole("Downloaded subversion: " + subversionFileName);
248+
} catch (IOException e) {
249+
logToConsole("Failed to download subversion: " + subversionFileName + " from URL: " + subversionUrl + " (" + e.getMessage() + ")");
250+
}
251+
}
252+
}
253+
}
254+
} catch (IOException e) {
255+
logToConsole("Error processing mod at URL: " + url + " (" + e.getMessage() + ")");
256+
}
257+
}
258+
259+
private boolean isValidExtension(String fileName) {
260+
String lowerCaseFileName = fileName.toLowerCase();
261+
return VALID_EXTENSIONS.stream().anyMatch(lowerCaseFileName::endsWith);
262+
}
263+
264+
private List<String> getDownloadUrls(String urlString) throws IOException {
265+
List<String> downloadUrls = new ArrayList<>();
266+
URL url = new URL(urlString);
267+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
268+
connection.setRequestMethod("GET");
269+
connection.connect();
270+
271+
if (connection.getResponseCode() == 200) {
272+
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
273+
String line;
274+
boolean hasMultipleVersions = false;
275+
while ((line = reader.readLine()) != null) {
276+
if (line.contains("<h1 class=\"p-title-value\">Choose file…</h1>")) {
277+
hasMultipleVersions = true;
278+
break;
279+
}
280+
}
281+
282+
if (hasMultipleVersions) {
283+
while ((line = reader.readLine()) != null) {
284+
Matcher matcher = DOWNLOAD_LINK_PATTERN.matcher(line);
285+
while (matcher.find()) {
286+
String relativeUrl = matcher.group(1);
287+
String fullUrl = "https://mods.sm64coopdx.com" + relativeUrl;
288+
downloadUrls.add(fullUrl);
289+
}
290+
}
291+
} else {
292+
downloadUrls.add(urlString + "download");
293+
}
294+
295+
reader.close();
296+
} else {
297+
logToConsole("Failed to connect to URL: " + urlString);
298+
}
299+
300+
connection.disconnect();
301+
return downloadUrls;
302+
}
303+
304+
private String getFileNameFromURL(String urlString) {
305+
try {
306+
URL url = new URL(urlString);
307+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
308+
connection.setRequestMethod("GET");
309+
connection.connect();
310+
311+
if (connection.getResponseCode() == 200) {
312+
String contentDisposition = connection.getHeaderField("Content-Disposition");
313+
if (contentDisposition != null && contentDisposition.contains("filename=")) {
314+
return contentDisposition.split("filename=")[1].replace("\"", "").trim();
315+
} else {
316+
return urlString.substring(urlString.lastIndexOf('/') + 1);
317+
}
318+
} else {
319+
logToConsole("Failed to connect to URL: " + urlString);
320+
}
321+
322+
connection.disconnect();
323+
} catch (IOException e) {
324+
logToConsole("Error getting file name from URL: " + e.getMessage());
325+
}
326+
return null;
327+
}
328+
329+
private void logToConsole(String message) {
330+
SwingUtilities.invokeLater(() -> {
331+
console.append(message + "\n");
332+
console.setCaretPosition(console.getDocument().getLength());
333+
});
334+
}
335+
336+
public static void main(String[] args) {
337+
SwingUtilities.invokeLater(() -> {
338+
SM64CoopDXModsDumperGUI dumper = new SM64CoopDXModsDumperGUI();
339+
dumper.setVisible(true);
340+
});
341+
}
342+
}

0 commit comments

Comments
 (0)