diff --git a/Copyright.md b/Copyright.md new file mode 100644 index 0000000..d4a81b4 --- /dev/null +++ b/Copyright.md @@ -0,0 +1,13 @@ +Copyright 2014 Stephen Leitnick + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 576ca67..7700d09 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ -# RobloxLauncher -A RobloxLauncher made using the RobloxProxy.dll. and Launch ROBLOX without the website, given the Place ID. +RobloxLauncher +============== + +Launch ROBLOX without the website, given the Place ID. + +Requires Java 1.6 or newer. + + +I created this application because I can no longer launch ROBLOX games from their website (on any browser). When I attempt to, +the ROBLOX updater window would pop up and sit there for several minutes, but never would launch the game. This application +shortcuts the process and launches the game by itself. diff --git a/RobloxLauncher/.classpath b/RobloxLauncher/.classpath new file mode 100644 index 0000000..c24d1b4 --- /dev/null +++ b/RobloxLauncher/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/RobloxLauncher/.gitignore b/RobloxLauncher/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/RobloxLauncher/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/RobloxLauncher/.project b/RobloxLauncher/.project new file mode 100644 index 0000000..3e06a36 --- /dev/null +++ b/RobloxLauncher/.project @@ -0,0 +1,17 @@ + + + RobloxLauncher + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/RobloxLauncher/.settings/org.eclipse.jdt.core.prefs b/RobloxLauncher/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3a21537 --- /dev/null +++ b/RobloxLauncher/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/Browser.java b/RobloxLauncher/src/sleitnick/roblox/launcher/Browser.java new file mode 100644 index 0000000..353388f --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/Browser.java @@ -0,0 +1,58 @@ +package sleitnick.roblox.launcher; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +public final class Browser { + + private static final Desktop DESKTOP = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + + /** + * Check if Browser support is available + * @return isSupported + */ + public static boolean isSupported() { + return DESKTOP.isSupported(Desktop.Action.BROWSE); + } + + /** + * Browse to the given URI + * @param uri {@link URI} + */ + public static void browse(URI uri) { + try { + DESKTOP.browse(uri); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Browse to the given URL + * @param url {@link URL} + */ + public static void browse(URL url) { + try { + browse(url.toURI()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + + /** + * Browse to the given URL + * @param url {@link String} URL + */ + public static void browse(String url) { + try { + browse(new URL(url)); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/DataStoreService.java b/RobloxLauncher/src/sleitnick/roblox/launcher/DataStoreService.java new file mode 100644 index 0000000..d2e3b56 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/DataStoreService.java @@ -0,0 +1,94 @@ +package sleitnick.roblox.launcher; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public final class DataStoreService { + + private static final class Data implements Serializable { + private static final long serialVersionUID = 1L; + private final Map data = new HashMap(); + } + + private static final String DATA_DIR_NAME = "data"; + private static final String DATA_FILE_NAME = "data.ser"; + + private static Data data = null; + + // Load data: + static { + File dataDir = new File(DATA_DIR_NAME); + if (dataDir.isDirectory()) { + File dataFile = new File(dataDir, DATA_FILE_NAME); + try { + FileInputStream fis = new FileInputStream(dataFile); + ObjectInputStream ois = new ObjectInputStream(fis); + Object objData = ois.readObject(); + ois.close(); + fis.close(); + if (objData instanceof Data) { + data = (Data)objData; + } + } catch (Exception e) { + } + } else { + dataDir.mkdir(); + } + if (data == null) { + data = new Data(); + } + } + + /** + * Set a value in the data store + * @param key {@link String} key + * @param value Value + */ + protected static void set(String key, Object value) { + data.data.put(key, value); + } + + /** + * Get value from given key + * @param key {@link String} key + * @return {@link Object} Value + */ + protected static Object get(String key) { + return data.data.get(key); + } + + /** + * Save the data in the data store + */ + protected static void save() { + File dataDir = new File(DATA_DIR_NAME); + if (!dataDir.isDirectory()) { + dataDir.mkdir(); + } + File dataFile = new File(dataDir, DATA_FILE_NAME); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + FileOutputStream fos = new FileOutputStream(dataFile); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(data); + oos.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/HttpService.java b/RobloxLauncher/src/sleitnick/roblox/launcher/HttpService.java new file mode 100644 index 0000000..7cf27c7 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/HttpService.java @@ -0,0 +1,54 @@ +package sleitnick.roblox.launcher; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public final class HttpService { + + private static final Map RESPONSE_CACHE = new HashMap(); + + /** + * Get the HTTP response from the given URL. + * @param urlStr {@link String} URL + * @param cacheResponse Whether or not to cache the response + * @return {@link String} response + */ + public static final String get(String urlStr, boolean cacheResponse) { + if (cacheResponse) { + String fromCache = RESPONSE_CACHE.get(urlStr); + if (fromCache != null) { + return fromCache; + } + } + URL url; + HttpURLConnection conn; + BufferedReader rd; + String line; + String result = ""; + try { + url = new URL(urlStr); + conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); + while ((line = rd.readLine()) != null) { + result += line; + } + rd.close(); + } catch(IOException e) { + result = ""; + } catch(Exception e) { + result = ""; + } + if (cacheResponse) { + RESPONSE_CACHE.put(urlStr, result); + } + return result; + } + + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/IntegerDocumentFilter.java b/RobloxLauncher/src/sleitnick/roblox/launcher/IntegerDocumentFilter.java new file mode 100644 index 0000000..28c4fb2 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/IntegerDocumentFilter.java @@ -0,0 +1,56 @@ +package sleitnick.roblox.launcher; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.DocumentFilter; + +public class IntegerDocumentFilter extends DocumentFilter { + + private boolean isUnsignedInteger(String str) { + if (str.isEmpty()) return true; + try { + Integer.parseUnsignedInt(str); + return true; + } catch(NumberFormatException e) { + return false; + } + } + + @Override + public void insertString(FilterBypass fb, int offset, String string, + AttributeSet attr) throws BadLocationException { + Document doc = fb.getDocument(); + StringBuilder sb = new StringBuilder(); + sb.append(doc.getText(0, doc.getLength())); + sb.insert(offset, string); + if (isUnsignedInteger(sb.toString())) { + super.insertString(fb, offset, string, attr); + } + } + + @Override + public void remove(FilterBypass fb, int offset, int length) + throws BadLocationException { + Document doc = fb.getDocument(); + StringBuilder sb = new StringBuilder(); + sb.append(doc.getText(0, doc.getLength())); + sb.delete(offset, offset + length); + if (isUnsignedInteger(sb.toString())) { + super.remove(fb, offset, length); + } + } + + @Override + public void replace(FilterBypass fb, int offset, int length, String text, + AttributeSet attrs) throws BadLocationException { + Document doc = fb.getDocument(); + StringBuilder sb = new StringBuilder(); + sb.append(doc.getText(0, doc.getLength())); + sb.replace(offset, offset + length, text); + if (isUnsignedInteger(sb.toString())) { + super.replace(fb, offset, length, text, attrs); + } + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/LuaFileException.java b/RobloxLauncher/src/sleitnick/roblox/launcher/LuaFileException.java new file mode 100644 index 0000000..2971a9c --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/LuaFileException.java @@ -0,0 +1,22 @@ +package sleitnick.roblox.launcher; + +public class LuaFileException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String msg; + + public LuaFileException() { + this("Lua file exception"); + } + + public LuaFileException(String msg) { + this.msg = msg; + } + + @Override + public String getMessage() { + return msg; + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/LuaScript.java b/RobloxLauncher/src/sleitnick/roblox/launcher/LuaScript.java new file mode 100644 index 0000000..1ec236b --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/LuaScript.java @@ -0,0 +1,70 @@ +package sleitnick.roblox.launcher; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.JsePlatform; + + + +public class LuaScript { + + private static final ArrayList LUA_VALID_EXT = new ArrayList() { + private static final long serialVersionUID = 1L; + { + this.add(".lua"); + this.add(".txt"); + } + }; + + private static String getSourceFromStream(InputStream stream) throws IOException { + StringBuilder sb = new StringBuilder(); + int ch; + while ((ch = stream.read()) != -1) { + sb.append((char)ch); + } + return sb.toString(); + } + + private String source; + + private boolean isValidName(String name) { + name = name.toLowerCase(); + for (String ext : LUA_VALID_EXT) { + if (name.endsWith(ext.toLowerCase())) { + return true; + } + } + return false; + } + + /** + * Create a Lua script from the given name + *

