Created
August 12, 2024 06:16
-
-
Save x4fx77x4f/e9d195fa21fdc2febf82abf063ea4b8a to your computer and use it in GitHub Desktop.
Unpack gluapack
This file contains hidden or 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
#!/usr/bin/env luajit | |
local argparse = require("argparse") -- https://github.com/luarocks/argparse | |
argparse = argparse(nil, "Decrypt and/or decompress a gluapack file.") | |
argparse:argument("file", "Path to file to read from, or \"-\" for standard input. Should be in \"garrysmod/download/data/gluapack/\".") | |
argparse:argument("key", "Value of \"gluapack_key\" console variable.") | |
argparse:option("--md5", "Value of \"gluapack_md5\" console variable.") | |
argparse:option("--output", "Path to file to write to, or \"-\" for standard output.", "-") | |
argparse:option("--output-at", "Which stage of processing to stop at before outputting.", "decompressed"):choices({ | |
"decrypted", | |
"skipped", | |
"decompressed", | |
}) | |
local arguments = argparse:parse() | |
local function decode_hex(input) | |
local output = {} | |
for output_index = 1, #input/2 do | |
local input_index = output_index*2-1 | |
output[output_index] = string.char(tonumber(string.sub(input, input_index, input_index+1), 16)) | |
end | |
return table.concat(output) | |
end | |
local function encode_as_hex(input) | |
return (string.gsub(input, ".", function(byte) | |
return string.format("%02x", string.byte(byte)) | |
end)) | |
end | |
local file_path = arguments.file | |
local encoded_key = arguments.key | |
assert(#encoded_key == 32) | |
local key = decode_hex(encoded_key) | |
local hash = arguments.md5 | |
if hash ~= nil then | |
assert(#hash == 32) | |
assert(string.find(hash, "[^%x]") == nil) | |
hash = string.lower(hash) | |
end | |
local output_path = arguments.output | |
local output_at = arguments.output_at | |
local rc4 = require("rc4") -- https://github.com/philanc/plc | |
local lzma = require("lzma") -- https://codeberg.org/leso-kn/lua-lzma | |
local luazen | |
if hash ~= nil then | |
luazen = require("luazen") -- https://github.com/philanc/luazen | |
end | |
local file = io.stdin | |
if file_path ~= "-" then | |
file = assert(io.open(file_path, "rb")) | |
end | |
local data = assert(file:read("*a")) | |
file:close() | |
repeat | |
data = rc4.rc4raw(key, data) | |
local padding = string.sub(data, 1, 256) | |
if string.find(padding, "[^%z]") ~= nil then | |
assert(io.stderr:write("bad padding (contains non-null byte)\n")) | |
end | |
if output_at == "decrypted" then | |
break | |
end | |
data = string.sub(data, 257) | |
if output_at == "skipped" then | |
break | |
end | |
data = lzma.decompress(data) | |
if hash ~= nil then | |
local actual_hash = encode_as_hex(luazen.md5(data)) | |
if actual_hash ~= hash then | |
assert(io.stderr:write("bad MD5 hash (expected '", hash, "', got '", actual_hash, "'\n")) | |
end | |
end | |
if output_at == "decompressed" then | |
break | |
end | |
until true | |
local output_file = io.stdout | |
if output_path ~= "-" then | |
output_file = assert(io.open(output_path, "w+b")) | |
end | |
assert(output_file:write(data)) |
This file contains hidden or 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
#include <std/mem.pat> | |
#include <std/sys.pat> | |
#pragma endian big | |
struct gluapack_entry_t { | |
u8 hash[16]; // Salted MD5 hash of full path. | |
u8 lcl_hash[16]; // Ditto but sans leading "addons/[^/]+/", "gamemodes/[^/]+/entities/", "gamemodes/", and "lua/". | |
u8 lcl_hash_2[16]; // Ditto but not sans "gamemodes/[^/]+/entities/". This one and the previous one might be switched. | |
u32 length; | |
char data[length]; | |
}; | |
struct gluapack_t { | |
u8 version; // Ignored by gluapack. | |
std::assert_warn(version == 0x00, "bad version"); // What it happens to be for me for this one particular file. | |
gluapack_entry_t entries[while ($ < std::mem::size())]; | |
} [[inline]]; | |
gluapack_t gluapack @ 0; |
This file contains hidden or 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
--[[ | |
Original: https://github.com/philanc/plc/blob/master/plc/rc4.lua | |
Original license terms: | |
Copyright (c) 2017 Phil Leblanc | |
Permission is hereby granted, free of charge, to any person | |
obtaining a copy of this software and associated documentation | |
files (the "Software"), to deal in the Software without restriction, | |
including without limitation the rights to use, copy, modify, merge, | |
publish, distribute, sublicense, and/or sell copies of the Software, | |
and to permit persons to whom the Software is furnished to do so, | |
subject to the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
This is a modified version of the aforementioned original. | |
New license terms: | |
Copyright (c) 2024 x4fx77x4f | |
Same license terms as above. | |
]] | |
local bit = require("bit") | |
local band, bxor = bit.band, bit.bxor | |
local byte, char, concat = string.byte, string.char, table.concat | |
local function keysched(key) | |
-- key must be a 16-byte string | |
assert(#key == 16) | |
local s = {} | |
local j,ii,jj | |
for i = 0, 255 do s[i+1] = i end | |
j = 0 | |
for i = 0, 255 do | |
ii = i+1 | |
j = band(j + s[ii] + byte(key, (i % 16) + 1), 0xff) | |
jj = j+1 | |
s[ii], s[jj] = s[jj], s[ii] | |
end | |
return s | |
end | |
local function step(s, i, j) | |
i = band(i + 1, 0xff) | |
local ii = i + 1 | |
j = band(j + s[ii], 0xff) | |
local jj = j + 1 | |
s[ii], s[jj] = s[jj], s[ii] | |
local k = s[ (band(s[ii] + s[jj], 0xff)) + 1 ] | |
return s, i, j, k | |
end | |
local function rc4raw(key, plain) | |
-- raw encryption | |
-- key must be a 16-byte string | |
local s = keysched(key) | |
local i, j = 0, 0 | |
local k | |
local t = {} | |
for n = 1, #plain do | |
s, i, j, k = step(s, i, j) | |
t[n] = char(bxor(byte(plain, n), k)) | |
end | |
return concat(t) | |
end | |
local function rc4(key, plain, drop) | |
-- encrypt 'plain', return encrypted text | |
-- key must be a 16-byte string | |
-- optional drop (default = 256): ignore first 'drop' iterations | |
drop = drop or 256 | |
local s = keysched(key) | |
local i, j = 0, 0 | |
local k | |
local t = {} | |
-- run and ignore 'drop' iterations | |
for _ = 1, drop do | |
s, i, j = step(s, i, j) | |
end | |
-- now start to encrypt | |
for n = 1, #plain do | |
s, i, j, k = step(s, i, j) | |
t[n] = char(bxor(byte(plain, n), k)) | |
end | |
return concat(t) | |
end | |
return { | |
rc4raw = rc4raw, | |
rc4 = rc4, | |
encrypt = rc4, | |
decrypt = rc4, | |
} |
This file contains hidden or 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
#!/usr/bin/env luajit | |
local argparse = require("argparse") -- https://github.com/luarocks/argparse | |
argparse = argparse(nil, "Unpack a decompressed gluapack file.") | |
argparse:argument("input_file", "Path to file to read from, or \"-\" for standard input. Should be output of \"decrypt.lua\".") | |
argparse:argument("output_directory", "Path to directory to write to.") | |
local arguments = argparse:parse() | |
local bit = require("bit") | |
local path = arguments.input_file | |
local file = io.stdin | |
if path ~= "-" then | |
file = assert(io.open(path, "rb")) | |
end | |
local output_path = arguments.output_directory | |
if string.byte(output_path, -1) ~= 0x2f then -- '/' | |
output_path = output_path.."/" | |
end | |
local file_length = assert(file:seek("end")) | |
assert(file:seek("set", 0)) | |
local function file_tell() | |
return assert(file:seek()) | |
end | |
local function file_read(length) | |
local data = assert(file:read(length)) | |
assert(#data == length) | |
return data | |
end | |
local function file_read_u8() | |
return string.byte(file_read(1)) | |
end | |
local function file_read_u32_be() | |
local b1, b2, b3, b4 = string.byte(file_read(4), 1, 4) | |
local number = bit.bor( | |
bit.lshift(b1, 8*3), | |
bit.lshift(b2, 8*2), | |
bit.lshift(b3, 8*1), | |
b4 | |
) | |
if number < 0 then | |
number = number+2^32 | |
end | |
return number | |
end | |
local function to_hex(input) | |
return (string.gsub(input, ".", function(byte) | |
return string.format("%02x", string.byte(byte)) | |
end)) | |
end | |
local version = file_read_u8(1) | |
if version ~= 0x00 then | |
assert(io.stderr:write("bad version (expected 0x00, got 0x", bit.tohex(version, 2), ")\n")) | |
end | |
while file_tell() < file_length do | |
local hash = file_read(16) | |
local lcl_hash = file_read(16) | |
local lcl_hash_2 = file_read(16) | |
local length = file_read_u32_be() | |
local data = file_read(length) | |
local output_file = assert(io.open(output_path..to_hex(hash)..".lua", "w+b")) | |
assert(output_file:write(data)) | |
output_file:close() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment