Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/Network-Play.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ Forge writes detailed network logs during online multiplayer games. These are se
| **Windows** | `%APPDATA%/Forge/networklogs/` |
| **macOS** | `~/Library/Application Support/Forge/networklogs/` |
| **Linux** | `~/.forge/networklogs/` |
| **Android** | `Android/data/forge.app/files/Forge/networklogs/` (typically not browsable without a file manager — use the in-app export below) |

On desktop, you can open this folder directly from the Forge game menu: **Online > Open Network Logs**.
On **Desktop**, you can open this folder directly from the Forge game menu: **Online > Open Network Logs**.

On **Mobile**, you can export logs in .zip file to your Downloads folder from **Settings > Files > Data Management > Export Network Logs**.

By default, logs from the last 10 games are kept; older logs are automatically removed.
48 changes: 48 additions & 0 deletions forge-gui-mobile/src/forge/screens/settings/FilesPage.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package forge.screens.settings;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
Expand Down Expand Up @@ -89,6 +93,26 @@ public void select() {
});
}
}, 0);
//Export Game Log
lstItems.addItem(new Extra(Forge.getLocalizer().getMessage("lblExportGameLog"), Forge.getLocalizer().getMessage("lblExportGameLogDescription")) {
@Override
public void select() {
exportLogs("lblExportGameLog",
new File(ForgeProfileProperties.getUserDir()),
(dir, name) -> name.startsWith("forge") && name.endsWith(".log"),
"forge-logs");
}
}, 0);
//Export Network Logs
lstItems.addItem(new Extra(Forge.getLocalizer().getMessage("lblExportNetworkLogs"), Forge.getLocalizer().getMessage("lblExportNetworkLogsDescription")) {
@Override
public void select() {
exportLogs("lblExportNetworkLogs",
new File(ForgeConstants.NETWORK_LOGS_DIR),
(dir, name) -> name.startsWith("network-debug-") && name.endsWith(".log"),
"forge-network-logs");
}
}, 0);
//Auditer
lstItems.addItem(new Extra(Forge.getLocalizer().getMessage("btnListImageData"), Forge.getLocalizer().getMessage("lblListImageData")) {
@Override
Expand Down Expand Up @@ -226,6 +250,30 @@ protected void doLayout(float width, float height) {
lstItems.setBounds(0, 0, width, height);
}

private void exportLogs(String dialogTitleKey, File sourceDir, FilenameFilter filter, String outputPrefix) {
if (Forge.getDeviceAdapter().needFileAccess()) {
Forge.getDeviceAdapter().requestFileAcces();
return;
}
final String dialogTitle = Forge.getLocalizer().getMessage(dialogTitleKey);
FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblExporting"), true, () -> {
try {
File[] matches = sourceDir.isDirectory() ? sourceDir.listFiles(filter) : null;
if (matches == null || matches.length == 0) {
FOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblNoLogFilesFound"), dialogTitle, FOptionPane.INFORMATION_ICON);
return;
}
String stamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
File downloads = new FileHandle(Forge.getDeviceAdapter().getDownloadsDir()).file();
File zipFile = new File(downloads, outputPrefix + "-" + stamp + ".zip");
ZipUtil.zipFiles(Arrays.asList(matches), zipFile);
FOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblSuccess") + "\n" + zipFile.getAbsolutePath(), dialogTitle, FOptionPane.INFORMATION_ICON);
} catch (IOException e) {
FOptionPane.showMessageDialog(e.toString(), Forge.getLocalizer().getMessage("lblError"), FOptionPane.ERROR_ICON);
}
}));
}

private abstract class FilesItem {
protected String label;
protected String description;
Expand Down
6 changes: 6 additions & 0 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3401,6 +3401,12 @@ lblPrepareDatabase=Preparing database...
lblLoadingGameResources=Loading game resources...
lblBackupRestore=Backup and Restore
lblBackupRestoreDescription=Backup or Restore Classic game mode data to/from Downloads folder
lblExportGameLog=Export Game Log
lblExportGameLogDescription=Export forge.log files to Downloads folder as a zip
lblExportNetworkLogs=Export Network Logs
lblExportNetworkLogsDescription=Export network debug logs to Downloads folder as a zip
lblExporting=Exporting...
lblNoLogFilesFound=No log files found
lblDataManagement=Data Management
lblPlsSelectActions=Please select options to perform action
lblBackupMsg=Backing up files
Expand Down
24 changes: 24 additions & 0 deletions forge-gui/src/main/java/forge/util/ZipUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
Expand All @@ -25,6 +26,29 @@ public static void zip(File source, File dest, String name) throws IOException {
}
}

/**
* Write the given files to a flat zip archive at {@code zipFile}. Each entry uses the
* source file's basename; no directory structure is preserved. Files that don't exist
* are skipped silently so callers don't have to pre-filter.
*/
public static void zipFiles(List<File> files, File zipFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zipOut = new ZipOutputStream(fos)) {
byte[] buffer = new byte[1024];
for (File file : files) {
if (file == null || !file.isFile()) continue;
try (FileInputStream fis = new FileInputStream(file)) {
zipOut.putNextEntry(new ZipEntry(file.getName()));
int length;
while ((length = fis.read(buffer)) >= 0) {
zipOut.write(buffer, 0, length);
}
zipOut.closeEntry();
}
}
}
}

private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
if (fileToZip.isHidden()) {
return;
Expand Down
Loading