179 lines
4.0 KiB
Go
179 lines
4.0 KiB
Go
package mpq
|
|
|
|
// #cgo LDFLAGS: -L./stormlib/ -lstorm -lz -lbz2 -lstdc++
|
|
// #include <stdlib.h>
|
|
// #define DWORD unsigned int
|
|
// #define HANDLE void *
|
|
// #define LONG int
|
|
// #define LPDWORD unsigned int *
|
|
// #define LPOVERLAPPED void *
|
|
// #define TCHAR char
|
|
// #define WINAPI
|
|
// #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 SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod);
|
|
// 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"
|
|
"os"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
type File interface {
|
|
Read(data []byte) (int, error)
|
|
Seek(offset int64, whence int) (int64, error)
|
|
Close() error
|
|
}
|
|
|
|
type Archive interface {
|
|
OpenFile(path string) (File, error)
|
|
GetPaths() []string
|
|
Close() error
|
|
}
|
|
|
|
func NewFromFile(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())
|
|
}
|
|
|
|
if err := a.buildPathMap(); err != nil {
|
|
a.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
type file struct {
|
|
handle unsafe.Pointer
|
|
}
|
|
|
|
func (f *file) Read(data []byte) (int, error) {
|
|
var bytesRead int
|
|
if result := C.SFileReadFile(f.handle, unsafe.Pointer(&data[0]), C.uint(len(data)), (*C.uint)(unsafe.Pointer(&bytesRead)), nil); result == 0 {
|
|
lastError := getLastError()
|
|
if lastError == 1002 { // ERROR_HANDLE_EOF
|
|
return bytesRead, io.EOF
|
|
}
|
|
|
|
return 0, fmt.Errorf("failed to read file (%d)", lastError)
|
|
}
|
|
|
|
return bytesRead, nil
|
|
}
|
|
|
|
func (f *file) Seek(offset int64, whence int) (int64, error) {
|
|
var method uint
|
|
switch whence {
|
|
case io.SeekStart:
|
|
method = 0 // FILE_BEGIN
|
|
case io.SeekCurrent:
|
|
method = 1 // FILE_CURRENT
|
|
case io.SeekEnd:
|
|
method = 2 // FILE_END
|
|
}
|
|
|
|
result := C.SFileSetFilePointer(f.handle, C.int(offset), nil, C.uint(method))
|
|
if result == math.MaxUint32 { // SFILE_INVALID_SIZE
|
|
return 0, fmt.Errorf("failed to set file pointer (%d)", getLastError())
|
|
}
|
|
|
|
return int64(result), 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
|
|
return nil
|
|
}
|
|
|
|
type archive struct {
|
|
handle unsafe.Pointer
|
|
paths map[string]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) {
|
|
if pathInt, ok := a.paths[path]; ok {
|
|
path = pathInt
|
|
}
|
|
|
|
cs := C.CString(path)
|
|
defer C.free(unsafe.Pointer(cs))
|
|
|
|
file := new(file)
|
|
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 {
|
|
var extPaths []string
|
|
for extPath := range a.paths {
|
|
extPaths = append(extPaths, extPath)
|
|
}
|
|
|
|
return extPaths
|
|
}
|
|
|
|
func (a *archive) buildPathMap() error {
|
|
f, err := a.OpenFile("(listfile)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
var buff bytes.Buffer
|
|
if _, err := io.Copy(&buff, f); err != nil {
|
|
return err
|
|
}
|
|
|
|
a.paths = make(map[string]string)
|
|
|
|
lines := strings.Split(string(buff.Bytes()), "\r\n")
|
|
for _, line := range lines {
|
|
pathInt := strings.TrimSpace(line)
|
|
if len(pathInt) > 0 {
|
|
pathExt := santizePath(pathInt)
|
|
a.paths[pathExt] = pathInt
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func santizePath(path string) string {
|
|
return strings.ToLower(strings.Replace(path, "\\", string(os.PathSeparator), -1))
|
|
}
|
|
|
|
func getLastError() uint {
|
|
return uint(C.GetLastError())
|
|
}
|