+ * Will search the class for the given file + * @param name Script name + */ + public LuaScript(String name) { + if (!isValidName(name)) { + throw new LuaFileException("Wrong Lua file type"); + } + InputStream stream = getClass().getResourceAsStream(name); + try { + String source = getSourceFromStream(stream); + this.source = source; + } catch(IOException e) { + e.printStackTrace(); + } + } + + public LuaValue execute(String url) { + Globals _G = JsePlatform.standardGlobals(); + _G.set("httpResponse", url); + LuaValue chunk = _G.load(source); + return chunk.call(); + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncher.java b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncher.java new file mode 100644 index 0000000..807a369 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncher.java @@ -0,0 +1,65 @@ +package sleitnick.roblox.launcher; + +import java.awt.Frame; +import java.io.File; +import java.io.IOException; + +public final class RobloxLauncher { + + public static final String URL_ROBLOX_VERSION = "http://setup.roblox.com/version"; + public static final String URL_REQUEST_GAME = "http://www.roblox.com/Game/PlaceLauncher.ashx?request=RequestGame&placeId=#ID"; + public static final String URL_REQUEST_GROUP_BUILD_GAME = "http://www.roblox.com/Game/PlaceLauncher.ashx?request=RequestGroupBuildGame&placeId=#ID"; + public static final String URL_DOWNLOAD_ROBLOX = "http://www.roblox.com/install/setup.ashx"; + + private static String robloxVersion; + static { + String response = HttpService.get(URL_ROBLOX_VERSION, false); + robloxVersion = response; + } + + private static final String LOCAL_APP_DATA = System.getenv("LOCALAPPDATA"); + private static final File RBX_DIR = new File(LOCAL_APP_DATA, "Roblox/Versions/" + robloxVersion); + + + /** + * Current version installed + * @return true if current version is installed + */ + public static boolean hasCurrentVersion() { + return RBX_DIR.exists(); + } + + /** + * Launch the given place + * @param place Place to launch + * @param frame Frame to launch from + * @throws RobloxVersionException + */ + protected static void launch(RobloxPlace place, Frame frame) throws RobloxVersionException { + + if (!hasCurrentVersion()) { + throw new RobloxVersionException(); + } + + String joinRequest = HttpService.get((place.isPersonalServer() ? URL_REQUEST_GROUP_BUILD_GAME : URL_REQUEST_GAME).replaceAll("#ID", Integer.toString(place.getPlaceId())), false); + + if (joinRequest.contains("JoinPlace=") || joinRequest.contains("Game/Join.ashx")) { + frame.setVisible(false); + try { + String launchCmd = (RBX_DIR.getAbsolutePath() + "/RobloxPlayerBeta.exe --id " + place.getPlaceId()); + Process rbxProcess = Runtime.getRuntime().exec(launchCmd); + rbxProcess.waitFor(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + frame.setVisible(true); + } else { + System.out.println("Join request failed"); + System.out.println(joinRequest); + } + + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncherApp.java b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncherApp.java new file mode 100644 index 0000000..785e642 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxLauncherApp.java @@ -0,0 +1,227 @@ +package sleitnick.roblox.launcher; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.net.URL; + +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; +import javax.swing.text.PlainDocument; + +public class RobloxLauncherApp implements ActionListener { + + private JFrame frame; + private JPanel panel; + private JPanel gamePanel; + private JLabel logoLabel; + private JLabel idLabel; + private JTextField idField; + private JButton searchButton; + private JLabel gameTitle, gameCreator; + private JLabel gameImage; + private JButton launchButton; + private JButton launchBrowserButton; + + private final Color background = new Color(75, 75, 75); + + private final String ID_INPUT_KEY = "idInput"; + + private RobloxPlace currentSearch = null; + + private void createGui() { + frame = new JFrame("ROBLOX Launcher"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setResizable(false); + frame.getContentPane().setBackground(background); + panel = new JPanel(); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + panel.setBackground(background); + gamePanel = new JPanel(); + gamePanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + gamePanel.setLayout(new BoxLayout(gamePanel, BoxLayout.PAGE_AXIS)); + gamePanel.setBackground(background); + frame.getContentPane().add(panel, BorderLayout.CENTER); + frame.getContentPane().add(gamePanel, BorderLayout.SOUTH); + URL logoURL = getClass().getResource("logo.png"); + ImageIcon logo = new ImageIcon(logoURL); + logoLabel = new JLabel(logo); + logoLabel.setBorder(new EmptyBorder(10, 10, 10, 10)); + idLabel = new JLabel("Place ID:"); + idLabel.setFont(new Font("Arial", Font.BOLD, 14)); + idLabel.setForeground(new Color(255, 255, 255)); + idField = new JTextField(20); + idField.setFont(new Font("Arial", Font.PLAIN, 12)); + { + Object idObj = DataStoreService.get(ID_INPUT_KEY); + if (idObj != null && idObj instanceof Integer) { + int id = (Integer)idObj; + idField.setText(Integer.toString(id)); + } + } + idLabel.setLabelFor(idField); + searchButton = new JButton("Search"); + searchButton.setBackground(background); + searchButton.setFont(new Font("Arial", Font.PLAIN, 12)); + frame.getContentPane().add(logoLabel, BorderLayout.NORTH); + panel.add(idLabel); + panel.add(idField); + panel.add(searchButton); + gameTitle = new JLabel(); + gameTitle.setBorder(new EmptyBorder(10, 0, 0, 0)); + gameTitle.setFont(new Font("Arial", Font.PLAIN, 18)); + gameTitle.setForeground(new Color(255, 255, 255)); + gameCreator = new JLabel(); + gameCreator.setFont(new Font("Arial", Font.PLAIN, 14)); + gameCreator.setForeground(new Color(255, 255, 255)); + gameImage = new JLabel(); + gameImage.setBorder(new EmptyBorder(10, 0, 10, 0)); + JSeparator sep = new JSeparator(JSeparator.HORIZONTAL); + sep.setBackground(new Color(100, 100, 100)); + sep.setForeground(background); + gamePanel.add(sep); + gamePanel.add(gameTitle); + gamePanel.add(gameCreator); + gamePanel.add(gameImage); + launchButton = new JButton("Play"); + launchButton.setBackground(background); + launchButton.setFont(new Font("Arial", Font.BOLD, 14)); + launchBrowserButton = new JButton("Open in Browser"); + launchBrowserButton.setBackground(background); + launchBrowserButton.setFont(new Font("Arial", Font.PLAIN, 14)); + gamePanel.add(launchBrowserButton); + gamePanel.add(launchButton); + frame.pack(); + frame.setLocation(100, 100); + URL iconURL = getClass().getResource("icon.png"); + ImageIcon icon = new ImageIcon(iconURL); + frame.setIconImage(icon.getImage()); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + DataStoreService.save(); + super.windowClosing(e); + } + }); + frame.setVisible(true); + } + + private void setIdFieldIntegerOnly() { + PlainDocument doc = (PlainDocument)idField.getDocument(); + doc.setDocumentFilter(new IntegerDocumentFilter()); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("launchGame")) { + if (!launchButton.isEnabled()) { + return; + } + String idStr = idField.getText().trim(); + if (!idStr.isEmpty()) { + int id = Integer.parseInt(idStr); + launchButton.setEnabled(false); + try { + RobloxPlace place = new RobloxPlace(id); + RobloxLauncher.launch(place, frame); + } catch(RobloxVersionException rbxVersionException) { + Browser.browse(RobloxLauncher.URL_DOWNLOAD_ROBLOX); + System.exit(0); + } catch (RobloxPlaceException e1) { + gameTitle.setText("Invalid place ID"); + gameCreator.setText(""); + gameImage.setIcon(null); + launchButton.setVisible(false); + launchBrowserButton.setVisible(false); + } + launchButton.setEnabled(true); + } + } else if (e.getActionCommand().equals("searchGame")) { + if (!idField.getText().isEmpty()) { + int id = Integer.parseInt(idField.getText()); + try { + frame.getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + idField.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + RobloxPlace place = new RobloxPlace(id); + currentSearch = place; + gameTitle.setText(place.getPlaceName()); + gameCreator.setText("Creator: " + place.getCreator()); + try { + URL url = new URL(place.getThumbnail()); + ImageIcon gameIcon = new ImageIcon(url); + Image img = gameIcon.getImage(); + img = img.getScaledInstance((int)(img.getWidth(null) / 1.2f), (int)(img.getHeight(null) / 1.2f), Image.SCALE_SMOOTH); + gameIcon = new ImageIcon(img); + gameImage.setIcon(gameIcon); + } catch (Exception e1) { + gameImage.setIcon(null); + } + DataStoreService.set(ID_INPUT_KEY, id); + launchButton.setVisible(true); + launchBrowserButton.setVisible(true); + frame.pack(); + } catch (RobloxPlaceException e1) { + gameTitle.setText(e1.getFailedToConnect() ? "Cannot connect to ROBLOX" : "Invalid place ID"); + gameCreator.setText(""); + gameImage.setIcon(null); + launchButton.setVisible(false); + launchBrowserButton.setVisible(false); + } finally { + frame.getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + idField.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + } + frame.pack(); + } + } else if (e.getActionCommand().equals("openInBrowser")) { + if (currentSearch != null) { + currentSearch.openInBrowser(); + } + } + } + + public RobloxLauncherApp() { + createGui(); + setIdFieldIntegerOnly(); + searchButton.setActionCommand("searchGame"); + searchButton.addActionListener(this); + launchButton.setActionCommand("launchGame"); + launchButton.addActionListener(this); + launchBrowserButton.setActionCommand("openInBrowser"); + launchBrowserButton.addActionListener(this); + idField.setActionCommand("searchGame"); + idField.addActionListener(this); + idField.requestFocus(); + idField.setCaretPosition(idField.getText().length()); + if (!idField.getText().isEmpty()) { + for (ActionListener listener : idField.getActionListeners()) { + listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "searchGame") { + private static final long serialVersionUID = 1L; + }); + } + } + } + + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + new RobloxLauncherApp(); + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlace.java b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlace.java new file mode 100644 index 0000000..f752b29 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlace.java @@ -0,0 +1,132 @@ +package sleitnick.roblox.launcher; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; + +import org.luaj.vm2.LuaValue; + + + +public class RobloxPlace implements Serializable { + + private static final long serialVersionUID = 1L; + private static final String PLACE_URL = "http://www.roblox.com/PlaceItem.aspx?id=#ID"; + + private final int placeId; + private String placeName; + private URI placeUri = null; + + private String creator; + private int creatorId; + + private String thumbnail; + + private boolean isPersonalServer; + + private void getPlaceInformation() throws RobloxPlaceException { + String url = PLACE_URL.replaceAll("#ID", Integer.toString(placeId)); + try { + placeUri = new URL(url).toURI(); + } catch (MalformedURLException e1) { + } catch (URISyntaxException e1) { + } + String response = HttpService.get(url, true); + if (response.isEmpty()) { + throw new RobloxPlaceException("Failed to connect to ROBLOX", placeId, true); + } + LuaScript parseResponse = new LuaScript("parse_place_response.lua"); + LuaValue luaResponse = parseResponse.execute(response); + if (luaResponse == LuaValue.FALSE) { + throw new RobloxPlaceException(placeId); + } else { + try { + placeName = URLDecoder.decode(luaResponse.get("Name").tojstring(), "ASCII"); + } catch (UnsupportedEncodingException e) { + placeName = luaResponse.get("Name").tojstring(); + } + try { + thumbnail = URLDecoder.decode(luaResponse.get("Thumbnail").tojstring(), "UTF-8"); + } catch(UnsupportedEncodingException e) { + thumbnail = luaResponse.get("Thumbnail").tojstring(); + } + try { + creator = URLDecoder.decode(luaResponse.get("Creator").tojstring(), "UTF-8"); + } catch(UnsupportedEncodingException e) { + creator = luaResponse.get("Creator").tojstring(); + } + creatorId = luaResponse.get("CreatorID").toint(); + isPersonalServer = luaResponse.get("IsPersonalServer").toboolean(); + } + } + + /** + * Reflect a Roblox place + * @param placeId Place ID representing the place + * @throws RobloxPlaceException + */ + public RobloxPlace(int placeId) throws RobloxPlaceException { + this.placeId = placeId; + getPlaceInformation(); + } + + /** + * Get the place ID + * @return Place ID + */ + public int getPlaceId() { + return placeId; + } + + /** + * Get the place name + * @return Place name + */ + public String getPlaceName() { + return placeName; + } + + /** + * Get the creator ID + * @return Creator ID + */ + public int getCreatorId() { + return creatorId; + } + + /** + * Get the creator name + * @return Creator name + */ + public String getCreator() { + return creator; + } + + /** + * Get the thumbnail URL + * @return Thumbnail URL + */ + public String getThumbnail() { + return thumbnail; + } + + /** + * Whether or not the place is a personal server + * @return if personal server + */ + public boolean isPersonalServer() { + return isPersonalServer; + } + + /** + * Open the place in the browser + */ + public void openInBrowser() { + Browser.browse(placeUri); + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlaceException.java b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlaceException.java new file mode 100644 index 0000000..3f47644 --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxPlaceException.java @@ -0,0 +1,48 @@ +package sleitnick.roblox.launcher; + + + +public class RobloxPlaceException extends Exception { + + private static final long serialVersionUID = 1L; + + private final String msg; + private final int id; + private final boolean failedToConnect; + + public RobloxPlaceException() { + this("Invalid ROBLOX place", -1, false); + } + + public RobloxPlaceException(String msg) { + this(msg, -1, false); + } + + public RobloxPlaceException(int id) { + this("The given ID(" + id + ") is not a valid ROBLOX place", id, false); + } + + public RobloxPlaceException(String msg, int id) { + this(msg, id, false); + } + + public RobloxPlaceException(String msg, int id, boolean failedToConnect) { + this.msg = msg; + this.id = id; + this.failedToConnect = failedToConnect; + } + + @Override + public String getMessage() { + return msg; + } + + public int getID() { + return id; + } + + public boolean getFailedToConnect() { + return failedToConnect; + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxVersionException.java b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxVersionException.java new file mode 100644 index 0000000..6feaf7b --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/RobloxVersionException.java @@ -0,0 +1,26 @@ +package sleitnick.roblox.launcher; + + + +public class RobloxVersionException extends Exception { + + private static final long serialVersionUID = 1L; + + private static final String DEFAULT_MSG = "Current ROBLOX version not installed"; + + private final String msg; + + public RobloxVersionException() { + msg = DEFAULT_MSG; + } + + public RobloxVersionException(String msg) { + this.msg = msg; + } + + @Override + public String getMessage() { + return msg; + } + +} diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/icon.png b/RobloxLauncher/src/sleitnick/roblox/launcher/icon.png new file mode 100644 index 0000000..7b60676 Binary files /dev/null and b/RobloxLauncher/src/sleitnick/roblox/launcher/icon.png differ diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/logo.png b/RobloxLauncher/src/sleitnick/roblox/launcher/logo.png new file mode 100644 index 0000000..b8804d0 Binary files /dev/null and b/RobloxLauncher/src/sleitnick/roblox/launcher/logo.png differ diff --git a/RobloxLauncher/src/sleitnick/roblox/launcher/parse_place_response.lua b/RobloxLauncher/src/sleitnick/roblox/launcher/parse_place_response.lua new file mode 100644 index 0000000..94f96ef --- /dev/null +++ b/RobloxLauncher/src/sleitnick/roblox/launcher/parse_place_response.lua @@ -0,0 +1,42 @@ +-- Parse Place Response +-- Stephen Leitnick +-- October 19, 2014 + +-- Is given the global variable 'httpResponse' which is a string + + + +-- Get PlaceID from the Play button (which assumes non-place assets will NOT have the button): +local placeId = ( + isPersonalServer + and + httpResponse:match([[class="VisitButton VisitButtonPersonalServer" placeid="(.-)">]]) + or + httpResponse:match([[class="VisitButton VisitButtonPlay" placeid="(.-)">]]) +) + +-- If response shows that the ID given isn't a ROBLOX place: +if (not placeId) then + return false +end + +-- Parse info: +local placeName = httpResponse:match([[

(.-)

]]) +local placeCreatorId, placeCreatorName = httpResponse:match([[(.-)]]) + +-- Get place thumbnail: +local placeThumbnail = httpResponse:match([[