Skip to content

Commit

Permalink
add FUSE support for Windows via Dokany
Browse files Browse the repository at this point in the history
  • Loading branch information
Talv committed Jul 31, 2019
1 parent 704e7d0 commit 0dc9961
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 37 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## [2.1.0] - 2019-07-31

* Added basic support for mounting CASC as filesystem visible to the OS using [FUSE](https://github.com/libfuse/libfuse).
* Linux will work out of the box.
* MacOS is likely to work but hasn't been tested.
* Under Windows app is compiled against `dokanfuse` wrapper library. In order for it to work `Dokany` must be installed on the system.

## [2.0.0] - 2019-07-26

* Refactored entire codebase..
Expand Down
24 changes: 15 additions & 9 deletions CMakeModules/FindFUSE.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,31 @@

# check if already in cache, be silent
IF (FUSE_INCLUDE_DIR)
SET (FUSE_FIND_QUIETLY TRUE)
SET (FUSE_FIND_QUIETLY TRUE)
ENDIF (FUSE_INCLUDE_DIR)

SET(DOKANY_DIR "C:\\Program Files\\Dokan\\Dokan Library-1.3.0")

# find includes
FIND_PATH (FUSE_INCLUDE_DIR fuse.h
/usr/local/include/osxfuse
/usr/local/include
/usr/include
/usr/local/include/osxfuse
/usr/local/include
/usr/include
"${DOKANY_DIR}\\include"
)

# find lib
if (APPLE)
if(APPLE)
SET(FUSE_NAMES libosxfuse.dylib fuse)
else (APPLE)
elseif (WIN32)
SET(FUSE_NAMES dokanfuse1)
else()
SET(FUSE_NAMES fuse)
endif (APPLE)
endif(APPLE)

FIND_LIBRARY(FUSE_LIBRARIES
NAMES ${FUSE_NAMES}
PATHS /lib64 /lib /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib
NAMES ${FUSE_NAMES}
PATHS /lib64 /lib /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib "${DOKANY_DIR}\\lib"
)

include (FindPackageHandleStandardArgs)
Expand Down
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ Usage:
-x, --extract-all Extract all files matching search filters.
-X, --extract-file [FILE...] Extract file(s) matching exactly.
-o, --outdir [PATH] Output directory for extracted files.
(default: ./)
(default: .)
-p, --stdout Pipe content of a file(s) to stdout instead
writing it to the filesystem.
-P, --progress Notify about progress during extraction.
-n, --dry-run Simulate extraction process without writing
any data to the filesystem.
Mount options:
-m, --mount [MOUNTPOINT] Mount CASC as a filesystem
```

### Examples
Expand All @@ -103,7 +106,7 @@ stormex '/mnt/s1/BnetGameLib/StarCraft II' -ld | sort -h

```sh
stormex '/mnt/s1/BnetGameLib/StarCraft II' \
-I '\/(DocumentInfo|Objects|Regions|Triggers)$' \
-I '\\(DocumentInfo|Objects|Regions|Triggers)$' \
-I '\.(fx|xml|txt|json|galaxy|SC2Style|SC2Hotkeys|SC2Lib|TriggerLib|SC2Interface|SC2Locale|SC2Components|SC2Layout)$' \
-E '(dede|eses|esmx|frfr|itit|kokr|plpl|ptbr|ruru|zhcn|zhtw)\.sc2data' \
-E '(PreloadAssetDB|TextureReductionValues)\.txt$' \
Expand All @@ -122,6 +125,40 @@ stormex -S '/mnt/s1/BnetGameLib/StarCraft II' -X 'mods/core.sc2mod/base.sc2data/
stormex -S '/mnt/s1/BnetGameLib/StarCraft II' -X 'mods/core.sc2mod/base.sc2data/EditorData/Images/EditorLogo.dds' -p | magick dds: png: | display png:
```

#### Mount as FUSE filesystem

```sh
mkdir -p cascfs
stormex -v -S '/mnt/s1/BnetGameLib/StarCraft II' -m ./cascfs
```

Result:

```sh
$ ls -l ./cascfs
dr-xr-xr-- - root 1 Jan 1970 campaigns
dr-xr-xr-- - root 1 Jan 1970 CKEY
.r-xr-xr-- 17M root 1 Jan 1970 DOWNLOAD
.r-xr-xr-- 41M root 1 Jan 1970 ENCODING
.r-xr-xr-- 59k root 1 Jan 1970 INSTALL
dr-xr-xr-- - root 1 Jan 1970 mods
.r-xr-xr-- 20M root 1 Jan 1970 ROOT
dr-xr-xr-- - root 1 Jan 1970 versions.osxarchive
dr-xr-xr-- - root 1 Jan 1970 versions.winarchive
```

##### Windows support via Dokany

[Dokany](https://github.com/dokan-dev) provides a FUSE wrapper for Windows. You've to install [Dokany's system driver](https://github.com/dokan-dev/dokany/wiki/Installation) in order for this feature to work.

As `[MOUNTPOINT]` argument provide a free drive letter. In following example CASC will be mounted at `S:\`.

```sh
stormex.exe -v -S X:\shared\SC2.4.8.4.73286 -m S
```

[![](https://i.imgur.com/1y7zCTL.png)](https://i.imgur.com/1y7zCTL.png)

## Credits

* Powered by [CascLib](https://github.com/ladislav-zezula/CascLib)
2 changes: 1 addition & 1 deletion include/cascfuse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

#include "storage.hpp"

int cascf_mount(const std::string& mountPoint, HANDLE hStorage);
int cascfs_mount(const std::string& mountPoint, HANDLE hStorage);
53 changes: 30 additions & 23 deletions src/cascfuse.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
#include "common.hpp"
#include "util.hpp"

enum FsNodeKind {
#ifndef WIN32
#define FUSE_STAT struct stat
#define FUSE_OFF_T off_t
#endif

enum class FsNodeKind {
Unknown,
Root,
Folder,
Expand Down Expand Up @@ -227,11 +232,11 @@ class FsTree {

FsTree cfFileTree;

static int cascf_getattr(const char *path, struct stat *stbuf)
static int cascfs_getattr(const char *path, FUSE_STAT *stbuf)
{
LOG_VERBOSE << path;
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
memset(stbuf, 0, sizeof(*stbuf));

auto fNode = cfFileTree.GetNodeAtPath(path);
if (fNode != NULL) {
Expand Down Expand Up @@ -268,7 +273,7 @@ static int cascf_getattr(const char *path, struct stat *stbuf)
return res;
}

static int cascf_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
static int cascfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, FUSE_OFF_T offset, struct fuse_file_info *fi)
{
LOG_VERBOSE << path;

Expand All @@ -287,7 +292,7 @@ static int cascf_readdir(const char *path, void *buf, fuse_fill_dir_t filler, of
return 0;
}

static int cascf_open(const char *path, struct fuse_file_info *fi)
static int cascfs_open(const char *path, struct fuse_file_info *fi)
{
LOG_VERBOSE << path;

Expand All @@ -302,7 +307,7 @@ static int cascf_open(const char *path, struct fuse_file_info *fi)
return 0;
}

static int cascf_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
static int cascfs_read(const char *path, char *buf, size_t size, FUSE_OFF_T offset, struct fuse_file_info *fi)
{
LOG_VERBOSE << path << " at " << offset << " size " << size;

Expand Down Expand Up @@ -335,25 +340,12 @@ static int cascf_read(const char *path, char *buf, size_t size, off_t offset, st
}
}

static struct fuse_operations cascf_oper = {
.getattr = cascf_getattr,
.open = cascf_open,
.read = cascf_read,
.readdir = cascf_readdir,
};

struct TFileTreeRootPub : public TFileTreeRoot
{
public:
CASC_FILE_TREE FileTree;
};

void cascf_populate(HANDLE hStorage)
void cascfs_populate(HANDLE hStorage)
{
auto hs = IsValidCascStorageHandle(hStorage);
cfFileTree.m_hStorage = hStorage;

PLOG_INFO << "Building file tree..";
LOG_DEBUG << "Building file tree..";

CASC_FIND_DATA findData;
HANDLE handle = CascFindFirstFile(hStorage, "*", &findData, NULL);
Expand Down Expand Up @@ -390,20 +382,31 @@ void cascf_populate(HANDLE hStorage)
cfFileTree.GenerateNodeHashMap(cfFileTree.GetRootNode());
}

int cascf_mount(const std::string& mountPoint, HANDLE hStorage)
static struct fuse_operations cascf_oper;

int cascfs_mount(const std::string& mountPoint, HANDLE hStorage)
{
cascf_populate(hStorage);
cascf_oper.getattr = cascfs_getattr;
cascf_oper.open = cascfs_open;
cascf_oper.read = cascfs_read;
cascf_oper.readdir = cascfs_readdir;

cascfs_populate(hStorage);

LOG_DEBUG << "Preparing to mount..";

#ifndef WIN32
if (!pathExists(mountPoint)) {
LOG_FATAL << "Path doesn't exist or is not accessible" << mountPoint;
return -2;
}
#endif

auto fChan = fuse_mount(mountPoint.c_str(), NULL);
if (fChan != NULL) {
auto fHandle = fuse_new(fChan, NULL, &cascf_oper, sizeof(cascf_oper), NULL);
if (fHandle != NULL) {
LOG_INFO << "cascfs " << static_cast<void*>(fHandle) << " mounted at " << mountPoint;
struct fuse_session *se = fuse_get_session(fHandle);
if (fuse_set_signal_handlers(se) == 0) {
LOG_DEBUG << "Entering CASC-FS loop..";
Expand All @@ -416,6 +419,10 @@ int cascf_mount(const std::string& mountPoint, HANDLE hStorage)
fuse_unmount(mountPoint.c_str(), fChan);
fuse_destroy(fHandle);
}
else {
LOG_FATAL << "fuse_new failed " << static_cast<void*>(fHandle);
return -2;
}
}
else {
LOG_FATAL << "Couldn't mount to " << mountPoint;
Expand Down
4 changes: 2 additions & 2 deletions src/stormex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ int main(int argc, char* argv[])
StorageExplorer stExplorer;
int tmp;

PLOG_INFO << "Opening storage..";
LOG_DEBUG << "Opening storage..";
if ((tmp = stExplorer.openStorage(appCtx.m_base.storageSrc)) != 0) {
PLOG_FATAL << "Failed to open the storage: " << appCtx.m_base.storageSrc << " E(" << tmp << ")";
exit(-1);
Expand All @@ -310,7 +310,7 @@ int main(int argc, char* argv[])

try {
if (appCtx.m_mount.mountPoint.length()) {
return cascf_mount(appCtx.m_mount.mountPoint, stExplorer.getHandle());
return cascfs_mount(appCtx.m_mount.mountPoint, stExplorer.getHandle());
}

auto fResults = enumerateFiles(stExplorer);
Expand Down

0 comments on commit 0dc9961

Please sign in to comment.