package com.pixelutilitys.arcade.emulators.AEPgb;
/**
* this source file released under the GNU Public Licence.
* see the accompanying copyright.txt for more information
* Copyright (C) 2000-2001 Ben Mazur
*/
import java.awt.Button;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* PgbNetplay is contains the Pgb network client and server
* code. It is responsible for handling the serial and the
* I/R port.
*
* transmission codes:
*
* 0x10, n, ... = name transmission, len, byte[...]
* 0x11, n, ... = emulator transmission, len, byte[...]
* 0x12, n, ... = version transmission, len, byte[...]
* 0x02 = IR transmission, 1 or 0
* 0x03 = serial transmission, serial data
*
*
* Currently, connecting causes what seems like way too many
* serial interrupts. Weird.
*/
public class PgbNetplay implements ActionListener {
boolean connected;
Socket socket;
PgbConnectionStatus pcs;
byte serialData;
byte serialControl;
byte lastSerialData;
boolean newSerialData;
boolean waitingOnSerial;
int serialCount;
int serialRetryCount;
boolean irTransmit;
boolean irReceive;
boolean irReadOn;
public void PgbNetPlay() {
socket = null;
connected = false;
serialCount = 0;
serialControl = 0;
irReadOn = false;
}
/**
* how many cycles until the next possible interrupt?
*/
public int cyclesLeft() {
if(!waitingOnSerial) {
// if we're not waiting on the serial port, then
// we're not going to interrupt
return Integer.MAX_VALUE;
}
return serialCount;
}
public byte cycle(int cv) {
// check the serial port
if(waitingOnSerial) {
// if we're waiting, decrement the count
serialCount -= cv;
if(serialCount <= 0) {
// when the count is reached,
doInput();
if(newSerialData) {
// yay, new data! return it.
serialData = lastSerialData;
newSerialData = false;
serialControl = 0;
waitingOnSerial = false;
if(connected) {
pcs.setStatus("read serial: " + serialData);
}
return PgbMemory.INT_SERIAL;
} else {
// no new data, retry or fail
if(serialRetryCount-- > 0) {
// retry in a lil' bit.
serialCount = 122 * 8 * 4 * 5;
} else {
// return last byte, I guess
serialData = lastSerialData;
newSerialData = false;
serialControl = 0;
waitingOnSerial = false;
if(connected) {
pcs.setStatus("read serial: " + serialData);
}
return PgbMemory.INT_SERIAL;
}
}
}
}
return 0;
}
public void setSerialData(byte data) {
//System.out.println("serial set:" + (data & 0xFF));
serialData = data;
}
public byte getSerialData() {
//System.out.println("serial returned:" + (serialData & 0xFF));
return serialData;
}
public void setSerialControl(byte control) {
serialControl = control;
if((serialControl & 0x80) == 0x80) {
// transfer data
if((serialControl & 0x01) == 0x01 || connected) {
// internal clock or connected, wait for a
// the count, then check and interrupt
waitingOnSerial = true;
serialCount = 122 * 8 * 4;
serialRetryCount = 2;
} else {
// external and not connected = ignore
waitingOnSerial = false;
}
if(connected) {
// write out the data
try {
socket.setTcpNoDelay(true);
OutputStream os = socket.getOutputStream();
os.write(0x03);
os.write(serialData & 0xFF);
pcs.setStatus("wrote serial: " + serialData);
} catch(IOException ex) {
//System.out.println("couldn't write");
}
}
} else {
waitingOnSerial = false;
}
}
public byte getSerialControl() {
return serialControl;
}
public void setIR(byte control) {
//System.out.println("write to GBC IR port:" + Integer.toHexString(control & 0xFF));
irTransmit = (control & 0x01) == 0x01;
irReadOn = (control & 0xC0) == 0xC0;
if(connected) {
try {
socket.setTcpNoDelay(true);
OutputStream os = socket.getOutputStream();
os.write(0x02);
os.write(irTransmit ? 1 : 0);
pcs.setStatus("wrote I/R: " + irTransmit);
} catch(IOException ex) {
//System.out.println("couldn't write");
}
}
}
public byte getIR() {
if(irReadOn) {
doInput();
}
//System.out.println("read from GBC IR port: " + Integer.toHexString(irReceive ? 0xC2 : 0xC0));
return irReceive ? (byte)0xC2 : (byte)0xC0;
}
public void doInput() {
int input;
int len;
byte[] data;
if(connected) {
try {
// don't block for long if no data
socket.setTcpNoDelay(true);
socket.setSoTimeout(1);
InputStream is = socket.getInputStream();
while(true) { // until throw...
input = is.read();
//System.out.println("sio read:" + input);
switch(input) {
case 0x10:
// game
len = is.read();
data = new byte[len];
is.read(data);
pcs.setGame(new String(data));
break;
case 0x11:
// emulator
len = is.read();
data = new byte[len];
is.read(data);
pcs.setGame(new String(data));
break;
case 0x12:
// version
len = is.read();
data = new byte[len];
is.read(data);
pcs.setGame(new String(data));
break;
case 0x02:
// I/R
irReceive = is.read() != 0;
pcs.setStatus("read I/R: " + irReceive);
break;
case 0x03:
// serial
lastSerialData = (byte)is.read();
newSerialData = true;
break;
}
}
} catch(IOException ex) {
//System.out.println("couldn't read");
}
} else {
// send disconnected signal to GB
lastSerialData = (byte)0xFF;
newSerialData = true;
}
}
public void sendInfo() {
if(connected) {
try {
socket.setTcpNoDelay(true);
OutputStream os = socket.getOutputStream();
os.write(0x10);
os.write(PgbSettings.gamestring.length());
os.write(PgbSettings.gamestring.getBytes());
} catch(IOException ex) {
//System.out.println("couldn't write");
}
}
}
public void popNetDialog(Frame frame) {
PgbNetDialog nd = new PgbNetDialog(frame);
nd.setLocation(frame.getLocation().x - 60, frame.getLocation().y + 80);
nd.setVisible(true);
if(nd.socket == null) {
connected = false;
return;
}
socket = nd.socket;
connected = true;
pcs = new PgbConnectionStatus(frame, this, socket);
pcs.setLocation(frame.getLocation().x - 310, frame.getLocation().y);
pcs.setVisible(true);
}
public void disconnect() {
if(connected) {
connected = false;
try {
socket.close();
} catch(Exception ex) {
// duh...
}
pcs.setVisible(false);
pcs.dispose();
}
}
public void actionPerformed(ActionEvent ev) {
if(ev.getActionCommand().equals("disconnect")) {
disconnect();
}
if(ev.getActionCommand().equals("close")) {
pcs.setVisible(false);
pcs.dispose();
}
}
}
/**
* PgbConnectionStatus is a non-modal dialog box displaying
* the netplay connection status.
*
* It is created by PgbNetplay when a connection is
* established and disposed of when the connection is
* closed.
*
* It has no real control over the connection socket and is
* entirely manipulated from within PgbNetplay.
*/
class PgbConnectionStatus extends Dialog {
String game, emulator, version;
Label connectedTo, playing, status;
TextField addressField, gameField, statusField;
Button disconnect;
public PgbConnectionStatus(Frame frame, ActionListener al, Socket socket) {
super(frame, "Pgb Connection Status", false);
setSize(300, 150);
setResizable(false);
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
c.insets = new Insets(2, 10, 2, 10);
connectedTo = new Label("Connected To:");
gridbag.setConstraints(connectedTo, c);
add(connectedTo);
addressField = new TextField(socket.getInetAddress().getHostName(), 20);
addressField.setEditable(false);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(addressField, c);
add(addressField);
playing = new Label("Playing:");
c.gridwidth = 1;
gridbag.setConstraints(playing, c);
add(playing);
gameField = new TextField("", 20);
gameField.setEditable(false);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(gameField, c);
add(gameField);
status = new Label("Status:");
c.gridwidth = 1;
gridbag.setConstraints(status, c);
add(status);
statusField = new TextField("", 20);
statusField.setEditable(false);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(statusField, c);
add(statusField);
disconnect = new Button("Disconnect");
disconnect.setActionCommand("disconnect");
disconnect.addActionListener(al);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(disconnect, c);
add(disconnect);
}
public void setStatus(String newstatus) {
statusField.setText(newstatus);
}
public void setGame(String game) {
this.game = game;
gameField.setText(game);
}
public void setEmulator(String emulator) {
this.emulator = emulator;
gameField.setText(game);
}
public void setVersion(String version) {
this.version = version;
gameField.setText(game);
}
public void disconnected() {
addressField.setText("DISCONNECTED");
disconnect.setLabel("Close");
disconnect.setActionCommand("close");
}
}
/**
* PgbNetDialog is a modal dialog box responsible for
* estabishling a remote connection.
*
* When the dialog is closed, socket should be either a
* connected socket if one was established, or null if
* it failed.
*/
class PgbNetDialog extends Dialog implements ActionListener{
Frame frame;
TextField status;
Button listen, connect, cancel;
public Socket socket;
public PgbNetDialog(Frame frame) {
super(frame, "Pgb Netplay", true);
this.frame = frame;
setSize(250, 90);
setResizable(false);
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
c.insets = new Insets(5, 10, 5, 10);
status = new TextField("", 30);
status.setEditable(false);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(status, c);
add(status);
listen = new Button("Listen");
listen.setActionCommand("listen");
listen.addActionListener(this);
c.gridwidth = 1;
gridbag.setConstraints(listen, c);
add(listen);
connect = new Button("Connect...");
connect.setActionCommand("connect");
connect.addActionListener(this);
gridbag.setConstraints(connect, c);
add(connect);
cancel = new Button("Cancel");
cancel.setActionCommand("cancel");
cancel.addActionListener(this);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(cancel, c);
add(cancel);
}
public void listen() {
ServerSocket ss;
status.setText("Listening...");
try{
ss = new ServerSocket(PgbSettings.netport);
ss.setSoTimeout(PgbSettings.netlistentimeout);
socket = ss.accept();
status.setText("Listening... connected to " + socket.getInetAddress().getHostName());
setVisible(false);
} catch(Exception e) {
status.setText("Listening... timed out");
socket = null;
}
}
public void connect() {
PgbConnectDialog cd = new PgbConnectDialog(frame);
String addressString;
InetAddress inetAddress;
cd.setLocation(this.getLocation().x + 30, this.getLocation().y + 30);
cd.setVisible(true);
addressString = cd.address;
if(addressString.length() < 1) {
return;
}
try {
inetAddress = InetAddress.getByName(addressString);
} catch(UnknownHostException ex) {
status.setText("Could not resolve connection address.");
return;
}
status.setText("Connecting to " + addressString + "...");
try {
socket = new Socket(inetAddress, PgbSettings.netport);
status.setText("Connected to " + addressString + ".");
setVisible(false);
} catch(Exception ex) {
status.setText("Connecting to " + addressString + "... timed out.");
socket = null;
return;
}
PgbSettings.lastnetaddress = addressString;
}
public void actionPerformed(ActionEvent ev) {
if(ev.getActionCommand().equals("listen")) {
listen();
}
if(ev.getActionCommand().equals("connect")) {
connect();
}
if(ev.getActionCommand().equals("cancel")) {
setVisible(false);
}
}
}
/**
* PgbConnectDialog is a modal dialog to enter the address to
* connect to.
*/
class PgbConnectDialog extends Dialog implements ActionListener {
public String address = "";
Label connectTo;
TextField addressField;
Button okay, cancel;
public PgbConnectDialog(Frame frame) {
super(frame, "Connect to...", true);
setSize(250, 90);
setResizable(false);
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
c.insets = new Insets(5, 10, 5, 10);
connectTo = new Label("Connect To:");
gridbag.setConstraints(connectTo, c);
add(connectTo);
okay = new Button("Okay");
okay.setActionCommand("okay");
okay.addActionListener(this);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(okay, c);
add(okay);
addressField = new TextField(PgbSettings.lastnetaddress, 20);
c.gridwidth = 1;
gridbag.setConstraints(addressField, c);
add(addressField);
cancel = new Button("Cancel");
cancel.setActionCommand("cancel");
cancel.addActionListener(this);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(cancel, c);
add(cancel);
}
public void actionPerformed(ActionEvent ev) {
if(ev.getActionCommand().equals("okay")) {
address = addressField.getText();
setVisible(false);
}
if(ev.getActionCommand().equals("cancel")) {
setVisible(false);
}
}
}