From 0489ae469ef96303ae85c34f8827a948a64901f8 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sat, 15 Dec 2018 13:30:18 -0800 Subject: [PATCH] add simple stormlib wrapper --- formats/mpq/mpq.go | 164 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 formats/mpq/mpq.go diff --git a/formats/mpq/mpq.go b/formats/mpq/mpq.go new file mode 100644 index 0000000..a3eee5d --- /dev/null +++ b/formats/mpq/mpq.go @@ -0,0 +1,164 @@ +package mpq + +// #cgo LDFLAGS: -L./stormlib/ -lstorm -lz -lbz2 -lstdc++ +// #include +// #define WINAPI +// #define DWORD unsigned int +// #define HANDLE void * +// #define LPDWORD unsigned int * +// #define LPOVERLAPPED void * +// #define TCHAR char +// #define bool unsigned char +// bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq); +// bool WINAPI SFileCloseArchive(HANDLE hMpq); +// bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile); +// DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh); +// bool WINAPI SFileReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped); +// bool WINAPI SFileCloseFile(HANDLE hFile); +// DWORD GetLastError(); +import "C" +import ( + "bytes" + "fmt" + "io" + "math" + "strings" + "unsafe" +) + +type File interface { + Read(data []byte) (int, error) + GetSize() (int, error) +} + +type Archive interface { + OpenFile(path string) (File, error) + GetPaths() ([]string, error) + Close() error +} + +func New(path string) (Archive, error) { + cs := C.CString(path) + defer C.free(unsafe.Pointer(cs)) + + a := new(archive) + if result := C.SFileOpenArchive(cs, 0, 0, &a.handle); result == 0 { + return nil, fmt.Errorf("failed to open archive (%d)", getLastError()) + } + + return a, nil +} + +type file struct { + handle unsafe.Pointer + offset int + size int +} + +func (f *file) Read(data []byte) (int, error) { + size, err := f.GetSize() + if err != nil { + return 0, err + } + + bytesRemaining := size - f.offset + if bytesRemaining == 0 { + return 0, io.EOF + } + + bytesRequested := len(data) + if bytesRequested > bytesRemaining { + bytesRequested = bytesRemaining + } + + var bytesRead int + if result := C.SFileReadFile(f.handle, unsafe.Pointer(&data[0]), C.unsigned(bytesRequested), (*C.unsigned)(unsafe.Pointer(&bytesRead)), nil); result == 0 { + return 0, fmt.Errorf("failed to read file (%d)", getLastError()) + } + + f.offset += bytesRead + return bytesRead, nil +} + +func (f *file) GetSize() (int, error) { + if f.size != math.MaxUint32 { + return f.size, nil + } + + size := int(C.SFileGetFileSize(f.handle, nil)) + if size == -1 { + return 0, fmt.Errorf("failed to get file size (%d)", getLastError()) + } + + f.size = size + return size, nil +} + +func (f *file) Close() error { + if result := C.SFileCloseFile(f.handle); result == 0 { + return fmt.Errorf("failed to close file (%d)", getLastError()) + } + + f.handle = nil + f.offset = 0 + f.size = 0 + + return nil +} + +type archive struct { + handle unsafe.Pointer + paths []string +} + +func (a *archive) Close() error { + if result := C.SFileCloseArchive(a.handle); result == 0 { + return fmt.Errorf("failed to close archive (%d)", getLastError()) + } + + a.handle = nil + a.paths = nil + + return nil +} + +func (a *archive) OpenFile(path string) (File, error) { + cs := C.CString(path) + defer C.free(unsafe.Pointer(cs)) + + file := &file{size: math.MaxUint32} + if result := C.SFileOpenFileEx(a.handle, cs, 0, &file.handle); result == 0 { + return nil, fmt.Errorf("failed to open file (%d)", getLastError()) + } + + return file, nil +} + +func (a *archive) GetPaths() ([]string, error) { + if len(a.paths) > 0 { + return a.paths, nil + } + + f, err := a.OpenFile("(listfile)") + if err != nil { + return nil, err + } + + var buff bytes.Buffer + if _, err := io.Copy(&buff, f); err != nil { + return nil, err + } + + for _, line := range strings.Split(string(buff.Bytes()), "\r\n") { + line = strings.TrimSpace(line) + if len(line) > 0 { + a.paths = append(a.paths, line) + } + } + + return a.paths, nil +} + +func getLastError() uint { + return uint(C.GetLastError()) +}