Skip to content

Instantly share code, notes, and snippets.

@gabcasanova
Created February 23, 2025 05:44
Show Gist options
  • Save gabcasanova/c096ea3f20ed92f8bf267151f99c1b04 to your computer and use it in GitHub Desktop.
Save gabcasanova/c096ea3f20ed92f8bf267151f99c1b04 to your computer and use it in GitHub Desktop.
far 1a extractor inlua
-- Lua 5.4 FAR Archive Extractor
-- Supports listing files, extracting individual files, and extracting all files with subfolder creation.
local function read_u32(file)
local bytes = file:read(4)
if not bytes or #bytes ~= 4 then return nil end
return string.unpack("<I4", bytes)
end
local function read_string(file, length)
return file:read(length)
end
local function create_directories(path)
local dir = ""
for part in string.gmatch(path, "[^\\]+") do
dir = dir .. part .. "\\"
if not os.rename(dir, dir) then
os.execute("mkdir " .. dir)
end
end
end
local function extract_file(file, offset, size, output_path)
file:seek("set", offset)
local data = file:read(size)
local output_file = io.open(output_path, "wb")
if not output_file then
error("Failed to create file: " .. output_path)
end
output_file:write(data)
output_file:close()
end
local function list_files(file_table)
print("Files in the archive:")
for i, entry in ipairs(file_table) do
print(string.format("%d: %s (Size: %d bytes)", i, entry.filename, entry.decompressed_size))
end
end
local function extract_all_files(file, file_table, output_folder)
for _, entry in ipairs(file_table) do
local output_path = output_folder .. "\\" .. entry.filename
create_directories(output_path:match("^(.*\\)"))
extract_file(file, entry.data_offset, entry.compressed_size, output_path)
print("Extracted: " .. output_path)
end
end
local function main()
io.write("Enter the path to the .FAR file: ")
local far_path = io.read()
local far_file = io.open(far_path, "rb")
if not far_file then
print("Failed to open file: " .. far_path)
return
end
-- Read header
local magic = far_file:read(8)
if magic ~= "FAR!byAZ" then
print("Invalid FAR file.")
far_file:close()
return
end
local version = read_u32(far_file)
local file_table_offset = read_u32(far_file)
-- Read file table
far_file:seek("set", file_table_offset)
local num_files = read_u32(far_file)
local file_table = {}
for i = 1, num_files do
local decompressed_size = read_u32(far_file)
local compressed_size = read_u32(far_file)
local data_offset = read_u32(far_file)
local filename_length = read_u32(far_file)
local filename = read_string(far_file, filename_length)
table.insert(file_table, {
decompressed_size = decompressed_size,
compressed_size = compressed_size,
data_offset = data_offset,
filename = filename
})
end
-- List files
list_files(file_table)
-- User options
io.write("Enter the number of the file to extract, or 'all' to extract all files: ")
local choice = io.read()
if choice == "all" then
io.write("Enter the output folder path: ")
local output_folder = io.read()
extract_all_files(far_file, file_table, output_folder)
else
local index = tonumber(choice)
if index and index >= 1 and index <= #file_table then
local entry = file_table[index]
io.write("Enter the output path (including filename): ")
local output_path = io.read()
extract_file(far_file, entry.data_offset, entry.compressed_size, output_path)
print("Extracted: " .. output_path)
else
print("Invalid selection.")
end
end
far_file:close()
end
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment