You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
9.0 KiB
290 lines
9.0 KiB
3 years ago
|
local ffi = require 'ffi'
|
||
|
ffi.cdef[[
|
||
|
struct SFILE_CREATE_MPQ {
|
||
|
unsigned long cbSize; // Size of this structure, in bytes
|
||
|
unsigned long dwMpqVersion; // Version of the MPQ to be created
|
||
|
void* pvUserData; // Reserved, must be NULL
|
||
|
unsigned long cbUserData; // Reserved, must be 0
|
||
|
unsigned long dwStreamFlags; // Stream flags for creating the MPQ
|
||
|
unsigned long dwFileFlags1; // File flags for (listfile). 0 = default
|
||
|
unsigned long dwFileFlags2; // File flags for (attributes). 0 = default
|
||
|
unsigned long dwFileFlags3; // File flags for (signature). 0 = default
|
||
|
unsigned long dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created
|
||
|
unsigned long dwSectorSize; // Sector size for compressed files
|
||
|
unsigned long dwRawChunkSize; // Size of raw data chunk
|
||
|
unsigned long dwMaxFileCount; // File limit for the MPQ
|
||
|
};
|
||
|
|
||
|
bool SFileCreateArchive2(const wchar_t* szMpqName, struct SFILE_CREATE_MPQ* pCreateInfo, uint32_t* phMpq);
|
||
|
bool SFileOpenArchive(const wchar_t* szMpqName, unsigned long dwPriority, unsigned long dwFlags, uint32_t* phMpq);
|
||
|
bool SFileCloseArchive(uint32_t hMpq);
|
||
|
bool SFileAddFileEx(uint32_t hMpq, const wchar_t* szFileName, const char* szArchivedName, unsigned long dwFlags, unsigned long dwCompression, unsigned long dwCompressionNext);
|
||
|
bool SFileExtractFile(uint32_t hMpq, const char* szToExtract, const wchar_t* szExtracted, unsigned long dwSearchScope);
|
||
|
bool SFileHasFile(uint32_t hMpq, const char* szFileName);
|
||
|
bool SFileSetMaxFileCount(uint32_t hMpq, unsigned long dwMaxFileCount);
|
||
|
|
||
|
bool SFileCreateFile(uint32_t hMpq, const char* szArchivedName, unsigned long long FileTime, unsigned long dwFileSize, unsigned long lcLocale, unsigned long dwFlags, uint32_t* phFile);
|
||
|
bool SFileWriteFile(uint32_t hFile, const void* pvData, unsigned long dwSize, unsigned long dwCompression);
|
||
|
bool SFileFinishFile(uint32_t hFile);
|
||
|
bool SFileOpenFileEx(uint32_t hMpq, const char* szFileName, unsigned long dwSearchScope, uint32_t* phFile);
|
||
|
bool SFileReadFile(uint32_t hFile, void* lpBuffer, unsigned long dwToRead, unsigned long* pdwRead, void* lpOverlapped);
|
||
|
unsigned long SFileGetFileSize(uint32_t hFile, unsigned long* pdwFileSizeHigh);
|
||
|
bool SFileCloseFile(uint32_t hFile);
|
||
|
bool SFileRemoveFile(uint32_t hMpq, const char* szFileName, unsigned long dwSearchScope);
|
||
|
|
||
|
bool SFileGetFileInfo(uint32_t hMpqOrFile, int InfoClass, void * pvFileInfo, unsigned long cbFileInfo, unsigned long* pcbLengthNeeded);
|
||
|
|
||
|
unsigned long SFileGetLocale();
|
||
|
|
||
|
unsigned long GetLastError();
|
||
|
]]
|
||
|
ffi.cdef[[
|
||
|
struct SYSTEMTIME {
|
||
|
unsigned short wYear;
|
||
|
unsigned short wMonth;
|
||
|
unsigned short wDayOfWeek;
|
||
|
unsigned short wDay;
|
||
|
unsigned short wHour;
|
||
|
unsigned short wMinute;
|
||
|
unsigned short wSecond;
|
||
|
unsigned short wMilliseconds;
|
||
|
};
|
||
|
struct FILETIME {
|
||
|
unsigned long dwLowDateTime;
|
||
|
unsigned long dwHighDateTime;
|
||
|
};
|
||
|
void GetSystemTime(struct SYSTEMTIME* lpSystemTime);
|
||
|
int SystemTimeToFileTime(const struct SYSTEMTIME* lpSystemTime, struct FILETIME*lpFileTime);
|
||
|
]]
|
||
|
|
||
|
local SFileMpqNumberOfFiles = 36
|
||
|
|
||
|
require 'filesystem'
|
||
|
local uni = require 'ffi.unicode'
|
||
|
local stormlib = ffi.load('stormlib')
|
||
|
|
||
|
local function current_filetime()
|
||
|
local systemtime = ffi.new('struct SYSTEMTIME')
|
||
|
local filetime = ffi.new('struct FILETIME')
|
||
|
ffi.C.GetSystemTime(systemtime)
|
||
|
if not ffi.C.SystemTimeToFileTime(systemtime, filetime) then
|
||
|
return 0
|
||
|
end
|
||
|
return filetime.dwLowDateTime | (filetime.dwHighDateTime << 32)
|
||
|
end
|
||
|
|
||
|
|
||
|
local wfile = {}
|
||
|
wfile.__index = wfile
|
||
|
|
||
|
function wfile:close()
|
||
|
if self.handle == 0 then
|
||
|
return
|
||
|
end
|
||
|
stormlib.SFileFinishFile(self.handle)
|
||
|
self.handle = 0
|
||
|
end
|
||
|
|
||
|
function wfile:write(buf)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
return stormlib.SFileWriteFile(self.handle, buf, #buf, 0x02)
|
||
|
end
|
||
|
|
||
|
local rfile = {}
|
||
|
rfile.__index = rfile
|
||
|
|
||
|
function rfile:close()
|
||
|
if self.handle == 0 then
|
||
|
return
|
||
|
end
|
||
|
stormlib.SFileCloseFile(self.handle)
|
||
|
self.handle = 0
|
||
|
end
|
||
|
|
||
|
function rfile:size()
|
||
|
if self.handle == 0 then
|
||
|
return 0
|
||
|
end
|
||
|
local size_hi = ffi.new('unsigned long[1]', 0)
|
||
|
local size_lo = stormlib.SFileGetFileSize(self.handle, size_hi)
|
||
|
return size_lo | (size_hi[0] << 32)
|
||
|
end
|
||
|
|
||
|
function rfile:read(n)
|
||
|
if self.handle == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
if not n then
|
||
|
n = self:size()
|
||
|
end
|
||
|
local buf = ffi.new('char[?]', n)
|
||
|
local pread = ffi.new('unsigned long[1]', 0)
|
||
|
if not stormlib.SFileReadFile(self.handle, buf, n, pread, nil) then
|
||
|
return nil
|
||
|
end
|
||
|
return ffi.string(buf, pread[0])
|
||
|
end
|
||
|
|
||
|
local archive = {}
|
||
|
archive.__index = archive
|
||
|
|
||
|
function archive:close()
|
||
|
if self.handle == 0 then
|
||
|
return
|
||
|
end
|
||
|
stormlib.SFileCloseArchive(self.handle)
|
||
|
self.handle = 0
|
||
|
end
|
||
|
|
||
|
function archive:add_file(name, path)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
local wpath = uni.u2w(path:string())
|
||
|
return stormlib.SFileAddFileEx(self.handle, wpath, name,
|
||
|
0x00000200 | 0x80000000, -- MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING,
|
||
|
0x02, -- MPQ_COMPRESSION_ZLIB,
|
||
|
0x02 --MPQ_COMPRESSION_ZLIB
|
||
|
)
|
||
|
end
|
||
|
|
||
|
function archive:extract(name, path)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
local dir = path:parent_path()
|
||
|
if not fs.exists(dir) then
|
||
|
fs.create_directories(dir)
|
||
|
end
|
||
|
local wpath = uni.u2w(path:string())
|
||
|
return stormlib.SFileExtractFile(self.handle, name, wpath,
|
||
|
0 --SFILE_OPEN_FROM_MPQ
|
||
|
)
|
||
|
end
|
||
|
|
||
|
function archive:has_file(name)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
return stormlib.SFileHasFile(self.handle, name)
|
||
|
end
|
||
|
|
||
|
function archive:remove_file(name)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
return stormlib.SFileRemoveFile(self.handle, name, 0)
|
||
|
end
|
||
|
|
||
|
function archive:open_file(name)
|
||
|
if self.handle == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
local phandle = ffi.new('uint32_t[1]', 0)
|
||
|
if not stormlib.SFileOpenFileEx(self.handle, name, 0, phandle) then
|
||
|
return nil
|
||
|
end
|
||
|
return setmetatable({ handle = phandle[0] }, rfile)
|
||
|
end
|
||
|
|
||
|
function archive:create_file(name, size, filetime)
|
||
|
if self.handle == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
if not filetime then
|
||
|
filetime = current_filetime()
|
||
|
end
|
||
|
local phandle = ffi.new('uint32_t[1]', 0)
|
||
|
if not stormlib.SFileCreateFile(self.handle, name, filetime, size, stormlib.SFileGetLocale(), 0x00000200 | 0x80000000, phandle) then
|
||
|
return nil
|
||
|
end
|
||
|
return setmetatable({ handle = phandle[0] }, wfile)
|
||
|
end
|
||
|
|
||
|
function archive:load_file(name)
|
||
|
if self.handle == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
local file = self:open_file(name)
|
||
|
if not file then
|
||
|
return nil
|
||
|
end
|
||
|
local content = file:read()
|
||
|
file:close()
|
||
|
return content
|
||
|
end
|
||
|
|
||
|
function archive:save_file(name, buf, filetime)
|
||
|
if self.handle == 0 then
|
||
|
return false
|
||
|
end
|
||
|
local file = self:create_file(name, #buf, filetime)
|
||
|
if not file then
|
||
|
return false
|
||
|
end
|
||
|
file:write(buf)
|
||
|
file:close()
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function archive:number_of_files()
|
||
|
if self.handle == 0 then
|
||
|
return 0
|
||
|
end
|
||
|
local pinfo = ffi.new('uint32_t[1]', 0)
|
||
|
if not stormlib.SFileGetFileInfo(self.handle, SFileMpqNumberOfFiles, pinfo, 4, nil) then
|
||
|
return 0
|
||
|
end
|
||
|
return pinfo[0]
|
||
|
end
|
||
|
|
||
|
local m = {}
|
||
|
function m.open(path, readonly, filecount)
|
||
|
local wpath = uni.u2w(path:string())
|
||
|
local phandle = ffi.new('uint32_t[1]', 0)
|
||
|
local flag = 0
|
||
|
if readonly then
|
||
|
flag = 0x100
|
||
|
end
|
||
|
if not stormlib.SFileOpenArchive(wpath, 0, flag, phandle) then
|
||
|
return nil
|
||
|
end
|
||
|
if filecount then
|
||
|
stormlib.SFileSetMaxFileCount(phandle[0], filecount)
|
||
|
end
|
||
|
return setmetatable({ handle = phandle[0] }, archive)
|
||
|
end
|
||
|
function m.create(path, filecount, encrypt)
|
||
|
local wpath = uni.u2w(path:string())
|
||
|
local phandle = ffi.new('uint32_t[1]', 0)
|
||
|
local info = ffi.new('struct SFILE_CREATE_MPQ')
|
||
|
info.cbSize = ffi.sizeof('struct SFILE_CREATE_MPQ')
|
||
|
info.dwMpqVersion = 0 --MPQ_FORMAT_VERSION_1
|
||
|
info.pvUserData = nil
|
||
|
info.cbUserData = 0
|
||
|
info.dwStreamFlags = 0 --STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE
|
||
|
if encrypt then
|
||
|
info.dwFileFlags1 = 0
|
||
|
info.dwFileFlags2 = 0
|
||
|
info.dwFileFlags3 = 0
|
||
|
else
|
||
|
info.dwFileFlags1 = 0x80000000 --MPQ_FILE_EXISTS
|
||
|
info.dwFileFlags2 = 0x80000000 --MPQ_FILE_EXISTS
|
||
|
info.dwFileFlags3 = 0x80000000 --MPQ_FILE_EXISTS
|
||
|
end
|
||
|
info.dwAttrFlags = 7 --MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5
|
||
|
info.dwSectorSize = 0x10000
|
||
|
info.dwRawChunkSize = 0
|
||
|
info.dwMaxFileCount = filecount
|
||
|
if not stormlib.SFileCreateArchive2(wpath, info, phandle) then
|
||
|
return nil
|
||
|
end
|
||
|
return setmetatable({ handle = phandle[0] }, archive)
|
||
|
end
|
||
|
function m.attach(handle)
|
||
|
return setmetatable({ handle = handle }, archive)
|
||
|
end
|
||
|
return m
|