/* 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 jpcsp.HLE.BufferInfo; import jpcsp.HLE.BufferInfo.LengthInfo; import jpcsp.HLE.BufferInfo.Usage; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.PspString; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.StringInfo; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import jpcsp.Emulator; import jpcsp.Processor; import jpcsp.HLE.kernel.managers.SceUidManager; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.SceNetAdhocctlPeerInfo; import jpcsp.HLE.kernel.types.pspNetMacAddress; import jpcsp.HLE.Modules; import jpcsp.hardware.Wlan; import jpcsp.network.INetworkAdapter; import org.apache.log4j.Logger; public class sceNetAdhocctl extends HLEModule { public static Logger log = Modules.getLogger("sceNetAdhocctl"); public static final int PSP_ADHOCCTL_EVENT_ERROR = 0; public static final int PSP_ADHOCCTL_EVENT_CONNECTED = 1; public static final int PSP_ADHOCCTL_EVENT_DISCONNECTED = 2; public static final int PSP_ADHOCCTL_EVENT_SCAN = 3; public static final int PSP_ADHOCCTL_EVENT_GAME = 4; public static final int PSP_ADHOCCTL_EVENT_DISCOVER = 5; public static final int PSP_ADHOCCTL_EVENT_WOL = 6; public static final int PSP_ADHOCCTL_EVENT_WOL_INTERRUPTED = 7; public static final int PSP_ADHOCCTL_STATE_DISCONNECTED = 0; public static final int PSP_ADHOCCTL_STATE_CONNECTED = 1; public static final int PSP_ADHOCCTL_STATE_SCAN = 2; public static final int PSP_ADHOCCTL_STATE_GAME = 3; public static final int PSP_ADHOCCTL_STATE_DISCOVER = 4; public static final int PSP_ADHOCCTL_STATE_WOL = 5; public static final int PSP_ADHOCCTL_MODE_NORMAL = 0; public static final int PSP_ADHOCCTL_MODE_GAMEMODE = 1; public static final int PSP_ADHOCCTL_MODE_NONE = -1; public static final int NICK_NAME_LENGTH = 128; public static final int GROUP_NAME_LENGTH = 8; public static final int IBSS_NAME_LENGTH = 6; public static final int ADHOC_ID_LENGTH = 9; public static final int MAX_GAME_MODE_MACS = 16; private boolean isInitialized; protected int adhocctlCurrentState; protected String adhocctlCurrentGroup; protected String adhocctlCurrentIBSS; protected int adhocctlCurrentMode; protected int adhocctlCurrentChannel; protected int adhocctlCurrentType; protected String adhocctlCurrentAdhocID; protected boolean doTerminate; protected SceKernelThreadInfo adhocctlThread; private boolean doScan; private volatile long scanStartMillis; private static final int SCAN_DURATION_MILLIS = 700; private boolean doDisconnect; private boolean doJoin; private boolean gameModeJoinComplete; protected LinkedList<AdhocctlPeer> peers; protected LinkedList<AdhocctlNetwork> networks; protected LinkedList<pspNetMacAddress> gameModeMacs; protected LinkedList<pspNetMacAddress> requiredGameModeMacs; protected INetworkAdapter networkAdapter; private long connectCompleteTimestamp; // Some games have problems when the PSP_ADHOCCTL_EVENT_CONNECTED // is sent too quickly after connecting to a network. // The connection will be set CONNECTED with a delay of 200ms. private static final int CONNECT_COMPLETE_DELAY_MILLIS = 200; private HashMap<Integer, AdhocctlHandler> adhocctlIdMap = new HashMap<Integer, AdhocctlHandler>(); private static final String adhocctlHandlerIdPurpose = "sceNetAdhocctl-Handler"; protected class AdhocctlHandler { private int entryAddr; private int currentEvent; private int currentError; private int currentArg; private final int id; private AdhocctlHandler(int addr, int arg) { entryAddr = addr; currentArg = arg; // PSP returns a handler ID between 0 and 3 id = SceUidManager.getNewId(adhocctlHandlerIdPurpose, 0, 3); } protected void triggerAdhocctlHandler() { SceKernelThreadInfo thread = Modules.ThreadManForUserModule.getCurrentThread(); if (thread != null) { Modules.ThreadManForUserModule.executeCallback(thread, entryAddr, null, true, currentEvent, currentError, currentArg); } } protected int getId() { return id; } protected void setEvent(int event) { currentEvent = event; } protected void setError(int error) { currentError = error; } protected void delete() { SceUidManager.releaseId(id, adhocctlHandlerIdPurpose); } @Override public String toString() { return String.format("AdhocctlHandler[id=%d, entry=0x%08X, arg=0x%08X]", getId(), entryAddr, currentArg); } } protected static class AdhocctlPeer { public String nickName; public byte[] macAddress; public long timestamp; public AdhocctlPeer(String nickName, byte[] macAddress) { this.nickName = nickName; this.macAddress = macAddress.clone(); updateTimestamp(); } public void updateTimestamp() { timestamp = getCurrentTimestamp(); } public boolean equals(String nickName, byte[] macAddress) { return nickName.equals(this.nickName) && sceNetAdhoc.isSameMacAddress(macAddress, this.macAddress); } public boolean equals(byte[] macAddress) { return sceNetAdhoc.isSameMacAddress(macAddress, this.macAddress); } @Override public String toString() { return String.format("nickName='%s', macAddress=%s, timestamp=%d", nickName, sceNet.convertMacAddressToString(macAddress), timestamp); } } protected static class AdhocctlNetwork { /** Channel number */ public int channel; /** Name of the connection (alphanumeric characters only) */ public String name; /** The BSSID */ public String bssid; /** mode */ public int mode; public boolean equals(int channel, String name, String bssid, int mode) { return channel == this.channel && name.equals(this.name) && bssid.equals(this.bssid) && mode == this.mode; } @Override public String toString() { return String.format("AdhocctlNetwork[channel=%d, name='%s', bssid='%s', mode=%d]", channel, name, bssid, mode); } } @Override public void start() { peers = new LinkedList<sceNetAdhocctl.AdhocctlPeer>(); networks = new LinkedList<sceNetAdhocctl.AdhocctlNetwork>(); gameModeMacs = new LinkedList<pspNetMacAddress>(); requiredGameModeMacs = new LinkedList<pspNetMacAddress>(); adhocctlCurrentIBSS = "Jpcsp"; adhocctlCurrentMode = PSP_ADHOCCTL_MODE_NONE; adhocctlCurrentChannel = Wlan.getAdhocChannel(); isInitialized = false; networkAdapter = Modules.sceNetModule.getNetworkAdapter(); super.start(); } protected static long getCurrentTimestamp() { return Emulator.getClock().microTime(); } protected void checkInitialized() { if (!isInitialized) { throw new SceKernelErrorException(SceKernelErrors.ERROR_NET_ADHOCCTL_NOT_INITIALIZED); } } public void hleNetAdhocctlAddGameModeMac(byte[] macAddr) { for (pspNetMacAddress macAddress : gameModeMacs) { if (sceNetAdhoc.isSameMacAddress(macAddress.macAddress, macAddr)) { // Already in the list return; } } pspNetMacAddress macAddress = new pspNetMacAddress(); macAddress.setMacAddress(macAddr); gameModeMacs.add(macAddress); if (log.isDebugEnabled()) { log.debug(String.format("Adding new Game Mode MAC: %s", macAddress)); } } private void doConnect() { if (adhocctlCurrentGroup != null && networkAdapter.isConnectComplete()) { long now = Emulator.getClock().currentTimeMillis(); if (now >= connectCompleteTimestamp) { if (adhocctlCurrentMode == PSP_ADHOCCTL_MODE_GAMEMODE) { setState(PSP_ADHOCCTL_STATE_GAME); notifyAdhocctlHandler(PSP_ADHOCCTL_EVENT_GAME, 0); } else { setState(PSP_ADHOCCTL_STATE_CONNECTED); notifyAdhocctlHandler(PSP_ADHOCCTL_EVENT_CONNECTED, 0); } doJoin = false; } } } private int getAdhocctlThreadPollDelay() { // Poll every 100ms final int quickPollDelay = 100000; if (adhocctlCurrentState == PSP_ADHOCCTL_STATE_SCAN) { // Scanning... return quickPollDelay; } if (doJoin) { // Joining... return quickPollDelay; } if (adhocctlCurrentState == PSP_ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentGroup != null) { // Connecting or Creating... return quickPollDelay; } // Poll every 500ms return 500000; } public void hleNetAdhocctlThread(Processor processor) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; if (log.isDebugEnabled()) { log.debug("hleNetAdhocctlThread"); } if (doTerminate) { setState(PSP_ADHOCCTL_STATE_DISCONNECTED); notifyAdhocctlHandler(PSP_ADHOCCTL_EVENT_DISCONNECTED, 0); setGroupName(null, PSP_ADHOCCTL_MODE_NONE); } else if (doDisconnect) { setState(PSP_ADHOCCTL_STATE_DISCONNECTED); notifyAdhocctlHandler(PSP_ADHOCCTL_EVENT_DISCONNECTED, 0); setGroupName(null, PSP_ADHOCCTL_MODE_NONE); doDisconnect = false; } else if (doScan) { setState(PSP_ADHOCCTL_STATE_SCAN); scanStartMillis = Emulator.getClock().milliTime(); doScan = false; } else if (doJoin) { if (adhocctlCurrentMode == PSP_ADHOCCTL_MODE_GAMEMODE) { // Join complete when all the required MACs have joined if (requiredGameModeMacs.size() > 0 && gameModeMacs.size() >= requiredGameModeMacs.size()) { if (log.isDebugEnabled()) { log.debug(String.format("All GameMode MACs have joined, GameMode Join is now complete")); } hleNetAdhocctlSetGameModeJoinComplete(true); // Make sure the list of game mode MACs is in the same order as the one // given at sceNetAdhocctlCreateEnterGameMode gameModeMacs.clear(); gameModeMacs.addAll(requiredGameModeMacs); } if (gameModeJoinComplete) { doConnect(); } else { // Add own MAC to list of game mode MACs hleNetAdhocctlAddGameModeMac(Wlan.getMacAddress()); } } else { doConnect(); } } else if (adhocctlCurrentState == PSP_ADHOCCTL_STATE_DISCONNECTED) { doConnect(); } if (adhocctlCurrentState == PSP_ADHOCCTL_STATE_CONNECTED || adhocctlCurrentState == PSP_ADHOCCTL_STATE_GAME || doJoin) { networkAdapter.updatePeers(); } else if (adhocctlCurrentState == PSP_ADHOCCTL_STATE_SCAN) { networkAdapter.updatePeers(); // End of SCAN? long now = Emulator.getClock().milliTime(); if (now - scanStartMillis > SCAN_DURATION_MILLIS) { // Return to DISCONNECTED state and trigger SCAN event setState(PSP_ADHOCCTL_STATE_DISCONNECTED); notifyAdhocctlHandler(PSP_ADHOCCTL_EVENT_SCAN, 0); } } if (doTerminate) { // Exit thread with status 0 processor.cpu._v0 = 0; threadMan.hleKernelExitDeleteThread(); adhocctlThread = null; doTerminate = false; } else { threadMan.hleKernelDelayThread(getAdhocctlThreadPollDelay(), false); } } protected void setState(int state) { adhocctlCurrentState = state; } protected void setGroupName(String groupName, int mode) { adhocctlCurrentGroup = groupName; adhocctlCurrentMode = mode; gameModeJoinComplete = false; gameModeMacs.clear(); if (groupName != null) { // Some games have problems when the PSP_ADHOCCTL_EVENT_CONNECTED // is sent too quickly after connecting to a network. // The connection will be set CONNECTED with a small delay. connectCompleteTimestamp = Emulator.getClock().currentTimeMillis() + CONNECT_COMPLETE_DELAY_MILLIS; } } public void hleNetAdhocctlConnect(String groupName) { if (log.isDebugEnabled()) { log.debug(String.format("hleNetAdhocctlConnect groupName='%s'", groupName)); } if (hleNetAdhocctlGetGroupName() == null || !hleNetAdhocctlGetGroupName().equals(groupName)) { setGroupName(groupName, PSP_ADHOCCTL_MODE_NORMAL); networkAdapter.sceNetAdhocctlConnect(); } } public void hleNetAdhocctlConnectGame(String groupName) { if (log.isDebugEnabled()) { log.debug(String.format("hleNetAdhocctlConnectGame groupName='%s'", groupName)); } setGroupName(groupName, PSP_ADHOCCTL_MODE_GAMEMODE); } public int hleNetAdhocctlGetState() { return adhocctlCurrentState; } protected void notifyAdhocctlHandler(int event, int error) { for (AdhocctlHandler handler : adhocctlIdMap.values()) { if (log.isDebugEnabled()) { log.debug(String.format("Notifying handler %s with event=%d, error=%d", handler, event, error)); } handler.setEvent(event); handler.setError(error); handler.triggerAdhocctlHandler(); } } public String hleNetAdhocctlGetAdhocID() { return adhocctlCurrentAdhocID; } public String hleNetAdhocctlGetGroupName() { return adhocctlCurrentGroup; } public String hleNetAdhocctlGetIBSS() { return adhocctlCurrentIBSS; } public int hleNetAdhocctlGetMode() { return adhocctlCurrentMode; } public int hleNetAdhocctlGetChannel() { return adhocctlCurrentChannel; } public void hleNetAdhocctlAddNetwork(String groupName, pspNetMacAddress mac, int mode) { hleNetAdhocctlAddNetwork(groupName, mac, adhocctlCurrentChannel, adhocctlCurrentIBSS, mode); } public void hleNetAdhocctlAddNetwork(String groupName, pspNetMacAddress mac, int channel, String ibss, int mode) { boolean found = false; for (AdhocctlNetwork network : networks) { if (network.equals(channel, groupName, ibss, mode)) { found = true; break; } } if (!found) { AdhocctlNetwork network = new AdhocctlNetwork(); network.channel = channel; network.name = groupName; network.bssid = ibss; network.mode = mode; networks.add(network); if (log.isDebugEnabled()) { log.debug(String.format("New network discovered %s", network)); } } } public void hleNetAdhocctlScanComplete() { // Force a completion of the scan at the next run of hleNetAdhocctlThread. // Note: the scan completion has to be executed from a PSP thread because it // is triggering a callback. scanStartMillis = 0; } public void hleNetAdhocctlAddPeer(String nickName, pspNetMacAddress mac) { boolean peerFound = false; for (AdhocctlPeer peer : peers) { if (peer.equals(nickName, mac.macAddress)) { // Update the timestamp peer.updateTimestamp(); peerFound = true; break; } } if (!peerFound) { AdhocctlPeer peer = new AdhocctlPeer(nickName, mac.macAddress); peers.add(peer); if (log.isDebugEnabled()) { log.debug(String.format("New peer discovered %s", peer)); } } } public List<String> getPeersNickName() { List<String> nickNames = new LinkedList<String>(); for (AdhocctlPeer peer : peers) { nickNames.add(peer.nickName); } return nickNames; } public int getNumberPeers() { return peers.size(); } public String getPeerNickName(byte[] macAddress) { for (AdhocctlPeer peer : peers) { if (peer.equals(macAddress)) { return peer.nickName; } } return null; } public void hleNetAdhocctlDeletePeer(byte[] macAddress) { for (AdhocctlPeer peer : peers) { if (peer.equals(macAddress)) { peers.remove(peer); if (log.isDebugEnabled()) { log.debug(String.format("Peer deleted %s", peer)); } break; } } } public void hleNetAdhocctlPeerUpdateTimestamp(byte[] macAddress) { for (AdhocctlPeer peer : peers) { if (peer.equals(macAddress)) { peer.updateTimestamp(); break; } } } public boolean isGameModeComplete() { // The Join for GameMode is complete when all the required MACs have joined return gameModeMacs.size() >= requiredGameModeMacs.size(); } public List<pspNetMacAddress> hleNetAdhocctlGetRequiredGameModeMacs() { return requiredGameModeMacs; } public void hleNetAdhocctlSetGameModeJoinComplete(boolean gameModeJoinComplete) { this.gameModeJoinComplete = gameModeJoinComplete; } public void hleNetAdhocctlSetGameModeMacs(byte[][] gameModeMacs) { // Make sure the list of game mode MACs is in the same order as the one // given at sceNetAdhocctlCreateEnterGameMode this.gameModeMacs.clear(); for (int i = 0; i < gameModeMacs.length; i++) { hleNetAdhocctlAddGameModeMac(gameModeMacs[i]); } } public static void fillNextPointersInLinkedList(TPointer buffer, int size, int elementSize) { for (int offset = 0; offset < size; offset += elementSize) { if (offset + elementSize >= size) { // Last one buffer.setValue32(offset, 0); } else { // Pointer to next one buffer.setValue32(offset, buffer.getAddress() + offset + elementSize); } } } /** * Initialise the Adhoc control library * * @param stacksize - Stack size of the adhocctl thread. Set to 0x2000 * @param priority - Priority of the adhocctl thread. Set to 0x30 * @param product - Pass a filled in ::productStruct * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xE26F226E, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlInit(int stackSize, int priority, @CanBeNull TPointer product) { if (isInitialized) { return SceKernelErrors.ERROR_NET_ADHOCCTL_ALREADY_INITIALIZED; } if (product.isNotNull()) { adhocctlCurrentType = product.getValue32(0); // 0 - Commercial type / 1 - Debug type. adhocctlCurrentAdhocID = product.getStringNZ(4, ADHOC_ID_LENGTH); if (log.isDebugEnabled()) { log.debug(String.format("Found product data: type=%d, AdhocID='%s'", adhocctlCurrentType, adhocctlCurrentAdhocID)); } } setState(PSP_ADHOCCTL_STATE_DISCONNECTED); doTerminate = false; doScan = false; doDisconnect = false; ThreadManForUser threadMan = Modules.ThreadManForUserModule; adhocctlThread = threadMan.hleKernelCreateThread("SceNetAdhocctl", ThreadManForUser.NET_ADHOC_CTL_LOOP_ADDRESS, priority, stackSize, 0, 0, SysMemUserForUser.USER_PARTITION_ID); threadMan.hleKernelStartThread(adhocctlThread, 0, 0, adhocctlThread.gpReg_addr); networkAdapter.sceNetAdhocctlInit(); isInitialized = true; return 0; } /** * Terminate the Adhoc control library * * @return 0 on success, < on error. */ @HLEFunction(nid = 0x9D689E13, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlTerm() { doTerminate = true; isInitialized = false; networkAdapter.sceNetAdhocctlTerm(); return 0; } /** * Connect to the Adhoc control * * @param name - The name of the connection (maximum 8 alphanumeric characters). * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x0AD043ED, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlConnect(@CanBeNull @StringInfo(maxLength=GROUP_NAME_LENGTH) PspString groupName) { checkInitialized(); hleNetAdhocctlConnect(groupName.getString()); return 0; } /** * Connect to the Adhoc control (as a host) * * @param name - The name of the connection (maximum 8 alphanumeric characters). * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xEC0635C1, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlCreate(@CanBeNull @StringInfo(maxLength=GROUP_NAME_LENGTH) PspString groupName) { checkInitialized(); setGroupName(groupName.getString(), PSP_ADHOCCTL_MODE_NORMAL); networkAdapter.sceNetAdhocctlCreate(); return 0; } /** * Connect to the Adhoc control (as a client) * * @param scaninfo - A valid ::SceNetAdhocctlScanInfo struct that has been filled by sceNetAchocctlGetScanInfo * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x5E7F79C9, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlJoin(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=28, usage=Usage.in) TPointer scanInfoAddr) { checkInitialized(); if (scanInfoAddr.isAddressGood()) { // IBSS Data field. int nextAddr = scanInfoAddr.getValue32(0); // Next group data. int ch = scanInfoAddr.getValue32(4); String groupName = scanInfoAddr.getStringNZ(8, GROUP_NAME_LENGTH); String bssID = scanInfoAddr.getStringNZ(16, IBSS_NAME_LENGTH); int mode = scanInfoAddr.getValue32(24); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlJoin nextAddr 0x%08X, ch %d, groupName '%s', bssID '%s', mode %d", nextAddr, ch, groupName, bssID, mode)); } doJoin = true; setGroupName(groupName, PSP_ADHOCCTL_MODE_NORMAL); networkAdapter.sceNetAdhocctlJoin(); } return 0; } /** * Scan the adhoc channels * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x08FFF7A0, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlScan() { checkInitialized(); doScan = true; networkAdapter.sceNetAdhocctlScan(); return 0; } /** * Disconnect from the Adhoc control * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x34401D65, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlDisconnect() { checkInitialized(); doDisconnect = true; networkAdapter.sceNetAdhocctlDisconnect(); // Delete all the peers while (!peers.isEmpty()) { AdhocctlPeer peer = peers.get(0); hleNetAdhocctlDeletePeer(peer.macAddress); } return 0; } /** * Register an adhoc event handler * * @param handler - The event handler. * @param unknown - Pass NULL. * * @return Handler id on success, < 0 on error. */ @HLEFunction(nid = 0x20B317A0, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlAddHandler(TPointer adhocctlHandlerAddr, int adhocctlHandlerArg) { checkInitialized(); AdhocctlHandler adhocctlHandler = new AdhocctlHandler(adhocctlHandlerAddr.getAddress(), adhocctlHandlerArg); int id = adhocctlHandler.getId(); if (id == SceUidManager.INVALID_ID) { return SceKernelErrors.ERROR_NET_ADHOCCTL_TOO_MANY_HANDLERS; } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlAddHandler returning id=0x%X", id)); } adhocctlIdMap.put(id, adhocctlHandler); return id; } /** * Delete an adhoc event handler * * @param id - The handler id as returned by sceNetAdhocctlAddHandler. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x6402490B, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlDelHandler(int id) { checkInitialized(); AdhocctlHandler handler = adhocctlIdMap.remove(id); if (handler != null) { handler.delete(); } return 0; } /** * Get the state of the Adhoc control * * @param event - Pointer to an integer to receive the status. Can continue when it becomes 1. * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x75ECD386, version = 150, checkInsideInterrupt = true) public int sceNetAdhocctlGetState(@BufferInfo(usage=Usage.out) TPointer32 stateAddr) { checkInitialized(); stateAddr.setValue(adhocctlCurrentState); return 0; } /** * Get the adhoc ID * * @param product - A pointer to a ::productStruct * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x362CBE8F, version = 150) public int sceNetAdhocctlGetAdhocId(TPointer addr) { checkInitialized(); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetAdhocId returning type=%d, adhocID='%s'", adhocctlCurrentType, adhocctlCurrentAdhocID)); } addr.setValue32(0, adhocctlCurrentType); addr.setStringNZ(4, ADHOC_ID_LENGTH, adhocctlCurrentAdhocID); return 0; } /** * Get a list of peers * * @param length - The length of the list. * @param buf - An allocated area of size length. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xE162CB14, version = 150) public int sceNetAdhocctlGetPeerList(@BufferInfo(usage=Usage.inout) TPointer32 sizeAddr, @CanBeNull @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=152, usage=Usage.out) TPointer buf) { checkInitialized(); int size = sizeAddr.getValue(); SceNetAdhocctlPeerInfo peerInfo = new SceNetAdhocctlPeerInfo(); sizeAddr.setValue(peerInfo.sizeof() * peers.size()); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetPeerList returning size=%d", sizeAddr.getValue())); } if (buf.isNotNull()) { int offset = 0; for (AdhocctlPeer peer : peers) { // Check if enough space available to write the next structure if (offset + peerInfo.sizeof() > size || peer == null) { break; } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetPeerList returning %s at 0x%08X", peer, buf.getAddress() + offset)); } peerInfo.nickName = peer.nickName; peerInfo.macAddress = new pspNetMacAddress(); peerInfo.macAddress.setMacAddress(peer.macAddress); peerInfo.timestamp = peer.timestamp; peerInfo.write(buf, offset); offset += peerInfo.sizeof(); } fillNextPointersInLinkedList(buf, offset, peerInfo.sizeof()); } return 0; } /** * Get peer information * * @param mac - The mac address of the peer. * @param size - Size of peerinfo. * @param peerinfo - Pointer to store the information. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x8DB83FDC, version = 150) public int sceNetAdhocctlGetPeerInfo(pspNetMacAddress macAddress, int size, @BufferInfo(lengthInfo=LengthInfo.previousParameter, usage=Usage.out) TPointer peerInfoAddr) { checkInitialized(); int result = SceKernelErrors.ERROR_NET_ADHOC_NO_ENTRY; if (sceNetAdhoc.isMyMacAddress(macAddress.macAddress)) { SceNetAdhocctlPeerInfo peerInfo = new SceNetAdhocctlPeerInfo(); peerInfo.nickName = sceUtility.getSystemParamNickname(); peerInfo.macAddress = new pspNetMacAddress(Wlan.getMacAddress()); peerInfo.timestamp = getCurrentTimestamp(); peerInfo.write(peerInfoAddr); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetPeerInfo for own MAC address, returning %s", peerInfo)); } result = 0; } else { for (AdhocctlPeer peer : peers) { if (macAddress.equals(peer.macAddress)) { SceNetAdhocctlPeerInfo peerInfo = new SceNetAdhocctlPeerInfo(); peerInfo.nickName = peer.nickName; peerInfo.macAddress = new pspNetMacAddress(peer.macAddress); peerInfo.timestamp = peer.timestamp; peerInfo.write(peerInfoAddr); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetPeerInfo returning %s", peerInfo)); } result = 0; break; } } } if (result != 0) { if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetPeerInfo returning 0x%08X", result)); } } return result; } /** * Get mac address from nickname * * @param nickname - The nickname. * @param length - The length of the list. * @param buf - An allocated area of size length. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x99560ABE, version = 150) public int sceNetAdhocctlGetAddrByName(@StringInfo(maxLength=NICK_NAME_LENGTH) PspString nickName, TPointer32 sizeAddr, @CanBeNull TPointer buf) { checkInitialized(); // Search for peers matching the given nick name LinkedList<AdhocctlPeer> matchingPeers = new LinkedList<sceNetAdhocctl.AdhocctlPeer>(); for (AdhocctlPeer peer : peers) { if (nickName.equals(peer.nickName)) { matchingPeers.add(peer); } } int size = sizeAddr.getValue(); SceNetAdhocctlPeerInfo peerInfo = new SceNetAdhocctlPeerInfo(); sizeAddr.setValue(peerInfo.sizeof() * matchingPeers.size()); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetAddrByName returning size=%d", sizeAddr.getValue())); } if (buf.isNotNull()) { int offset = 0; for (AdhocctlPeer peer : matchingPeers) { // Check if enough space available to write the next structure if (offset + peerInfo.sizeof() > size) { break; } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetAddrByName returning %s at 0x%08X", peer, buf.getAddress() + offset)); } peerInfo.nickName = peer.nickName; peerInfo.macAddress = new pspNetMacAddress(); peerInfo.macAddress.setMacAddress(peer.macAddress); peerInfo.timestamp = peer.timestamp; peerInfo.write(buf, offset); offset += peerInfo.sizeof(); } fillNextPointersInLinkedList(buf, offset, peerInfo.sizeof()); } return 0; } /** * Get nickname from a mac address * * @param mac - The mac address. * @param nickname - Pointer to a char buffer where the nickname will be stored. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x8916C003, version = 150) public int sceNetAdhocctlGetNameByAddr(pspNetMacAddress macAddress, TPointer nickNameAddr) { checkInitialized(); String nickName = ""; for (AdhocctlPeer peer : peers) { if (sceNetAdhoc.isSameMacAddress(macAddress.macAddress, peer.macAddress)) { nickName = peer.nickName; } } nickNameAddr.setStringNZ(NICK_NAME_LENGTH, nickName); return 0; } /** * Get Adhocctl parameter * * @param params - Pointer to a ::SceNetAdhocctlParams * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xDED9D28E, version = 150) public int sceNetAdhocctlGetParameter(TPointer paramsAddr) { checkInitialized(); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetParameter returning channel=%d, group='%s', IBSS='%s', nickName='%s'", adhocctlCurrentChannel, adhocctlCurrentGroup, adhocctlCurrentIBSS, sceUtility.getSystemParamNickname())); } paramsAddr.setValue32(0, adhocctlCurrentChannel); paramsAddr.setStringNZ(4, GROUP_NAME_LENGTH, adhocctlCurrentGroup); paramsAddr.setStringNZ(12, IBSS_NAME_LENGTH, adhocctlCurrentIBSS); paramsAddr.setStringNZ(18, NICK_NAME_LENGTH, sceUtility.getSystemParamNickname()); return 0; } /** * Get the results of a scan * * @param length - The length of the list. * @param buf - An allocated area of size length. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x81AEE1BE, version = 150) public int sceNetAdhocctlGetScanInfo(@BufferInfo(usage=Usage.inout) TPointer32 sizeAddr, @CanBeNull @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=112, usage=Usage.out) TPointer buf) { checkInitialized(); final int scanInfoSize = 28; int size = sizeAddr.getValue(); sizeAddr.setValue(scanInfoSize * networks.size()); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetScanInfo returning size=%d", sizeAddr.getValue())); } if (buf.isNotNull()) { int offset = 0; for (AdhocctlNetwork network : networks) { // Check if enough space available to write the next structure if (offset + scanInfoSize > size || network == null) { break; } if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetScanInfo returning %s at 0x%08X", network, buf.getAddress() + offset)); } /** Pointer to next Network structure in list: will be written later */ offset += 4; /** Channel number */ buf.setValue32(offset, network.channel); offset += 4; /** Name of the connection (alphanumeric characters only) */ buf.setStringNZ(offset, GROUP_NAME_LENGTH, network.name); offset += GROUP_NAME_LENGTH; /** The BSSID */ buf.setStringNZ(offset, IBSS_NAME_LENGTH, network.bssid); offset += IBSS_NAME_LENGTH; /** Padding */ buf.setValue16(offset, (short) 0); offset += 2; /** Mode */ buf.setValue32(offset, network.mode); offset += 4; } fillNextPointersInLinkedList(buf, offset, scanInfoSize); } return 0; } /** * Connect to the Adhoc control game mode (as a host) * * @param name - The name of the connection (maximum 8 alphanumeric characters). * @param unknown - Pass 1. * @param num - The total number of players (including the host). * @param macs - A pointer to a list of the participating mac addresses, host first, then clients. * @param timeout - Timeout in microseconds. * @param unknown2 - pass 0. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xA5C055CE, version = 150) public int sceNetAdhocctlCreateEnterGameMode(@CanBeNull @StringInfo(maxLength=GROUP_NAME_LENGTH) PspString groupName, int unknown, int num, TPointer macsAddr, int timeout, int unknown2) { checkInitialized(); if (unknown <= 0 || unknown > 3 || num < 2 || num > 16) { return SceKernelErrors.ERROR_NET_ADHOCCTL_INVALID_PARAMETER; } if (unknown == 1 && num > 4) { return SceKernelErrors.ERROR_NET_ADHOCCTL_INVALID_PARAMETER; } gameModeMacs.clear(); requiredGameModeMacs.clear(); for (int i = 0; i < num; i++) { pspNetMacAddress macAddress = new pspNetMacAddress(); macAddress.read(macsAddr, i * macAddress.sizeof()); requiredGameModeMacs.add(macAddress); if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlCreateEnterGameMode macAddress#%d=%s", i, macAddress)); } } // We have to wait for all the MACs to have joined to go into CONNECTED state doJoin = true; setGroupName(groupName.getString(), PSP_ADHOCCTL_MODE_GAMEMODE); return 0; } @HLEUnimplemented @HLEFunction(nid = 0xB0B80E80, version = 150) public int sceNetAdhocctlCreateEnterGameModeMin() { checkInitialized(); return 0; } /** * Connect to the Adhoc control game mode (as a client) * * @param name - The name of the connection (maximum 8 alphanumeric characters). * @param hostmac - The mac address of the host. * @param timeout - Timeout in microseconds. * @param unknown - pass 0. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x1FF89745, version = 150) public int sceNetAdhocctlJoinEnterGameMode(@StringInfo(maxLength=GROUP_NAME_LENGTH) PspString groupName, pspNetMacAddress macAddress, int timeout, int unknown) { checkInitialized(); doJoin = true; setGroupName(groupName.getString(), PSP_ADHOCCTL_MODE_GAMEMODE); return 0; } /** * Exit game mode. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xCF8E084D, version = 150) public int sceNetAdhocctlExitGameMode() { checkInitialized(); doDisconnect = true; Modules.sceNetAdhocModule.hleExitGameMode(); return 0; } /** * Get game mode information * * @param gamemodeinfo - Pointer to store the info. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x5A014CE0, version = 150) public int sceNetAdhocctlGetGameModeInfo(TPointer gameModeInfoAddr) { checkInitialized(); int offset = 0; gameModeInfoAddr.setValue32(offset, gameModeMacs.size()); offset += 4; for (pspNetMacAddress macAddress : gameModeMacs) { if (log.isDebugEnabled()) { log.debug(String.format("sceNetAdhocctlGetGameModeInfo returning %s", macAddress)); } macAddress.write(gameModeInfoAddr, offset); offset += macAddress.sizeof(); } return 0; } }