Created
October 30, 2013 20:06
-
-
Save Anaminus/7239332 to your computer and use it in GitHub Desktop.
Retrieves Roblox API data from one or more specified sources.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
function FetchAPI ( force, sources ) | |
Retrieves Roblox API data from one or more specified sources. | |
Returns two values: The unparsed API dump string, and a table of class | |
names and their corresponding explorer image indexes. | |
- `force`: If a source fails, use the next source to retrieve the data. | |
- `sources`: A list of source strings to retrieve the data from. Sources | |
are used in the order they appear in the list. May also be a single | |
source string. Valid sources: | |
- `local`: Gets the data from the user's Roblox installation. Fails if | |
either the Roblox Player or the Roblox Studio are not installed | |
(both are required). Generally the fasted method, as no internet | |
connection is required. However, it requires knowledge of the | |
current operating system, which is not fully supported. | |
- `wiki`: Gets the data from the Roblox Wiki. Data may not be the | |
latest version, and may contain errors. Data may be cached. | |
- `website`: Gets the data by downloading the necessary files directly | |
from setup.roblox.com. Data may be cached. | |
Dependencies: | |
- LuaFileSystem | |
Wiki Source: | |
- LuaSocket | |
Website Source: | |
- LuaSocket | |
- LuaZip | |
]] | |
-- Combines arguments into a path, and normalizes | |
local function path(...) | |
local a = {...} | |
local p = a[1] or '' | |
for i = 2,#a do | |
p = p .. '/' .. a[i] | |
end | |
return p:gsub('[\\/]+','/') | |
end | |
-- Returns a directory of a Roblox installation. | |
-- `type`: "player" or "studio" | |
-- Implementation is OS dependent. | |
local function getRobloxDir(type) | |
-- Windows 7 64-bit | |
local lfs = require 'lfs' | |
local versions = 'C:/Program Files (x86)/Roblox/Versions/' | |
for dir in lfs.dir(versions) do | |
local version = path(versions,dir) | |
if dir ~= '.' and dir ~= '..' and lfs.attributes(version, 'mode') == 'directory' then | |
local exe = type == 'studio' and 'RobloxStudioBeta.exe' or 'RobloxPlayerBeta.exe' | |
local f = io.open(path(version,exe),'rb') | |
if f then | |
f:close() | |
return version | |
end | |
end | |
end | |
return nil,'could not find installation' | |
end | |
local Source = {} | |
-- Get data from the user's Roblox installation | |
Source['local'] = function(rbxPlayerDir,rbxStudioDir) | |
local rbxPlayerDir = rbxPlayerDir or getRobloxDir('player') | |
if not rbxPlayerDir then | |
return nil,'Roblox player not installed' | |
end | |
local rbxStudioDir = rbxStudioDir or getRobloxDir('studio') | |
if not rbxStudioDir then | |
return nil,'Roblox studio not installed' | |
end | |
-- get reflection metadata | |
local rmd do | |
local a = io.open(path(rbxStudioDir,'ReflectionMetadata.xml'),'r') | |
if not a then | |
return nil,'could not find ReflectionMetadata in studio installation' | |
end | |
local b = io.open(path(rbxPlayerDir,'ReflectionMetadata.xml'),'w') | |
rmd = a:read('*a') | |
-- copy to player folder for API dump | |
b:write(rmd) | |
b:flush() | |
a:close() | |
b:close() | |
end | |
-- dump API | |
local apiDump do | |
local lfs = require 'lfs' | |
local dir = lfs.currentdir() | |
lfs.chdir(rbxPlayerDir) | |
if os.execute('RobloxPlayerBeta --API api.dmp') ~= 0 then | |
lfs.chdir(dir) | |
return nil,'failed to dump API' | |
end | |
local f = io.open('api.dmp','r') | |
if not f then | |
lfs.chdir(dir) | |
return nil,'failed to find API dump' | |
end | |
apiDump = f:read('*a') | |
f:close() | |
os.remove('api.dmp') | |
lfs.chdir(dir) | |
end | |
local explorerIndex = {} | |
for class,index in rmd:gmatch('<Item class="ReflectionMetadataClass">.-<string name="Name">(.-)</string>.-<string name="ExplorerImageIndex">(.-)</string>') do | |
explorerIndex[class] = tonumber(index) | |
end | |
return apiDump,explorerIndex | |
end | |
-- Get data from the Roblox Wiki | |
Source['wiki'] = function() | |
local function cachedGet(url,file,bin,exp) | |
local http = require 'socket.http' | |
local lfs = require 'lfs' | |
local cache = path(os.getenv('TEMP'),'lua-get-cache/') | |
lfs.mkdir(cache) | |
file = cache .. file | |
bin = bin and 'b' or '' | |
local content | |
local mod = lfs.attributes(file,'modification') | |
if mod and os.difftime(os.time(),mod) < (exp or 1*60*60*24) then | |
-- if the last time the file was updated is within the expiration time | |
-- get the content from the cache | |
local f = io.open(file,'r' .. bin) | |
if f then | |
content = f:read('*a') | |
f:close() | |
end | |
end | |
if not content then | |
-- if the cached file expired or cached file did not exist | |
-- send the request | |
local src,err = http.request(url) | |
if src then | |
content = src | |
-- update the cache | |
local f = io.open(file,'w' .. bin) | |
if f then | |
f:write(src) | |
f:flush() | |
f:close() | |
end | |
else | |
return nil,err | |
end | |
end | |
return content | |
end | |
local apiDump = cachedGet( | |
[[http://wiki.roblox.com/index.php?title=Class_reference/API_dump/raw&action=raw]], | |
'APIDump.txt' | |
) | |
local explorerIndex = cachedGet( | |
[[http://wiki.roblox.com/index.php?title=Class_reference/Explorer_index/raw&action=raw]], | |
'ExplorerIndex.txt' | |
) | |
local data = {} | |
for class,index in explorerIndex:gmatch('([^\r\n]+)\t(%d+)') do | |
data[class] = tonumber(index) | |
end | |
return apiDump,data | |
end | |
--[[ | |
Files required to successfully dump API: | |
RobloxPlayerBeta.exe | |
AppSettings.xml | |
boost.dll | |
fmodex.dll | |
Log.dll | |
OgreMain.dll | |
tbb.dll | |
content (directory) | |
ReflectionMetadata.xml | |
Server: | |
http://setup.roblox.com | |
Version hashes: | |
versionPlayer: /version | |
versionStudio: /versionQTStudio | |
Archives: | |
/version-<versionPlayer>-RobloxApp.zip | |
/version-<versionPlayer>-Libraries.zip | |
/version-<versionStudio>-RobloxStudio.zip | |
Each file is usually a couple MB in size, hence why this is the slowest | |
method. | |
Manual: | |
AppSettings.xml | |
<?xml version="1.0" encoding="UTF-8"?> | |
<Settings> | |
<ContentFolder>content</ContentFolder> | |
<BaseUrl>http://www.roblox.com</BaseUrl> | |
</Settings> | |
]] | |
-- Get data directly from install server | |
Source['website'] = function() | |
local lfs = require 'lfs' | |
local http = require 'socket.http' | |
local zip = require 'zip' | |
-- Base domain. Might be useful for fetching from test sites. | |
local base = 'roblox.com' | |
-- Temp directory for downloaded files. | |
local tmp = path(os.getenv('TEMP'),'lua-get-cache/') | |
lfs.mkdir(tmp) | |
-- Get latest version hashes of player and studio. | |
local versionPlayer = http.request('http://setup.' .. base .. '/version') | |
local versionStudio = http.request('http://setup.' .. base .. '/versionQTStudio') | |
local playerDir = path(tmp,versionPlayer) | |
local studioDir = path(tmp,versionStudio) | |
-- zip file name; unzip location | |
local zips = {} | |
local function exists(file) | |
return not not lfs.attributes(file) | |
end | |
-- If player needs updating | |
if not exists(playerDir) then | |
lfs.mkdir(playerDir) | |
-- AppSettings must be created manually | |
local app = io.open(path(playerDir,'AppSettings.xml'),'w') | |
app:write([[ | |
<?xml version="1.0" encoding="UTF-8"?> | |
<Settings> | |
<ContentFolder>content</ContentFolder> | |
<BaseUrl>http://www.]] .. base .. [[</BaseUrl> | |
</Settings>]]) | |
app:flush() | |
app:close() | |
-- Content directory is required by the exe | |
lfs.mkdir(path(playerDir,'content')) | |
zips[#zips+1] = {versionPlayer .. '-RobloxApp.zip', playerDir} | |
zips[#zips+1] = {versionPlayer .. '-Libraries.zip', playerDir} | |
end | |
-- If studio needs updating | |
if not exists(studioDir) then | |
lfs.mkdir(studioDir) | |
zips[#zips+1] = {versionStudio .. '-RobloxStudio.zip', studioDir}; | |
end | |
if #zips > 0 then | |
-- Temp zip file location | |
local ztmp = os.tmpname() | |
-- Get any files that need updating | |
for i = 1,#zips do | |
local zipn = zips[i][1] | |
local dir = zips[i][2] | |
-- Request the current zip file | |
http.request{ | |
url = 'http://setup.' .. base .. '/' .. zipn; | |
sink = ltn12.sink.file(io.open(ztmp,'wb')); | |
} | |
-- Unzip | |
local zipfile = zip.open(ztmp) | |
if not zipfile then | |
return nil,'failed to get file `' .. zipn .. '`' | |
end | |
for data in zipfile:files() do | |
local filename = data.filename | |
if filename:sub(-1,-1) ~= '/' then | |
-- If file is not a directory | |
-- Copy file to given folder | |
local zfile = assert(zipfile:open(filename)) | |
local file = assert(io.open(path(dir,filename),'wb')) | |
file:write(zfile:read('*a')) | |
file:flush() | |
file:close() | |
zfile:close() | |
end | |
end | |
zipfile:close() | |
end | |
-- remote temp zip file | |
os.remove(ztmp) | |
end | |
-- Now local source can properly handle the files | |
return Source['local'](playerDir,studioDir) | |
end | |
return function(force,sources) | |
sources = sources or {'website','wiki','local'} | |
if type(sources) == 'string' then | |
sources = {sources} | |
end | |
for i = 1,#sources do | |
local func = Source[sources[i]] | |
if not func then | |
error('`' .. tostring(sources[i]) .. '` is not a valid source',2) | |
end | |
local apiDump,explorerIndex = func() | |
if apiDump then | |
return apiDump,explorerIndex | |
elseif not force then | |
error(explorerIndex,2) | |
end | |
end | |
error('all sources failed',2) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment