Skip to content

Instantly share code, notes, and snippets.

@Anaminus
Created October 30, 2013 20:06
Show Gist options
  • Save Anaminus/7239332 to your computer and use it in GitHub Desktop.
Save Anaminus/7239332 to your computer and use it in GitHub Desktop.
Retrieves Roblox API data from one or more specified sources.
--[[
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