/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. assumes: - list contains fbw/fbp command - list clears the screen - sceDisplaySetFrameBuf is called after the list has executed todo: - need to save GE state - texture on/off - blend on/off + params. - save matrices, looks like something wrong with projection - more ... - don't save the same piece of ram twice (multiple texture uploads of the same texture/clut) - capture multiple lists per frame, ideally we want to capture everything between two calls to sceDisplaySetFrameBuf */ package jpcsp.graphics.capture; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.Buffer; import java.util.HashSet; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.HLE.kernel.types.PspGeList; import jpcsp.graphics.VideoEngine; import org.apache.log4j.Level; public class CaptureManager { private static OutputStream out; public static boolean captureInProgress; private static boolean listExecuted; private static CaptureFrameBufDetails replayFrameBufDetails; private static Level logLevel; private static HashSet<Integer> capturedImages; public static void startReplay(String filename) { if (captureInProgress) { VideoEngine.log.error("Ignoring startReplay, capture is in progress"); return; } VideoEngine.log.info("Starting replay: " + filename); try { InputStream in = new BufferedInputStream(new FileInputStream(filename)); while (in.available() > 0) { CaptureHeader header = CaptureHeader.read(in); int packetType = header.getPacketType(); switch(packetType) { case CaptureHeader.PACKET_TYPE_LIST: CaptureList list = CaptureList.read(in); list.commit(); break; case CaptureHeader.PACKET_TYPE_RAM: CaptureRAM ramFragment = CaptureRAM.read(in); ramFragment.commit(); break; // deprecated case CaptureHeader.PACKET_TYPE_DISPLAY_DETAILS: CaptureDisplayDetails displayDetails = CaptureDisplayDetails.read(in); displayDetails.commit(); break; case CaptureHeader.PACKET_TYPE_FRAMEBUF_DETAILS: // don't replay this one immediately, wait until after the list has finished executing replayFrameBufDetails = CaptureFrameBufDetails.read(in); break; default: throw new Exception("Unknown packet type " + packetType); } } in.close(); } catch(Exception e) { VideoEngine.log.error("Failed to start replay: " + e.getMessage()); e.printStackTrace(); } } public static void endReplay() { // replay final sceDisplaySetFrameBuf replayFrameBufDetails.commit(); replayFrameBufDetails = null; VideoEngine.log.info("Replay completed"); Emulator.PauseEmu(); } public static void startCapture(String filename, PspGeList list) { //public static void startCapture(int displayBufferAddress, int displayBufferWidth, int displayBufferPsm, // int drawBufferAddress, int drawBufferWidth, int drawBufferPsm, // int depthBufferAddress, int depthBufferWidth) { if (captureInProgress) { VideoEngine.log.error("Ignoring startCapture, capture is already in progress"); return; } // Set the VideoEngine log level to TRACE when capturing, // the information in the log file is also interesting logLevel = VideoEngine.log.getLevel(); VideoEngine.getInstance().setLogLevel(Level.TRACE); capturedImages = new HashSet<Integer>(); try { VideoEngine.log.info("Starting capture... (list=" + list.id + ")"); out = new BufferedOutputStream(new FileOutputStream(filename)); CaptureHeader header; /* // write render target details header = new CaptureHeader(CaptureHeader.PACKET_TYPE_DISPLAY_DETAILS); header.write(out); CaptureDisplayDetails displayDetails = new CaptureDisplayDetails(); displayDetails.write(out); */ // write command buffer header = new CaptureHeader(CaptureHeader.PACKET_TYPE_LIST); header.write(out); CaptureList commandList = new CaptureList(list); commandList.write(out); captureInProgress = true; listExecuted = false; } catch(Exception e) { VideoEngine.log.error("Failed to start capture: " + e.getMessage()); e.printStackTrace(); Emulator.PauseEmu(); } } public static void endCapture() { if (!captureInProgress) { VideoEngine.log.warn("Ignoring endCapture, capture hasn't been started"); Emulator.PauseEmu(); return; } try { out.flush(); out.close(); out = null; } catch(Exception e) { VideoEngine.log.error("Failed to end capture: " + e.getMessage()); e.printStackTrace(); Emulator.PauseEmu(); } captureInProgress = false; VideoEngine.log.info("Capture completed"); VideoEngine.log.setLevel(logLevel); Emulator.PauseEmu(); } public static void captureRAM(int address, int length) { if (!captureInProgress) { VideoEngine.log.warn("Ignoring captureRAM, capture hasn't been started"); return; } if (!Memory.isAddressGood(address)) { return; } try { // write ram fragment CaptureHeader header = new CaptureHeader(CaptureHeader.PACKET_TYPE_RAM); header.write(out); CaptureRAM captureRAM = new CaptureRAM(address, length); captureRAM.write(out); } catch (Exception e) { VideoEngine.log.error("Failed to capture RAM: " + e.getMessage()); e.printStackTrace(); } } public static void captureImage(int imageaddr, int level, Buffer buffer, int width, int height, int bufferWidth, int imageType, boolean compressedImage, int compressedImageSize, boolean invert, boolean overwriteFile) { try { // write image to the file system, not to the capture file itself CaptureImage captureImage = new CaptureImage(imageaddr, level, buffer, width, height, bufferWidth, imageType, compressedImage, compressedImageSize, invert, overwriteFile, null); captureImage.write(); if (capturedImages != null) { capturedImages.add(imageaddr); } } catch (Exception e) { VideoEngine.log.error("Failed to capture Image: " + e.getMessage()); e.printStackTrace(); Emulator.PauseEmu(); } } public static boolean isImageCaptured(int imageaddr) { if (capturedImages == null) { return false; } return capturedImages.contains(imageaddr); } public static void captureFrameBufDetails() { if (!captureInProgress) { VideoEngine.log.warn("Ignoring captureRAM, capture hasn't been started"); return; } try { CaptureHeader header = new CaptureHeader(CaptureHeader.PACKET_TYPE_FRAMEBUF_DETAILS); header.write(out); CaptureFrameBufDetails details = new CaptureFrameBufDetails(); details.write(out); } catch(Exception e) { VideoEngine.log.error("Failed to capture frame buf details: " + e.getMessage()); e.printStackTrace(); Emulator.PauseEmu(); } } public static void markListExecuted() { listExecuted = true; } public static boolean hasListExecuted() { return listExecuted; } }