Created
September 21, 2015 19:24
-
-
Save goto-bus-stop/a5b42da7558cedbc9a08 to your computer and use it in GitHub Desktop.
RCS deobf
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
#!/bin/sh | |
mkdir rcs/$(date '+%m%d') | |
curl https://code.radiant.dj/rs.min.js -o rcs/$(date '+%m%d')/rs.min.js | |
node unobfuscate rcs/$(date '+%m%d')/rs.min.js |
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
{ | |
"dependencies": { | |
"escodegen": "", | |
"esprima": "", | |
"esrefactor": "", | |
"estraverse": "" | |
} | |
} |
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
iterate(spec, object) | |
getModule(spec) | |
API.getUserByName(name) | |
f(command) | |
validURL(url) | |
htmlspecialchars(string) | |
striphtml(string) | |
isNumber(string) | |
sendSocket(action, param) | |
weirdEscape(string) | |
spawnNotification(title, message) | |
secondsToTimeClean(seconds) | |
addChatLog(className, iconClass, title, message) | |
addChatLogSmall(className, iconClass, title, message) | |
addRawChatLog(className, iconClass, title, message) | |
addRawChatLogSmall(className, iconClass, title, message) | |
addChatReceive(className, user, message) | |
contextClass(user) | |
getSpecialRank(uid) | |
getUserFromArgs(string) | |
langVar(string, values) | |
getFiles(done) | |
setCookie(name, value) | |
getCookie(name) | |
cleanASCII(string) | |
rsDelete(cid) | |
rsDebugMode(log) | |
parseDescription(description) | |
avatar(avatarId) | |
badge(badgeId) | |
language(langId) | |
rsCheckUpdates(__) | |
rsCheckUpdatesForced(__) | |
rsCheckLocation(__) | |
getSessionData(uid, key, defaultValue) | |
setSessionData(uid, key, value) | |
__onWaitlistUpdate(update) | |
__onWait_list_update(update) | |
__onDJAdvance(advance) | |
__autoJoin(advance) | |
__onChat(message) | |
rs.socket.onmessage(message) | |
__getPlaylists(force) | |
__onVoteUpdate(vote) | |
__onUserJoin(user) | |
__onUserLeave(user) | |
__onGrabUpdate(grab) | |
__onChatCommand(text) | |
__onScoreUpdate(score) | |
__onSkip(__) | |
__roomState(state) | |
__autoMute(advance) | |
__voteRecording(vote) | |
__grabRecording(grab) | |
__argumentParser(string) | |
__commandController(string) | |
__lengthAlert(advance) | |
__smartVote(advance) | |
__castVote(advance) | |
__userJoin(user) | |
__userLeave(user) | |
__userWoot(vote) | |
__userMeh(vote) | |
__userGrab(grab) | |
__chatLog(message) | |
__autoRespond(message) | |
__chatCMD(message) | |
__chatNotifications(message) | |
__mentionNotification(message) | |
__chatHistory(message) | |
__historyCheck(advance) | |
__getPermission(user) | |
__songStats(advance) | |
__mehRow(advance) | |
__nowPlaying(advance) | |
__inlineImages(message) | |
__customEmotes(message) | |
__checkImages(url, messageEl, __href) | |
__checkEmotes(url, messageEl, name) | |
__baBanUID(uid) | |
__baMuteUID(uid) | |
__baTrollUID(uid) | |
__baDelTrollUID(uid) | |
__userInformation(user, __) | |
__uiSkipButton(user) | |
__etaInfo(isSelf) | |
__checkLocked(message) | |
__onChatReceived(message) |
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
var fs = require('fs') | |
var esprima = require('esprima') | |
var escodegen = require('escodegen') | |
var estraverse = require('estraverse') | |
var esrefactor = require('esrefactor') | |
var origFile = process.argv[2] || 'rs.min.js' | |
var outFile = origFile.replace(/\.min\.js$/, '.js') | |
var signatures = {} | |
try { | |
signatures = parseSignatures(fs.readFileSync('rcs-method-signatures.txt', 'utf8')) | |
} | |
catch (e) {} | |
fs.readFile(origFile, { encoding: 'utf8' }, function (e, content) { | |
var ast = esprima.parse(content, { range: true, comment: true }) | |
var decl = getObfuscatedDeclaration(ast) | |
var name = decl.id.name | |
var map = decl.init.elements | |
// replace a['b'] with a.b | |
estraverse.replace(ast, { | |
enter: function (node) { | |
if (node.type === 'MemberExpression' && node.object.name === name) { | |
return node.property && map[node.property.value] || node | |
} | |
}, | |
leave: function (node) { | |
if (node.type === 'MemberExpression' && node.computed && | |
typeof node.property.value === 'string' && | |
/^[a-z_$][0-9a-z_$]*$/i.test(node.property.value)) { | |
node.property = $smallRange({ type: 'Identifier', name: node.property.value }) | |
node.computed = false | |
} | |
} | |
}) | |
ast = cleanAst(ast) | |
if (signatures) { | |
var getName = function (node) { | |
if (node.type === 'Identifier') return node.name | |
if (node.type === 'MemberExpression') { | |
return getName(node.object) + '.' + getName(node.property) | |
} | |
} | |
var renames = [] | |
estraverse.traverse(ast, { | |
enter: function (node) { | |
var name, fn | |
if (node.type === 'FunctionDeclaration') { | |
name = getName(node.id) | |
fn = node | |
} | |
if (node.type === 'AssignmentExpression' && | |
node.right.type === 'FunctionExpression') { | |
name = getName(node.left) | |
fn = node.right | |
} | |
if (node.type === 'Property' && | |
node.value.type === 'FunctionExpression') { | |
name = getName(node.key) | |
fn = node.value | |
} | |
if (name && fn && name in signatures) { | |
var params = signatures[name] | |
params.forEach(function (newName, i) { | |
if (fn.params[i]) | |
renames.push({ | |
idx: fn.params[i].range[0], | |
to: newName | |
}) | |
}) | |
} | |
else { | |
} | |
} | |
}) | |
processRenames(ast, renames) | |
} | |
var better = escodegen.generate(ast, { | |
format: { indent: { style: ' ' } }, | |
comment: true | |
}) | |
fs.writeFile(outFile, better, function (e) { | |
console.log(e) | |
}) | |
}) | |
function getObfuscatedDeclaration(ast) { | |
var first | |
var body = ast.body | |
if (body[0].type === 'ExpressionStatement') { | |
body = body[0].expression.argument.callee.body.body | |
} | |
// remove the obfuscated var map | |
first = body.shift() | |
if (first.type === 'VariableDeclaration') { | |
var res = first.declarations.shift() | |
// some scripts have more declarations bunched together, so we | |
// should put the extra decls back in | |
if (first.declarations.length > 0) { | |
body.unshift(first) | |
} | |
return res | |
} | |
} | |
function parseSignatures(string) { | |
return string | |
.split('\n') | |
.filter(function (x) { return /^[a-z._0-9]+\((?:[a-z_0-9, ]*)\)$/i.test(x) }) | |
.map(function (line) { return /^([a-z._0-9]+)\((.*?)\)$/i.exec(line).slice(1) }) | |
.reduce(function (signatures, line) { | |
var name = line[0], args = line[1].split(',').map(function (arg) { return arg.trim() }) | |
signatures[name] = args | |
return signatures | |
}, {}) | |
} | |
function $id(name) { | |
return $smallRange({ type: 'Identifier', name: name }) | |
} | |
function $literal(value) { return { type: 'Literal', value: value } } | |
function $comment(ast, type, value) { | |
ast.trailingComments = ast.trailingComments || [] | |
ast.trailingComments.push({ | |
type: type, | |
value: value | |
}) | |
return ast | |
} | |
function $largeRange(ast) { return ast.range = [ 0, Infinity ], ast } | |
function $smallRange(ast, n) { | |
return ast.range = [ n || 0, n || 0 ], ast | |
} | |
function $statement(expr) { | |
return { | |
type: 'ExpressionStatement', | |
expression: expr, | |
range: expr.range | |
} | |
} | |
function $block(stmt) { | |
return stmt.type === 'BlockStatement' ? stmt : { | |
type: 'BlockStatement', | |
body: [ stmt ], | |
range: stmt.range | |
} | |
} | |
// expand ternary expression statements into if(){}else{} blocks | |
function expandTernary(expr) { | |
return { | |
type: 'IfStatement', | |
range: expr.range, | |
test: expr.test, | |
consequent: $block($statement(expr.consequent)), | |
alternate: expr.alternate.type === 'ConditionalExpression' | |
? expandTernary(expr.alternate) | |
: $block($statement(expr.alternate)) | |
} | |
} | |
function wrapIfBranches(node) { | |
if (node.consequent.type !== 'BlockStatement') { | |
node.consequent = $block(node.consequent) | |
} | |
if (node.alternate && node.alternate !== 'BlockStatement') { | |
node.alternate = $block(node.alternate) | |
} | |
return node | |
} | |
function wrapBody(node) { | |
if (node.body.type !== 'BlockStatement') { | |
node.body = $block(node.body) | |
} | |
} | |
function expandAndOr(node) { | |
if (node.expression.operator === '&&') { | |
return { | |
type: 'IfStatement', | |
range: node.range, | |
test: node.expression.left, | |
consequent: $block($statement(node.expression.right)) | |
} | |
} | |
else if (node.expression.operator === '||') { | |
return { | |
type: 'IfStatement', | |
range: node.range, | |
test: { | |
type: 'UnaryExpression', | |
operator: '!', | |
range: node.expression.left.range, | |
argument: node.expression.left, | |
prefix: true | |
}, | |
consequent: $statement(node.expression.right) | |
} | |
} | |
return node | |
} | |
var unyodaOperators = { | |
'>': '<', | |
'<': '>', | |
'>=': '<=', | |
'<=': '>=' | |
} | |
function unyoda(node) { | |
if (node.left.type === 'Literal' && node.right.type !== 'Literal') { | |
var tmp = node.right | |
node.right = node.left | |
node.left = tmp | |
if (node.operator in unyodaOperators) { | |
node.operator = unyodaOperators[node.operator] | |
} | |
} | |
return node | |
} | |
function findReturnVar(ast) { | |
var lastSt = ast.body[ast.body.length - 1] | |
if (lastSt && lastSt.type === 'ReturnStatement') { | |
var retVal = lastSt.argument | |
return retVal.type === 'NewExpression' | |
? retVal.callee | |
: retVal.type === 'Identifier' | |
? retVal | |
: null | |
} | |
} | |
function cleanAst(ast) { | |
estraverse.replace(ast, { | |
enter: function (node) { | |
// add braces around branch/loop constructs if they are not yet present | |
if (node.type === 'IfStatement') { | |
wrapIfBranches(node) | |
} | |
else if (node.type === 'ForStatement' || | |
node.type === 'WhileStatement') { | |
wrapBody(node) | |
} | |
// turn !0, !1 into true, false | |
else if (node.type === 'UnaryExpression' && | |
node.operator === '!' && | |
node.argument.type === 'Literal') { | |
if (node.argument.value === 0) { | |
return { type: 'Literal', value: true, raw: 'true' } | |
} | |
else if (node.argument.value === 1) { | |
return { type: 'Literal', value: false, raw: 'false' } | |
} | |
} | |
else if (node.type === 'BinaryExpression') { | |
return unyoda(node) | |
} | |
// expand ternary ?: statements to if/else statements | |
else if (node.type === 'ExpressionStatement' && | |
node.expression.type === 'ConditionalExpression') { | |
return expandTernary(node.expression) | |
} | |
// expand compressed &&, || expressions into if/else statements | |
else if (node.type === 'ExpressionStatement' && | |
node.expression.type === 'LogicalExpression') { | |
return expandAndOr(node) | |
} | |
// expand some expressions into multiple statements | |
else if (node.type === 'BlockStatement') { | |
node.body = node.body.reduce(function (newBody, node) { | |
// expand comma-separated expressions on a single line to multiple statements | |
if (node.type === 'ExpressionStatement' && | |
node.expression.type === 'SequenceExpression') { | |
return newBody.concat(node.expression.expressions.map($statement)) | |
} | |
// expand comma-separated expressions in a return statement to multiple statements | |
else if (node.type === 'ReturnStatement' && | |
node.argument && | |
node.argument.type === 'SequenceExpression') { | |
var exprs = node.argument.expressions | |
node.argument = exprs.pop() | |
return newBody.concat(exprs.map($statement)).concat([ node ]) | |
} | |
else if (node.type === 'EmptyStatement') { | |
return newBody | |
} | |
return newBody.concat([ node ]) | |
}, []) | |
} | |
}, | |
leave: function (node) { | |
// remove braces from else statements that contain only an if statement | |
// (i.e. else if statements) | |
if (node.type === 'IfStatement' && | |
node.alternate && node.alternate.type === 'BlockStatement' && | |
node.alternate.body.length === 1 && node.alternate.body[0].type === 'IfStatement') { | |
node.alternate = node.alternate.body[0] | |
} | |
return node | |
} | |
}) | |
return ast | |
} | |
function processRenames(ast, renames) { | |
var renaming = new esrefactor.Context(ast) | |
renames.forEach(function (r) { | |
var id = renaming.identify(r.idx) | |
if (id) { | |
// rename manually. | |
// esrefactor renames things "in-place" in the source code, | |
// which means that you have to parse the source again every | |
// time you rename a variable. Since we don't need to retain | |
// formatting (it's minified code at this point, after all) | |
// we can manually rename all variables at once without ever | |
// parsing the source again. | |
if (id.identifier) id.identifier.name = r.to | |
if (id.declaration) id.declaration.name = r.to | |
id.references.forEach(function (node) { node.name = r.to }) | |
} | |
}) | |
return ast | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment