/*
* Gamers Own Instant Messenger
* Copyright (C) 2005-2006 Herbert Poul (kahless@sphene.net)
* http://goim.sphene.net
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package net.sphene.goim.rcp.extensionpoints;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.xmlpull.v1.XmlPullParser;
import net.sphene.goim.rcp.GOIMPlugin;
import net.sphene.goim.rcp.beans.GOIMGameItem;
import net.sphene.goim.rcp.beans.GOIMGameList;
import net.sphene.goim.rcp.beans.GameStatusChangeEvent;
import net.sphene.goim.rcp.beans.GOIMGameItem.GOIMGameExecuteCommands;
import net.sphene.goim.rcp.extensionpoints.GameExtensionPoint.GameExtensionProxy;
import net.sphene.goim.rcp.extensionpoints.game.IGameDestination;
import net.sphene.goim.rcp.preferences.PreferenceConstants;
import net.sphene.libs.SpheneStringUtil;
public class GameAdapter implements IGame {
protected InetSocketAddress getCurrentDestination(GOIMGameItem game) {
return (InetSocketAddress)game.getSessionData(IGame.GAMEITEM_SESSION_CURRENT_DESTINATION);
}
protected void setCurrentDestination(GOIMGameItem game, InetSocketAddress destination) {
game.setSessionData(IGame.GAMEITEM_SESSION_CURRENT_DESTINATION,destination);
}
protected void setCurrentSourcePort(GOIMGameItem game, int sourcePort) {
game.setSessionData(IGame.GAMEITEM_SESSION_CURRENT_SOURCEPORT,sourcePort);
}
protected int getCurrentSourcePort(GOIMGameItem game) {
Integer sourcePort = (Integer)game.getSessionData(IGame.GAMEITEM_SESSION_CURRENT_SOURCEPORT);
if(sourcePort == null) return -1;
return sourcePort.intValue();
}
protected void setNextDestination(GOIMGameItem game, InetSocketAddress destination) {
game.setSessionData(IGame.GAMEITEM_SESSION_NEXT_DESTINATION,destination);
}
protected InetSocketAddress getNextDestination(GOIMGameItem game) {
return (InetSocketAddress)game.getSessionData(IGame.GAMEITEM_SESSION_NEXT_DESTINATION);
}
protected void setPacketCount(GOIMGameItem game, int packetCount) {
game.setSessionData(IGame.GAMEITEM_SESSION_PACKET_COUNT,packetCount);
}
protected int getPacketCount(GOIMGameItem game) {
Integer packetCount = (Integer)game.getSessionData(IGame.GAMEITEM_SESSION_PACKET_COUNT);
if(packetCount == null) return 0;
return packetCount.intValue();
}
public void closedUdpPort(GOIMGameItem game, int port) {
if(getCurrentDestination(game) != null && getCurrentSourcePort(game) == port) {
System.out.printf("Closed connection (%s) to %s\n",game.retrieveExtensionProxy().name,getCurrentDestination(game).toString());
setCurrentDestination(game,null);
setNextDestination(game,null);
setPacketCount(game,0);
leftServer();
}
}
/**
* This method can be overloaded to return e.g. 2 when destination.getPort() is the default port of the game,
* and 100 when it is a known port for e.g. punkbuster update or soemthing. This way we need x packets in a row to signal a server join.
* @param game
* @param destination
* @param sourceport
* @return
*/
public int requiredPacketCountForDestination(GOIMGameItem game, InetSocketAddress destination, int sourceport) {
return 2;
}
public void sniffedPacketFromGame(GOIMGameItem game, InetSocketAddress destination, int sourceport) {
// We ignore all broadcasts ...
byte[] address = destination.getAddress().getAddress();
if(address[0] == (byte)255 &&
address[1] == (byte)255 &&
address[2] == (byte)255 &&
address[3] == (byte)255)
return;
// When we receive the 3rd packet to the same destination on the same source port we fire the connect event..
InetSocketAddress nextDestination = getNextDestination(game);
if(getPacketCount(game) == requiredPacketCountForDestination(game,destination,sourceport)) {
System.out.println("enough packets ... ");
if(nextDestination != null && nextDestination.equals(destination)) {
System.out.printf("I got a connection from %s to %s (source port: %d)\n",game.retrieveExtensionProxy().name,destination.toString(),sourceport);
setCurrentDestination(game,nextDestination);
setCurrentSourcePort(game,sourceport);
setNextDestination(game,null);
joinedServer(game);
} else {
setPacketCount(game,0);
setCurrentDestination(game,null);
}
}
//System.out.println("nextDestination: " + nextDestination.toString());
if(nextDestination != null && nextDestination.equals(destination)) {
setPacketCount(game,getPacketCount(game)+1);
//System.out.printf("Connected packet to %s (source port: %d)\n",destination.toString(),sourceport);
} else {
if(getCurrentDestination(game) != null && getCurrentDestination(game).equals(destination)) {
setNextDestination(game,null);
setPacketCount(game, 0);
} else {
setPacketCount(game, 1);
setNextDestination(game,destination);
}
}
}
/**
* This method gets called by sniffedPacketFromGame if the user just joined a
* (new) server. The default implementation just fires the event.
*
* (subclasses should always call this implementation if they overload)
* @param game the GameItem of the joined server.
*
*/
protected void joinedServer(GOIMGameItem game) {
GOIMPlugin.gameStatusChanged.fireEvent(new GameStatusChangeEvent(this,GameStatusChangeEvent.TYPE_JOINED_GAME));
}
/**
* This method gets called by closedUdpPort(GOIMGameItem,int) if it detects that
* the local UDP port was closed used for this game. (ie. When the game server was
* left)
*
* Subclass should always call this super method when overloading, because it sends
* the game server left event.
*
*/
protected void leftServer() {
GOIMPlugin.gameStatusChanged.fireEvent(new GameStatusChangeEvent(this,GameStatusChangeEvent.TYPE_LEFT_GAME));
}
//public void initWithConfig(GOIMGameItem item) {
// this.game = item;
//}
public IGameExtension getStatusPacket(GOIMGameItem game) {
if(getCurrentDestination(game) == null) return null;
return new GameStatusExtension(getCurrentDestination(game),game.retrieveExtensionProxy(),game, null);
}
public String getStatusString(GOIMGameItem game) {
if(getCurrentDestination(game) == null) return null;
return String.format("%s (@ %s:%d)",game.retrieveExtensionProxy().name,getCurrentDestination(game).getAddress().getHostAddress(),getCurrentDestination(game).getPort());
//return null;
}
public static class GameStatusExtension implements IGame.IGameExtension,PacketExtension {
public static final String ELEMENT_NAME = "x";
public static final String NAMESPACE = "http://goim.sphene.net/gameStatus";
//public static final MessageFormat targetMessageFormat = new MessageFormat("{0,number,integer}.{1,number,integer}.{2,number,integer}.{3,number,integer}:{4,number,integer}");
public static InetSocketAddress parseSocketAddress(String text) {
try {
// Object[] obj = targetMessageFormat.parse(text);
// return new InetSocketAddress(InetAddress.getByAddress(new byte[]{((Integer)obj[0]).byteValue(),
// ((Integer)obj[1]).byteValue(),
// ((Integer)obj[2]).byteValue(),
// ((Integer)obj[3]).byteValue()}),
// ((Integer)obj[0]).intValue());
StringTokenizer st = new StringTokenizer(text,".:");
byte[] ip = new byte[4];
int port = 0;
for(int i = 0 ; st.hasMoreElements() ; i ++) {
String token = st.nextToken();
int val = Integer.parseInt(token);
if(i < 4) ip[i] = (byte) val;
else port = val;
}
return new InetSocketAddress(InetAddress.getByAddress(ip),port);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String formatSocketAddressIP(InetAddress address) {
byte[] ip = address.getAddress();
return new StringBuffer().append(ip[0]&0xff).append('.').append(ip[1]&0xff).append('.').append(ip[2]&0xff).append('.').append(ip[3]&0xff).toString();
}
public static String formatSocketAddress(InetSocketAddress addr) {
//return targetMessageFormat.format(new Object[] {(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],addr.getPort()});
return formatSocketAddressIP(addr.getAddress()) + ':' + addr.getPort();
}
InetSocketAddress target;
GOIMGameItem game;
private GameExtensionProxy proxy;
private Map<String, String> atts;
String gameId;
public GameStatusExtension(InetSocketAddress target, GameExtensionProxy proxy, GOIMGameItem game, Map<String, String> atts, String gameId) {
this.gameId = gameId;
this.target = target;
this.game = game;
this.proxy = proxy;
this.atts = atts;
}
public GameStatusExtension(InetSocketAddress target, GameExtensionProxy proxy, GOIMGameItem game, Map<String, String> atts) {
this(target,proxy,game,atts,(game == null || game.retrieveExtensionProxy() == null ? null : game.retrieveExtensionProxy().id));
}
public Map<String,String> getAdditionalAttributes() { return atts; }
public String setAdditionalAttribute(String key, String value) {
if(atts == null)
atts = new HashMap<String,String>();
return atts.put(key,value);
}
public String getElementName() {
return ELEMENT_NAME;
}
public String getNamespace() {
return NAMESPACE;
}
public String toXML() {
StringBuffer additionalXMLAttributes = new StringBuffer("");
if(atts != null)
for(String key : atts.keySet()) {
additionalXMLAttributes.append(' ').append(key).append("=\"").append(SpheneStringUtil.escapeXMLElementEntities(atts.get(key))).append("\"");
}
return String.format("<%s xmlns=\"%s\" game=\"%s\" target=\"%s\"%s></%s>",
getElementName(),getNamespace(),gameId,
formatSocketAddress(target),additionalXMLAttributes.toString(),getElementName());
}
public static class Provider implements PacketExtensionProvider {
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
InetSocketAddress addr = parseSocketAddress(parser.getAttributeValue("","target"));
GOIMGameList list = GOIMPlugin.getPreferenceObject(GOIMGameList.class);
String gameId = parser.getAttributeValue("","game");
int attributes = parser.getAttributeCount();
Map<String,String> atts = new HashMap<String,String>();
for(int i = 0 ; i < attributes ; i++) {
String name = parser.getAttributeName(i);
if(name.equals("xmlns") || name.equals("game") || name.equals("target"))
continue;
atts.put(parser.getAttributeName(i),parser.getAttributeValue(i));
}
if(atts.size() < 1) atts = null;
GameExtensionProxy proxy = GameExtensionPoint.getGameExtension(gameId);
GOIMGameItem game = null;
for(GOIMGameItem item : list) {
if(item.gameId.equals(gameId)) {
game = item;
break;
}
}
if(proxy == null || addr == null) return null;
return new GameStatusExtension(addr,proxy,game,atts,gameId);
}
}
public GOIMGameItem getGameItem() {
return game;
}
public GameExtensionProxy getGameExtension() {
return proxy;
}
public InetSocketAddress getDestination() {
return target;
}
}
/**
* Returns the command used to launch the game and connect to a specified server.
* @param game
* @return
* @see #getExecuteParameters(GOIMGameItem, IGameDestination)
*/
public List<GOIMGameExecuteCommands> getConnectCommands(GOIMGameItem game) {
List<GOIMGameExecuteCommands> commands = new ArrayList<GOIMGameExecuteCommands>();
GOIMGameExecuteCommands cmd;
cmd = getExecuteCommandBefore();
if(cmd != null) commands.add(cmd);
if(game.customizedExecute && game.executeCommands != null && game.executeCommands.size() > 0) {
for(GOIMGameExecuteCommands command : game.executeCommands) {
if(command.commandType == GOIMGameExecuteCommands.ExecuteCommandType.CONNECT ||
command.commandType == GOIMGameExecuteCommands.ExecuteCommandType.BOTH) {
commands.add(command);
}
}
} else {
String command = getConnectCommand(game);
commands.add(stringCommandToObject(command));
}
cmd = getExecuteCommandAfter();
if(cmd != null) commands.add(cmd);
return commands;
}
public String getConnectCommand(GOIMGameItem game) {
return game.retrieveExtensionProxy().getDefaultConnectCommand();
}
/**
* Returns the Execute Command before a game is launched. (from the preference store)
* @return a GOIMGameExecuteCommands representing the command.
*/
private GOIMGameExecuteCommands getExecuteCommandBefore() {
String cmd = GOIMPlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.P_GAME_EXECUTE_COMMAND_BEFORE);
return stringCommandToObject(cmd);
}
/**
* Returns the Execute Command after a game is launched. (from the preference store)
* @return a GOIMGameExecuteCommands representing the command.
*/
private GOIMGameExecuteCommands getExecuteCommandAfter() {
String cmd = GOIMPlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.P_GAME_EXECUTE_COMMAND_AFTER);
return stringCommandToObject(cmd);
}
private GOIMGameExecuteCommands stringCommandToObject(String cmd) {
if("".equals(cmd.trim())) return null;
return new GOIMGameExecuteCommands(cmd,true,GOIMGameExecuteCommands.ExecuteCommandType.BOTH);
}
/**
* Returns the command used to launch the game.
* @param game
* @return
* @see #getExecuteParameters(GOIMGameItem, IGameDestination)
*/
public List<GOIMGameExecuteCommands> getLaunchCommands(GOIMGameItem game) {
List<GOIMGameExecuteCommands> commands = new ArrayList<GOIMGameExecuteCommands>();
GOIMGameExecuteCommands cmd;
cmd = getExecuteCommandBefore();
if(cmd != null) commands.add(cmd);
if(game.customizedExecute && game.executeCommands != null && game.executeCommands.size() > 0) {
for(GOIMGameExecuteCommands command : game.executeCommands) {
if(command.commandType == GOIMGameExecuteCommands.ExecuteCommandType.LAUNCH ||
command.commandType == GOIMGameExecuteCommands.ExecuteCommandType.BOTH) {
commands.add(command);
}
}
} else {
String command;
command = game.retrieveExtensionProxy().launchcommand;
if(command == null) {
command = game.retrieveExtensionProxy().params.get("launchcommand");
}
if(command == null) {
command = "{gamepath}";
}
commands.add(stringCommandToObject(command));
}
cmd = getExecuteCommandAfter();
if(cmd != null) commands.add(cmd);
return commands;
}
/**
* Returns parameters which can be used in launchcommand and connectcommand.
* Subclasses may overwrite this method to provide custom parameters.
* By default adds: serverip, serverport and gamepath
*
* so you can use e.g.: {gamepath} +connect {serverip}:{serverport}
* @param game
* @param gameDestination
* @return
*/
public Map<String,String> getExecuteParameters(GOIMGameItem game, IGameDestination gameDestination) {
Map<String,String> params = new HashMap<String,String>();
InetSocketAddress destination = null;
if(gameDestination != null) destination = gameDestination.getDestination();
params.put("gamename",game.retrieveExtensionProxy().name);
params.put("gameid",game.retrieveExtensionProxy().id);
if(destination != null) {
params.put("serveraddress",GameStatusExtension.formatSocketAddress(destination));
params.put("serverip",GameStatusExtension.formatSocketAddressIP(destination.getAddress()));
params.put("serverport",Integer.toString(destination.getPort()));
}
params.put("gamepath",game.path);
return params;
}
/**
* Executes the specified game.
*/
public void execute(GOIMGameItem game, IGameDestination gameDestination) {
InetSocketAddress destination = null;
if(gameDestination != null) destination = gameDestination.getDestination();
List<GOIMGameExecuteCommands> commands = null;
Map<String,String> params = getExecuteParameters(game, gameDestination);
if(destination == null) {
commands = getLaunchCommands(game);
} else {
commands = getConnectCommands(game);
}
if(commands != null) {
new Thread(new GameLauncher(commands,params,game)).start();
}
}
public boolean needsOnlineStatusChange(GOIMGameItem item) {
return getCurrentDestination(item) != null && item.retrieveExtensionProxy().changestatus;
}
public String getTooltipText(GOIMGameItem game, IGameExtension gameEx) {
GameExtensionProxy proxy = gameEx.getGameExtension();
return proxy.name + ": " + GameStatusExtension.formatSocketAddress(gameEx.getDestination());
}
public void init(GameExtensionProxy proxy) {
}
public static class GameLauncher implements Runnable {
private List<GOIMGameExecuteCommands> commands;
private Map<String, String> params;
private GOIMGameItem game;
public GameLauncher(List<GOIMGameExecuteCommands> commands, Map<String,String> params, GOIMGameItem game) {
this.commands = commands;
this.params = params;
this.game = game;
}
public void run() {
for(GOIMGameExecuteCommands cmd : commands) {
String command = cmd.command;
for(String param : params.keySet()) {
command.replace("{" + param + "}",params.get(param));
}
try {
Process process = Runtime.getRuntime().exec(command,null,new File(game.path).getParentFile());
if(cmd.waitforterminate) {
process.waitFor();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}