/* 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/>. */ package jpcsp.HLE.modules; import static jpcsp.HLE.modules.sceNetAdhocctl.fillNextPointersInLinkedList; import static jpcsp.util.Utilities.writeBytes; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.LinkedList; import jpcsp.HLE.BufferInfo; import jpcsp.HLE.BufferInfo.LengthInfo; import jpcsp.HLE.BufferInfo.Usage; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.CheckArgument; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer16; import jpcsp.HLE.TPointer32; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.HLE.kernel.managers.SceUidManager; import jpcsp.HLE.kernel.types.IAction; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.pspAbstractMemoryMappedStructure; import jpcsp.HLE.kernel.types.pspAbstractMemoryMappedStructureVariableLength; import jpcsp.HLE.kernel.types.pspNetMacAddress; import jpcsp.HLE.Modules; import jpcsp.hardware.Wlan; import jpcsp.memory.IMemoryReader; import jpcsp.memory.MemoryReader; import jpcsp.network.INetworkAdapter; import jpcsp.network.adhoc.AdhocMessage; import jpcsp.network.adhoc.PdpObject; import jpcsp.network.adhoc.PtpObject; import jpcsp.network.upnp.AutoDetectJpcsp; import jpcsp.scheduler.Scheduler; import jpcsp.settings.AbstractBoolSettingsListener; import jpcsp.util.Utilities; import org.apache.log4j.Logger; public class sceNetAdhoc extends HLEModule { public static Logger log = Modules.getLogger("sceNetAdhoc"); // For test purpose when running 2 different Jpcsp instances on the same computer: // one computer has to have netClientPortShift=0 and netServerPortShift=100, // the other computer, netClientPortShift=100 and netServerPortShift=0. private int netClientPortShift = 0; private int netServerPortShift = 0; // Period to update the Game Mode protected static final int GAME_MODE_UPDATE_MICROS = 12000; protected static final int PSP_ADHOC_POLL_READY_TO_SEND = 1; protected static final int PSP_ADHOC_POLL_DATA_AVAILABLE = 2; protected static final int PSP_ADHOC_POLL_CAN_CONNECT = 4; protected static final int PSP_ADHOC_POLL_CAN_ACCEPT = 8; protected HashMap<Integer, PdpObject> pdpObjects; protected HashMap<Integer, PtpObject> ptpObjects; private int currentFreePort; public static final byte[] ANY_MAC_ADDRESS = new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; private GameModeScheduledAction gameModeScheduledAction; protected GameModeArea masterGameModeArea; protected LinkedList<GameModeArea> replicaGameModeAreas; private static final String replicaIdPurpose = "sceNetAdhoc-Replica"; private static final int adhocGameModePort = 31000; private DatagramSocket gameModeSocket; private boolean isInitialized; private class ClientPortShiftSettingsListener extends AbstractBoolSettingsListener { @Override protected void settingsValueChanged(boolean value) { if (value) { setNetClientPortShift(100); } else { setNetClientPortShift(0); } } } private class ServerPortShiftSettingsListener extends AbstractBoolSettingsListener { @Override protected void settingsValueChanged(boolean value) { if (value) { setNetServerPortShift(100); } else { setNetServerPortShift(0); } } } protected static class GameModeScheduledAction implements IAction { private final int scheduleRepeatMicros; private long nextSchedule; public GameModeScheduledAction(int scheduleRepeatMicros) { this.scheduleRepeatMicros = scheduleRepeatMicros; } public void stop() { Scheduler.getInstance().removeAction(nextSchedule, this); } public void start() { Scheduler.getInstance().addAction(this); } @Override public void execute() { Modules.sceNetAdhocModule.hleGameModeUpdate(); nextSchedule = Scheduler.getNow() + scheduleRepeatMicros; Scheduler.getInstance().addAction(nextSchedule, this); } } public static class GameModeArea { public pspNetMacAddress macAddress; public int addr; public int size; public int id; private byte[] newData; private long updateTimestamp; public GameModeArea(int addr, int size) { this.addr = addr; this.size = size; id = -1; } public GameModeArea(pspNetMacAddress macAddress, int addr, int size) { this.macAddress = macAddress; this.addr = addr; this.size = size; id = SceUidManager.getNewUid(replicaIdPurpose); } public void delete() { if (id >= 0) { SceUidManager.releaseUid(id, replicaIdPurpose); id = -1; } } public void setNewData(byte[] newData) { updateTimestamp = Emulator.getClock().microTime(); this.newData = newData; } public void setNewData() { byte[] data = new byte[size]; IMemoryReader memoryReader = MemoryReader.getMemoryReader(addr, size, 1); for (int i = 0; i < data.length; i++) { data[i] = (byte) memoryReader.readNext(); } setNewData(data); } public void resetNewData() { newData = null; } public byte[] getNewData() { return newData; } public boolean hasNewData() { return newData != null; } public void writeNewData() { if (newData != null) { writeBytes(addr, Math.min(size, newData.length), newData, 0); } } public long getUpdateTimestamp() { return updateTimestamp; } @Override public String toString() { if (macAddress == null) { return String.format("Master GameModeArea addr=0x%08X, size=%d", addr, size); } return String.format("Replica GameModeArea id=%d, macAddress=%s, addr=0x%08X, size=%d", id, macAddress, addr, size); } } protected static String getPollEventName(int event) { return String.format("Unknown 0x%X", event); } protected static class pspAdhocPollId extends pspAbstractMemoryMappedStructure { public int id; public int events; public int revents; @Override protected void read() { id = read32(); events = read32(); revents = read32(); } @Override protected void write() { write32(id); write32(events); write32(revents); } @Override public int sizeof() { return 12; } @Override public String toString() { return String.format("PollId[id=%d, events=0x%X(%s), revents=0x%X(%s)]", id, events, getPollEventName(events), revents, getPollEventName(revents)); } } protected static class GameModeUpdateInfo extends pspAbstractMemoryMappedStructureVariableLength { public int updated; public long timeStamp; @Override protected void read() { super.read(); updated = read32(); timeStamp = read64(); } @Override protected void write() { super.write(); write32(updated); write64(timeStamp); } } @Override public void start() { setSettingsListener("emu.netClientPortShift", new ClientPortShiftSettingsListener()); setSettingsListener("emu.netServerPortShift", new ServerPortShiftSettingsListener()); AutoDetectJpcsp autoDetectJpcsp = AutoDetectJpcsp.getInstance(); if (autoDetectJpcsp != null) { autoDetectJpcsp.discoverOtherJpcspInBackground(); } pdpObjects = new HashMap<Integer, PdpObject>(); ptpObjects = new HashMap<Integer, PtpObject>(); currentFreePort = 0x4000; replicaGameModeAreas = new LinkedList<sceNetAdhoc.GameModeArea>(); isInitialized = false; super.start(); } public void setNetClientPortShift(int netClientPortShift) { this.netClientPortShift = netClientPortShift; log.info(String.format("Using netClientPortShift=%d", netClientPortShift)); } public void setNetServerPortShift(int netServerPortShift) { this.netServerPortShift = netServerPortShift; log.info(String.format("Using netServerPortShift=%d", netServerPortShift)); } public int getClientPortFromRealPort(byte[] clientMacAddress, int realPort) { if (isMyMacAddress(clientMacAddress)) { // if the client is my-self, then this is actually a server port... return getServerPortFromRealPort(realPort); } return realPort - netClientPortShift; } public int getRealPortFromClientPort(byte[] clientMacAddress, int clientPort) { if (isMyMacAddress(clientMacAddress)) { // if the client is my-self, then this is actually a server port... return getRealPortFromServerPort(clientPort); } return clientPort + netClientPortShift; } public int getServerPortFromRealPort(int realPort) { return realPort - netServerPortShift; } public int getRealPortFromServerPort(int serverPort) { return serverPort + netServerPortShift; } public boolean hasNetPortShiftActive() { return netServerPortShift > 0 || netClientPortShift > 0; } protected void checkInitialized() { if (!isInitialized) { throw new SceKernelErrorException(SceKernelErrors.ERROR_NET_ADHOC_NOT_INITIALIZED); } } public void hleExitGameMode() { masterGameModeArea = null; replicaGameModeAreas.clear(); stopGameMode(); } public void hleGameModeUpdate() { if (log.isDebugEnabled()) { log.debug(String.format("hleGameModeUpdate")); } try { if (gameModeSocket == null) { gameModeSocket = new DatagramSocket(Modules.sceNetAdhocModule.getRealPortFromServerPort(adhocGameModePort)); // For broadcast gameModeSocket.setBroadcast(true); // Non-blocking (timeout = 0 would mean blocking) gameModeSocket.setSoTimeout(1); } // Send master area if (masterGameModeArea != null && masterGameModeArea.hasNewData()) { try { AdhocMessage adhocGameModeMessage = getNetworkAdapter().createAdhocGameModeMessage(masterGameModeArea); SocketAddress socketAddress[] = Modules.sceNetAdhocModule.getMultiSocketAddress(sceNetAdhoc.ANY_MAC_ADDRESS, Modules.sceNetAdhocModule.getRealPortFromClientPort(sceNetAdhoc.ANY_MAC_ADDRESS, adhocGameModePort)); for (int i = 0; i < socketAddress.length; i++) { DatagramPacket packet = new DatagramPacket(adhocGameModeMessage.getMessage(), adhocGameModeMessage.getMessageLength(), socketAddress[i]); gameModeSocket.send(packet); if (log.isDebugEnabled()) { log.debug(String.format("GameMode message sent to %s: %s", socketAddress[i], adhocGameModeMessage)); } } } catch (SocketTimeoutException e) { // Ignore exception } } // Receive all waiting messages do { try { byte[] bytes = new byte[10000]; DatagramPacket packet = new DatagramPacket(bytes, bytes.length); gameModeSocket.receive(packet); AdhocMessage adhocGameModeMessage = getNetworkAdapter().createAdhocGameModeMessage(packet.getData(), packet.getLength()); if (log.isDebugEnabled()) { log.debug(String.format("GameMode received: %s", adhocGameModeMessage)); } for (GameModeArea gameModeArea : replicaGameModeAreas) { if (isSameMacAddress(gameModeArea.macAddress.macAddress, adhocGameModeMessage.getFromMacAddress())) { if (log.isDebugEnabled()) { log.debug(String.format("Received new Data for GameMode Area %s", gameModeArea)); } gameModeArea.setNewData(adhocGameModeMessage.getData()); break; } } } catch (SocketTimeoutException e) { // No more messages available break; } } while (true); } catch (IOException e) { log.error("hleGameModeUpdate", e); } } protected void startGameMode() { if (gameModeScheduledAction == null) { if (log.isDebugEnabled()) { log.debug(String.format("Starting GameMode")); } gameModeScheduledAction = new GameModeScheduledAction(GAME_MODE_UPDATE_MICROS); gameModeScheduledAction.start(); } } protected void stopGameMode() { if (gameModeScheduledAction != null) { if (log.isDebugEnabled()) { log.debug(String.format("Stopping GameMode")); } gameModeScheduledAction.stop(); gameModeScheduledAction = null; } if (gameModeSocket != null) { gameModeSocket.close(); gameModeSocket = null; } } public SocketAddress getSocketAddress(byte[] macAddress, int realPort) throws UnknownHostException { return getNetworkAdapter().getSocketAddress(macAddress, realPort); } public SocketAddress[] getMultiSocketAddress(byte[] macAddress, int realPort) throws UnknownHostException { return getNetworkAdapter().getMultiSocketAddress(macAddress, realPort); } public static boolean isSameMacAddress(byte[] macAddress1, byte[] macAddress2) { if (macAddress1.length != macAddress2.length) { return false; } for (int i = 0; i < macAddress1.length; i++) { if (macAddress1[i] != macAddress2[i]) { return false; } } return true; } public static boolean isAnyMacAddress(byte[] macAddress) { return isSameMacAddress(macAddress, ANY_MAC_ADDRESS); } public static boolean isMyMacAddress(byte[] macAddress) { return isSameMacAddress(Wlan.getMacAddress(), macAddress); } private int getFreePort() { int freePort = currentFreePort; if (netClientPortShift > 0 || netServerPortShift > 0) { currentFreePort += 2; } else { currentFreePort++; } if (currentFreePort > 0x7FFF) { currentFreePort -= 0x4000; } return freePort; } public int checkPdpId(int pdpId) { checkInitialized(); if (!pdpObjects.containsKey(pdpId)) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid Pdp Id=%d", pdpId)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_NET_ADHOC_INVALID_SOCKET_ID); } return pdpId; } public int checkPtpId(int ptpId) { checkInitialized(); if (!ptpObjects.containsKey(ptpId)) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid Ptp Id=%d", ptpId)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_NET_ADHOC_INVALID_SOCKET_ID); } return ptpId; } public void hleAddPtpObject(PtpObject ptpObject) { ptpObjects.put(ptpObject.getId(), ptpObject); } protected INetworkAdapter getNetworkAdapter() { return Modules.sceNetModule.getNetworkAdapter(); } /** * Initialize the adhoc library. * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xE1D621D7, version = 150, checkInsideInterrupt = true) public int sceNetAdhocInit() { log.info(String.format("sceNetAdhocInit: using MAC address=%s, nick name='%s'", sceNet.convertMacAddressToString(Wlan.getMacAddress()), sceUtility.getSystemParamNickname())); if (isInitialized) { return SceKernelErrors.ERROR_NET_ADHOC_ALREADY_INITIALIZED; } isInitialized = true; return 0; } /** * Terminate the adhoc library * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xA62C6F57, version = 150, checkInsideInterrupt = true) public int sceNetAdhocTerm() { isInitialized = false; return 0; } @HLEFunction(nid = 0x7A662D6B, version = 150) public int sceNetAdhocPollSocket(TPointer socketsAddr, int count, int timeout, int nonblock) { checkInitialized(); Memory mem = Memory.getInstance(); int countEvents = 0; for (int i = 0; i < count; i++) { pspAdhocPollId pollId = new pspAdhocPollId(); pollId.read(mem, socketsAddr.getAddress() + i * pollId.sizeof()); PdpObject pdpObject = pdpObjects.get(pollId.id); PtpObject ptpObject = null; if (pdpObject == null) { ptpObject = ptpObjects.get(pollId.id); pdpObject = ptpObject; } if (pdpObject != null) { try { pdpObject.update(); } catch (IOException e) { // Ignore exception } } pollId.revents = 0; if ((pollId.events & PSP_ADHOC_POLL_DATA_AVAILABLE) != 0 && pdpObject.getRcvdData() > 0) { pollId.revents |= PSP_ADHOC_POLL_DATA_AVAILABLE; } if ((pollId.events & PSP_ADHOC_POLL_READY_TO_SEND) != 0) { // Data can always be sent pollId.revents |= PSP_ADHOC_POLL_READY_TO_SEND; } if ((pollId.events & PSP_ADHOC_POLL_CAN_CONNECT) != 0) { if (ptpObject != null && ptpObject.canConnect()) { pollId.revents |= PSP_ADHOC_POLL_CAN_CONNECT; } } if ((pollId.events & PSP_ADHOC_POLL_CAN_ACCEPT) != 0) { if (ptpObject != null && ptpObject.canAccept()) { pollId.revents |= PSP_ADHOC_POLL_CAN_ACCEPT; } } if (pollId.revents != 0) { countEvents++; } pollId.write(mem); log.info(String.format("sceNetAdhocPollSocket pollId[%d]=%s", i, pollId)); } return countEvents; } @HLEUnimplemented @HLEFunction(nid = 0x73BFD52D, version = 150) public int sceNetAdhocSetSocketAlert(int id, int flags) { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x4D2CE199, version = 150) public int sceNetAdhocGetSocketAlert() { return 0; } /** * Create a PDP object. * * @param mac - Your MAC address (from sceWlanGetEtherAddr) * @param port - Port to use, lumines uses 0x309 * @param bufsize - Socket buffer size, lumines sets to 0x400 * @param unk1 - Unknown, lumines sets to 0 * * @return The ID of the PDP object (< 0 on error) */ @HLEFunction(nid = 0x6F92741B, version = 150) public int sceNetAdhocPdpCreate(pspNetMacAddress macAddress, int port, int bufSize, int unk1) { checkInitialized(); if (port == 0) { // Allocate a free port port = getFreePort(); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocPdpCreate: using free port %d", port)); } } PdpObject pdpObject = getNetworkAdapter().createPdpObject(); int result = pdpObject.create(macAddress, port, bufSize); if (result == pdpObject.getId()) { pdpObjects.put(pdpObject.getId(), pdpObject); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocPdpCreate: returning id=0x%X", result)); } } else { if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocPdpCreate: returning error=0x%08X", result)); } } return result; } /** * Send a PDP packet to a destination * * @param id - The ID as returned by ::sceNetAdhocPdpCreate * @param destMacAddr - The destination MAC address, can be set to all 0xFF for broadcast * @param port - The port to send to * @param data - The data to send * @param len - The length of the data. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return Bytes sent, < 0 on error */ @HLEFunction(nid = 0xABED3790, version = 150) public int sceNetAdhocPdpSend(@CheckArgument("checkPdpId") int id, pspNetMacAddress destMacAddress, int port, @BufferInfo(lengthInfo=LengthInfo.nextParameter, usage=Usage.in) TPointer data, int len, int timeout, int nonblock) { if (log.isTraceEnabled()) { log.trace(String.format("Send data: %s", Utilities.getMemoryDump(data.getAddress(), len))); } return pdpObjects.get(id).send(destMacAddress, port, data, len, timeout, nonblock); } /** * Receive a PDP packet * * @param id - The ID of the PDP object, as returned by ::sceNetAdhocPdpCreate * @param srcMacAddr - Buffer to hold the source mac address of the sender * @param port - Buffer to hold the port number of the received data * @param data - Data buffer * @param dataLength - The length of the data buffer * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return Number of bytes received, < 0 on error. */ @HLEFunction(nid = 0xDFE53E03, version = 150) public int sceNetAdhocPdpRecv(@CheckArgument("checkPdpId") int id, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=6, usage=Usage.out) TPointer srcMacAddr, @BufferInfo(usage=Usage.out) TPointer16 portAddr, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=32, usage=Usage.out) TPointer data, @BufferInfo(usage=Usage.inout) TPointer32 dataLengthAddr, int timeout, int nonblock) { int result = pdpObjects.get(id).recv(srcMacAddr, portAddr, data, dataLengthAddr, timeout, nonblock); return result; } /** * Delete a PDP object. * * @param id - The ID returned from ::sceNetAdhocPdpCreate * @param unk1 - Unknown, set to 0 * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x7F27BB5E, version = 150) public int sceNetAdhocPdpDelete(@CheckArgument("checkPdpId") int id, int unk1) { pdpObjects.remove(id).delete(); return 0; } /** * Get the status of all PDP objects * * @param size - Pointer to the size of the stat array (e.g 20 for one structure) * @param stat - Pointer to a list of ::pspStatStruct structures. * * typedef struct pdpStatStruct * { * struct pdpStatStruct *next; // Pointer to next PDP structure in list * int pdpId; // pdp ID * unsigned char mac[6]; // MAC address * unsigned short port; // Port * unsigned int rcvdData; // Bytes received * } pdpStatStruct * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xC7C1FC57, version = 150) public int sceNetAdhocGetPdpStat(TPointer32 sizeAddr, @CanBeNull TPointer buf) { checkInitialized(); final int objectInfoSize = 20; int size = sizeAddr.getValue(); sizeAddr.setValue(objectInfoSize * pdpObjects.size()); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocGetPdpStat returning size=%d", sizeAddr.getValue())); } if (buf.isNotNull()) { int offset = 0; for (int pdpId : pdpObjects.keySet()) { PdpObject pdpObject = pdpObjects.get(pdpId); // Check if enough space available to write the next structure if (offset + objectInfoSize > size || pdpObject == null) { break; } try { pdpObject.update(); } catch (IOException e) { // Ignore error } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocGetPdpStat returning %s at 0x%08X", pdpObject, buf.getAddress() + offset)); } /** Pointer to next PDP structure in list: will be written later */ offset += 4; /** pdp ID */ buf.setValue32(offset, pdpObject.getId()); offset += 4; /** MAC address */ pdpObject.getMacAddress().write(buf.getMemory(), buf.getAddress() + offset); offset += pdpObject.getMacAddress().sizeof(); /** Port */ buf.setValue16(offset, (short) pdpObject.getPort()); offset += 2; /** Bytes received */ buf.setValue32(offset, pdpObject.getRcvdData()); offset += 4; } fillNextPointersInLinkedList(buf, offset, objectInfoSize); } return 0; } /** * Open a PTP connection * * @param srcmac - Local mac address. * @param srcport - Local port. * @param destmac - Destination mac. * @param destport - Destination port * @param bufsize - Socket buffer size * @param delay - Interval between retrying (microseconds). * @param count - Number of retries. * @param unk1 - Pass 0. * * @return A socket ID on success, < 0 on error. */ @HLEFunction(nid = 0x877F6D66, version = 150) public int sceNetAdhocPtpOpen(pspNetMacAddress srcMacAddress, int srcPort, pspNetMacAddress destMacAddress, int destPort, int bufSize, int retryDelay, int retryCount, int unk1) { checkInitialized(); PtpObject ptpObject = getNetworkAdapter().createPtpObject(); ptpObject.setMacAddress(srcMacAddress); ptpObject.setPort(srcPort); ptpObject.setDestMacAddress(destMacAddress); ptpObject.setDestPort(destPort); ptpObject.setBufSize(bufSize); ptpObject.setRetryDelay(retryDelay); ptpObject.setRetryCount(retryCount); int result = ptpObject.open(); if (result != 0) { // Open failed... ptpObject.delete(); return result; } ptpObjects.put(ptpObject.getId(), ptpObject); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocPtpOpen: returning id=0x%X", ptpObject.getId())); } return ptpObject.getId(); } /** * Wait for connection created by sceNetAdhocPtpOpen() * * @param id - A socket ID. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xFC6FC07B, version = 150) public int sceNetAdhocPtpConnect(@CheckArgument("checkPtpId") int id, int timeout, int nonblock) { return ptpObjects.get(id).connect(timeout, nonblock); } /** * Wait for an incoming PTP connection * * @param srcmac - Local mac address. * @param srcport - Local port. * @param bufsize - Socket buffer size * @param delay - Interval between retrying (microseconds). * @param count - Number of retries. * @param queue - Connection queue length. * @param unk1 - Pass 0. * * @return A socket ID on success, < 0 on error. */ @HLEFunction(nid = 0xE08BDAC1, version = 150) public int sceNetAdhocPtpListen(pspNetMacAddress srcMacAddress, int srcPort, int bufSize, int retryDelay, int retryCount, int queue, int unk1) { checkInitialized(); PtpObject ptpObject = getNetworkAdapter().createPtpObject(); ptpObject.setMacAddress(srcMacAddress); ptpObject.setPort(srcPort); ptpObject.setBufSize(bufSize); ptpObject.setRetryDelay(retryDelay); ptpObject.setRetryCount(retryCount); ptpObject.setQueue(queue); int result = ptpObject.listen(); if (result != 0) { // Listen failed... ptpObject.delete(); return result; } ptpObjects.put(ptpObject.getId(), ptpObject); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocPtpListen: returning id=0x%X", ptpObject.getId())); } return ptpObject.getId(); } /** * Accept an incoming PTP connection * * @param id - A socket ID. * @param mac - Connecting peers mac. * @param port - Connecting peers port. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x9DF81198, version = 150) public int sceNetAdhocPtpAccept(@CheckArgument("checkPtpId") int id, @CanBeNull TPointer peerMacAddr, @CanBeNull TPointer16 peerPortAddr, int timeout, int nonblock) { return ptpObjects.get(id).accept(peerMacAddr.getAddress(), peerPortAddr.getAddress(), timeout, nonblock); } /** * Send data * * @param id - A socket ID. * @param data - Data to send. * @param datasize - Size of the data. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return 0 success, < 0 on error. */ @HLEFunction(nid = 0x4DA4C788, version = 150) public int sceNetAdhocPtpSend(@CheckArgument("checkPtpId") int id, TPointer data, TPointer32 dataSizeAddr, int timeout, int nonblock) { if (log.isTraceEnabled()) { log.trace(String.format("Send data: %s", Utilities.getMemoryDump(data.getAddress(), dataSizeAddr.getValue()))); } return ptpObjects.get(id).send(data.getAddress(), dataSizeAddr, timeout, nonblock); } /** * Receive data * * @param id - A socket ID. * @param data - Buffer for the received data. * @param datasize - Size of the data received. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x8BEA2B3E, version = 150) public int sceNetAdhocPtpRecv(@CheckArgument("checkPtpId") int id, TPointer data, TPointer32 dataSizeAddr, int timeout, int nonblock) { return ptpObjects.get(id).recv(data, dataSizeAddr, timeout, nonblock); } /** * Wait for data in the buffer to be sent * * @param id - A socket ID. * @param timeout - Timeout in microseconds. * @param nonblock - Set to 0 to block, 1 for non-blocking. * * @return A socket ID on success, < 0 on error. */ @HLEFunction(nid = 0x9AC2EEAC, version = 150) public int sceNetAdhocPtpFlush(@CheckArgument("checkPtpId") int id, int timeout, int nonblock) { // Faked: return successful completion return 0; } /** * Close a socket * * @param id - A socket ID. * @param unk1 - Pass 0. * * @return A socket ID on success, < 0 on error. */ @HLEFunction(nid = 0x157E6225, version = 150) public int sceNetAdhocPtpClose(@CheckArgument("checkPtpId") int id, int unknown) { ptpObjects.remove(id).delete(); return 0; } /** * Get the status of all PTP objects * * @param size - Pointer to the size of the stat array (e.g 20 for one structure) * @param stat - Pointer to a list of ::ptpStatStruct structures. * * typedef struct ptpStatStruct * { * struct ptpStatStruct *next; // Pointer to next PTP structure in list * int ptpId; // ptp ID * unsigned char mac[6]; // MAC address * unsigned char peermac[6]; // Peer MAC address * unsigned short port; // Port * unsigned short peerport; // Peer Port * unsigned int sentData; // Bytes sent * unsigned int rcvdData; // Bytes received * int unk1; // Unknown * } ptpStatStruct; * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xB9685118, version = 150) public int sceNetAdhocGetPtpStat(TPointer32 sizeAddr, @CanBeNull TPointer buf) { checkInitialized(); final int objectInfoSize = 36; int size = sizeAddr.getValue(); // Return size required sizeAddr.setValue(objectInfoSize * ptpObjects.size()); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocGetPtpStat returning size=%d", sizeAddr.getValue())); } if (buf.isNotNull()) { int offset = 0; pspNetMacAddress nonExistingDestMacAddress = new pspNetMacAddress(); for (int pdpId : ptpObjects.keySet()) { PtpObject ptpObject = ptpObjects.get(pdpId); // Check if enough space available to write the next structure if (offset + objectInfoSize > size || ptpObject == null) { break; } try { ptpObject.update(); } catch (IOException e) { // Ignore error } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocGetPtpStat returning %s at 0x%08X", ptpObject, buf.getAddress() + offset)); } /** Pointer to next PDP structure in list: will be written later */ offset += 4; /** ptp ID */ buf.setValue32(offset, ptpObject.getId()); offset += 4; /** MAC address */ ptpObject.getMacAddress().write(buf.getMemory(), buf.getAddress() + offset); offset += ptpObject.getMacAddress().sizeof(); /** Dest MAC address */ if (ptpObject.getDestMacAddress() != null) { ptpObject.getDestMacAddress().write(buf.getMemory(), buf.getAddress() + offset); offset += ptpObject.getDestMacAddress().sizeof(); } else { nonExistingDestMacAddress.write(buf.getMemory(), buf.getAddress() + offset); offset += nonExistingDestMacAddress.sizeof(); } /** Port */ buf.setValue16(offset, (short) ptpObject.getPort()); offset += 2; /** Dest Port */ buf.setValue16(offset, (short) ptpObject.getDestPort()); offset += 2; /** Bytes sent */ buf.setValue32(offset, ptpObject.getSentData()); offset += 4; /** Bytes received */ buf.setValue32(offset, ptpObject.getRcvdData()); offset += 4; /** Unknown */ buf.setValue32(offset, 4); // PSP seems to return value 4 here offset += 4; } fillNextPointersInLinkedList(buf, offset, objectInfoSize); } return 0; } /** * Create own game object type data. * * @param data - A pointer to the game object data. * @param size - Size of the game data. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x7F75C338, version = 150) public int sceNetAdhocGameModeCreateMaster(TPointer data, int size) { checkInitialized(); masterGameModeArea = new GameModeArea(data.getAddress(), size); startGameMode(); return 0; } /** * Create peer game object type data. * * @param mac - The mac address of the peer. * @param data - A pointer to the game object data. * @param size - Size of the game data. * * @return The id of the replica on success, < 0 on error. */ @HLEFunction(nid = 0x3278AB0C, version = 150) public int sceNetAdhocGameModeCreateReplica(pspNetMacAddress macAddress, TPointer data, int size) { checkInitialized(); boolean found = false; int result = 0; for (GameModeArea gameModeArea : replicaGameModeAreas) { if (isSameMacAddress(gameModeArea.macAddress.macAddress, macAddress.macAddress)) { // Updating the exiting replica gameModeArea.addr = data.getAddress(); gameModeArea.size = size; result = gameModeArea.id; found = true; break; } } if (!found) { GameModeArea gameModeArea = new GameModeArea(macAddress, data.getAddress(), size); if (log.isDebugEnabled()) { log.debug(String.format("Adding GameMode Replica %s", gameModeArea)); } result = gameModeArea.id; replicaGameModeAreas.add(gameModeArea); } startGameMode(); return result; } /** * Update own game object type data. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x98C204C8, version = 150) public int sceNetAdhocGameModeUpdateMaster() { checkInitialized(); if (masterGameModeArea != null) { if (log.isTraceEnabled()) { log.trace(String.format("Master Game Mode Area: %s", Utilities.getMemoryDump(masterGameModeArea.addr, masterGameModeArea.size))); } masterGameModeArea.setNewData(); } return 0; } /** * Update peer game object type data. * * @param id - The id of the replica returned by sceNetAdhocGameModeCreateReplica. * @param info - address of GameModeUpdateInfo structure. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xFA324B4E, version = 150) public int sceNetAdhocGameModeUpdateReplica(int id, @CanBeNull TPointer infoAddr) { checkInitialized(); for (GameModeArea gameModeArea : replicaGameModeAreas) { if (gameModeArea.id == id) { GameModeUpdateInfo gameModeUpdateInfo = new GameModeUpdateInfo(); if (infoAddr.isNotNull()) { gameModeUpdateInfo.read(infoAddr); } if (gameModeArea.hasNewData()) { if (log.isDebugEnabled()) { log.debug(String.format("Updating GameMode Area with new data: %s", gameModeArea)); } gameModeArea.writeNewData(); gameModeArea.resetNewData(); if (log.isTraceEnabled()) { log.trace(String.format("Replica GameMode Area updated: %s", Utilities.getMemoryDump(gameModeArea.addr, gameModeArea.size))); } gameModeUpdateInfo.updated = 1; } else { gameModeUpdateInfo.updated = 0; } if (infoAddr.getAddress() != 0) { gameModeUpdateInfo.timeStamp = gameModeArea.getUpdateTimestamp(); gameModeUpdateInfo.write(Memory.getInstance()); } break; } } return 0; } /** * Delete own game object type data. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xA0229362, version = 150) public int sceNetAdhocGameModeDeleteMaster() { checkInitialized(); masterGameModeArea = null; if (replicaGameModeAreas.size() <= 0) { stopGameMode(); } return 0; } /** * Delete peer game object type data. * * @param id - The id of the replica. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x0B2228E9, version = 150) public int sceNetAdhocGameModeDeleteReplica(int id) { checkInitialized(); for (GameModeArea gameModeArea : replicaGameModeAreas) { if (gameModeArea.id == id) { replicaGameModeAreas.remove(gameModeArea); break; } } if (replicaGameModeAreas.size() <= 0 && masterGameModeArea == null) { stopGameMode(); } return 0; } }