package com.grapeshot.halfnes.ui; import com.grapeshot.halfnes.FileUtils; import static com.grapeshot.halfnes.utils.BIT8; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.ListView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; /** * @author Stephen Chin - [email protected] */ public class OnScreenMenu extends StackPane { private GUIInterface gui; private ListView<MenuAction> menu; private ListView<MenuAction> gameMenu; private final ObservableList<MenuAction> menuItems = FXCollections.<MenuAction>observableArrayList( new MenuAction("Resume", this::resume), new MenuAction("Load Game", this::loadGame), new MenuAction("Reset", this::reset), new MenuAction("Exit", this::exit), new MenuAction("Power Off", this::powerOff)); private final ObservableList<MenuAction> games = FXCollections.<MenuAction>observableArrayList( new MenuAction("Back", () -> gameMenu.setVisible(false))); public OnScreenMenu(GUIInterface gui) { this.gui = gui; menu = new ListView<>(menuItems); gameMenu = new ListView(games); addMenuListeners(menu); addMenuListeners(gameMenu); getChildren().addAll(menu, gameMenu); gameMenu.setVisible(false); setVisible(false); } private void addMenuListeners(ListView<MenuAction> menu) { menu.addEventHandler(javafx.scene.input.KeyEvent.KEY_RELEASED, e -> { if (e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)) { menu.getSelectionModel().getSelectedItem().run(); } }); menu.addEventHandler(javafx.scene.input.MouseEvent.MOUSE_CLICKED, e -> { if (e.getClickCount() == 2) { menu.getSelectionModel().getSelectedItem().run(); } }); } public void show() { gui.getNes().pause(); setVisible(true); } private void hide() { setVisible(false); } public void loadROMs(String path) { if (path.toLowerCase().endsWith(".zip")) { try { loadRomFromZip(path); } catch (IOException ex) { gui.messageBox("Could not load file:\nFile does not exist or is not a valid NES game.\n" + ex.getMessage()); } } else { games.add(new GameAction(new File(path))); runGame(path); } } private void loadRomFromZip(String zipName) throws IOException { listRomsInZip(zipName).stream().map(romName -> new GameAction(zipName, romName)).forEach(games::add); if (games.size() == 2) { games.get(1).run(); } else if (games.size() > 2) { Platform.runLater(() -> loadGame()); } } private List<String> listRomsInZip(String zipName) throws IOException { final List<String> romNames; try (ZipFile zipFile = new ZipFile(zipName)) { final Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); romNames = new ArrayList<>(); while (zipEntries.hasMoreElements()) { final ZipEntry entry = zipEntries.nextElement(); if (!entry.isDirectory() && (entry.getName().endsWith(".nes") || entry.getName().endsWith(".fds") || entry.getName().endsWith(".nsf"))) { romNames.add(entry.getName()); } } } if (romNames.isEmpty()) { throw new IOException("No NES games found in ZIP file."); } return romNames; } private File extractRomFromZip(String zipName, String romName) throws IOException { final File outputFile; final FileOutputStream fos; try (ZipInputStream zipStream = new ZipInputStream(new FileInputStream(zipName))) { ZipEntry entry; do { entry = zipStream.getNextEntry(); } while ((entry != null) && (!entry.getName().equals(romName))); if (entry == null) { zipStream.close(); throw new IOException("Cannot find file " + romName + " inside archive " + zipName); } //name temp. extracted file after parent zip and file inside //note: here's the bug, when it saves the temp file if it's in a folder //in the zip it's trying to put it in the same folder outside the zip outputFile = new File(new File(zipName).getCanonicalFile().getParent() + File.separator + FileUtils.stripExtension(new File(zipName).getName()) + " - " + romName); if (outputFile.exists() && !outputFile.delete()) { gui.messageBox("Cannot extract file. File " + outputFile.getCanonicalPath() + " already exists."); zipStream.close(); return null; } final byte[] buf = new byte[4096]; fos = new FileOutputStream(outputFile); int numBytes; while ((numBytes = zipStream.read(buf, 0, buf.length)) != -1) { fos.write(buf, 0, numBytes); } } fos.close(); return outputFile; } private void resume() { gui.getNes().resume(); hide(); } private void loadGame() { gameMenu.setVisible(true); gameMenu.requestFocus(); } private void runGame(String path) { gui.getNes().loadROM(path); Platform.runLater(() -> { gameMenu.setVisible(false); hide(); }); } private void reset() { gui.getNes().reset(); hide(); } private void exit() { gui.getNes().quit(); Platform.exit(); } private void powerOff() { // try { // Runtime.getRuntime().exec("sudo shutdown -h now"); // } catch (IOException ex) { // Logger.getLogger(OnScreenMenu.class.getName()).log(Level.SEVERE, null, ex); // } } class MenuAction { String name; Runnable action; MenuAction() { } MenuAction(String name, Runnable action) { this.name = name; this.action = action; } public void run() { action.run(); } @Override public String toString() { return name; } } class GameAction extends MenuAction { GameAction(File game) { name = game.getName(); action = () -> { try { gui.getNes().loadROM(game.getCanonicalPath()); Platform.runLater(() -> { gameMenu.setVisible(false); menu.setVisible(false); }); } catch (IOException e) { gui.messageBox(e.getMessage()); } }; } GameAction(final String zipName, final String romName) { if (romName.toLowerCase().endsWith(".nes")) { name = romName.substring(0, romName.length() - 4); } else { name = romName; } action = () -> { try { final File extractedFile = extractRomFromZip(zipName, romName); if (extractedFile != null) { extractedFile.deleteOnExit(); } runGame(extractedFile.getCanonicalPath()); } catch (IOException e) { gui.messageBox(e.getMessage()); } }; } } }