Skip to content

Instantly share code, notes, and snippets.

@x4fx77x4f
Created August 12, 2024 06:16
Show Gist options
  • Save x4fx77x4f/e9d195fa21fdc2febf82abf063ea4b8a to your computer and use it in GitHub Desktop.
Save x4fx77x4f/e9d195fa21fdc2febf82abf063ea4b8a to your computer and use it in GitHub Desktop.
Unpack gluapack
#!/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))
#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;
--[[
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,
}
#!/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