Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
Import/Export Mods from Tabletop Simulator, including all assets.

## Status

Currently this code is rather alpha quality. It has also only been tested on a limited number of mods and machines. **Do not rely on this to backup your files without checking it restores correctly on another install.** If you find a configuration / mod that doesn't work, please let me know.
Currently this code is rather alpha quality. It has also only been tested on a limited number of mods and machines.
**Do not rely on this to backup your files without checking it restores correctly on another install.**
If you find a configuration / mod that doesn't work, please let me know.

Listing, export and import all should work. Note that old-style mods (`.cjc` files) are *not* supported - they simply will not be listed.

To export a mod, you ideally should have downloaded *all* assets. Opening a mod in Tabletop Simulator is usually enough, but make sure you have taken something out of every bag in the mod. If anything is missing, then the tool will tell you. TTS Manager can attempt to download the files for you, but this feature is very new.
To export a mod, you ideally should have downloaded *all* assets.
Opening a mod in Tabletop Simulator is usually enough, but make sure you have taken something out of every bag in the mod.
If anything is missing, then the tool will tell you.
TTS Manager can attempt to download the files for you, but this feature is very new.

## Quickstart
Download the installer from the [releases](https://github.com/cwoac/TTS-Manager/releases) and install it. Then run the gui from the created shortcut link.
Expand All @@ -26,3 +30,9 @@ These are primarily tracked on github, but roughly:
- A better gui
- downloading arbitary pak files
- LOTS MORE TESTING.

## Changelog
* V0.6.0
- Now correctly grabs non-image/model types for the new layout of TTS mods
- Added ability to export just the 'unavailable' files of a mod to a 'part.pak' file.
- Various bug fixes and preliminary Linux/Mac support from 'bobpaul' (thanks!)
308 changes: 150 additions & 158 deletions tts/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,172 +1,164 @@
import os
import os.path
import tts
import platform
if platform.system() == 'Linux':
import xdgappdirs
from typing import Tuple

def standard_basepath():
if platform.system() == 'Windows':
basepath = os.path.join(os.path.expanduser("~"),"Documents","My Games","Tabletop Simulator")
elif platform.system() == 'Linux':
basepath = os.path.join(xdgappdirs.user_data_dir(),"Tabletop Simulator")
else:
basepath = os.path.join(os.path.expanduser("~"),"Library","Tabletop Simulator")
return basepath

class FileSystem:
def __init__(self,base_path=None,tts_install_path=None):
if base_path is not None:
self.basepath=base_path
else:
self.basepath=standard_basepath()
if tts_install_path is not None:
self.modpath=os.path.join(tts_install_path,"Tabletop Simulator_Data")
else:
self.modpath=self.basepath
self._saves = os.path.join(self.basepath,"Saves")
self._chest = os.path.join(self._saves,"Chest")
self._mods = os.path.join(self.modpath,"Mods")
self._images= os.path.join(self._mods,"Images")
self._models= os.path.join(self._mods,"Models")
self._workshop = os.path.join(self._mods,"Workshop")

def get_dir_by_type(self,save_type):
st={
tts.SaveType.workshop:self._workshop,
tts.SaveType.save:self._saves,
tts.SaveType.chest:self._chest
}
return st[save_type]

def check_dirs(self):
"""Do all the directories exist?"""
for dir in [ self._saves, self._mods, self._images, self._models, self._workshop ]:
if not os.path.isdir(dir):
tts.logger().error("TTS Dir missing: {}".format(dir))
return False
#These directories don't always exist, and that's OK
for dir in [ self._chest ]:
if not os.path.isdir(dir):
tts.logger().warn("TTS Dir missing: {}".format(dir))
return True

def create_dirs(self):
"""Attempt to create any missing directories."""
for dir in [ self._saves, self._chest, self._mods, self._images, self._models, self._workshop ]:
os.makedirs(dir,exist_ok=True)

@property
def saves_dir(self):
return self._saves

@property
def images_dir(self):
return self._images

def get_image_path(self,filename):
return os.path.join(self._images,filename)

def get_model_path(self,filename):
return os.path.join(self._models,filename)

def get_workshop_path(self,filename):
return os.path.join(self._workshop,filename)

def get_save_path(self,filename):
return os.path.join(self._saves,filename)

def get_chest_path(self,filename):
return os.path.join(self._chest,filename)

def get_path_by_type(self,filename,save_type):
return os.path.join(self.get_dir_by_type(save_type),filename)

def find_details(self,basename):
result=self.find_image(basename)
if result:
return result,True
result=self.find_model(basename)
if result:
return result,False
return None,None

def find_image(self,basename):
result=None
stripname = tts.strip_filename(basename)
for image_format in ['.png','.jpg','.bmp']:
filename=os.path.join(self._images,stripname+image_format)
if os.path.isfile(filename):
result=filename
break
return result
import tts
import tts.util
from tts.filetype import FileType

def find_model(self,basename):
result=None
stripname = tts.strip_filename(basename)
for model_format in ['.obj']:
filename=os.path.join(self._models,stripname+model_format)
if os.path.isfile(filename):
result=filename
break
return result
if platform.system() == 'Linux':
import xdgappdirs

def get_filenames_in(self,search_path):
if not os.path.isdir(search_path):
tts.logger().warn("Tried to search non-existent path {}.".format(search_path))
return []
return [os.path.splitext(file)[0] for file in os.listdir(search_path) if os.path.splitext(file)[1].lower()=='.json']

def get_save_filenames(self):
files=self.get_filenames_in(self._saves)
if files and 'SaveFileInfos' in files:
files.remove('SaveFileInfos')
return files

def get_workshop_filenames(self):
files=self.get_filenames_in(self._workshop)
if files and 'WorkshopFileInfos' in files:
files.remove('WorkshopFileInfos')
return files

def get_chest_filenames(self):
return self.get_filenames_in(self._chest)

def get_filenames_by_type(self,save_type):
if save_type==tts.SaveType.workshop:
return self.get_workshop_filenames()
if save_type==tts.SaveType.save:
return self.get_save_filenames()
if save_type==tts.SaveType.chest:
return self.get_chest_filenames()
# TODO: error handling here
return None

def get_json_filename_from(self,basename,paths):
result=None
def get_json_filename_from(basename, paths):
result = None
for pth in paths:
filename=os.path.join(pth,basename+'.json')
if os.path.isfile(filename):
result=filename
break
filename = os.path.join(pth, basename + '.json')
if os.path.isfile(filename):
result = filename
break
# TODO: error handling here
return result

def get_json_filename(self,basename):
return self.get_json_filename_from(basename,[self._workshop,self._saves,self._chest])

def get_json_filename_for_type(self,basename,save_type):
return self.get_json_filename_from(basename,[self.get_dir_by_type(save_type)])
def get_filenames_in(search_path):
if not os.path.isdir(search_path):
tts.logger().warn("Tried to search non-existent path {}.".format(search_path))
return []
return [os.path.splitext(file)[0] for file in os.listdir(search_path) if
os.path.splitext(file)[1].lower() == '.json']

def get_json_filename_type(self,basename):
if os.path.isfile(os.path.join(self._workshop,basename+'.json')):
return tts.SaveType.workshop
if os.path.isfile(os.path.join(self._saves,basename+'.json')):
return tts.SaveType.save
if os.path.isfile(os.path.join(self._chest,basename+'.json')):
return tts.SaveType.chest
# TODO: error handling here
return None

def __str__(self):
return "Saves: {} Mods: {}".format(self.basepath,self.modpath)
def standard_basepath():
if platform.system() == 'Windows':
basepath = os.path.join(os.path.expanduser("~"), "Documents", "My Games", "Tabletop Simulator")
elif platform.system() == 'Linux':
basepath = os.path.join(xdgappdirs.user_data_dir(), "Tabletop Simulator")
else:
basepath = os.path.join(os.path.expanduser("~"), "Library", "Tabletop Simulator")
return basepath


class FileSystem:
def __init__(self, base_path=None, tts_install_path=None):
if base_path is not None:
self.basepath = base_path
else:
self.basepath = standard_basepath()
if tts_install_path is not None:
self.modpath = os.path.join(tts_install_path, "Tabletop Simulator_Data")
else:
self.modpath = self.basepath
self._saves = os.path.join(self.basepath, "Saves")
self._chest = os.path.join(self._saves, "Chest")
self._mods = os.path.join(self.modpath, "Mods")
self._images = os.path.join(self._mods, "Images")
self._models = os.path.join(self._mods, "Models")
self._workshop = os.path.join(self._mods, "Workshop")

def get_dir_by_type(self, save_type):
st = {
tts.SaveType.workshop: self._workshop,
tts.SaveType.save: self._saves,
tts.SaveType.chest: self._chest
}
return st[save_type]

def check_dirs(self):
"""Do all the directories exist?"""
for dir in [self._saves, self._mods, self._images, self._models, self._workshop]:
if not os.path.isdir(dir):
tts.logger().error("TTS Dir missing: {}".format(dir))
return False
# These directories don't always exist, and that's OK
for dir in [self._chest]:
if not os.path.isdir(dir):
tts.logger().warn("TTS Dir missing: {}".format(dir))
return True

def create_dirs(self):
"""Attempt to create any missing directories."""
for dir in [self._saves, self._chest, self._mods, self._images, self._models, self._workshop]:
os.makedirs(dir, exist_ok=True)

@property
def saves_dir(self):
return self._saves

@property
def images_dir(self):
return self._images

def get_dir(self, type: FileType) -> str:
return os.path.join(self._mods, type.value)

def get_file_path(self, file_name: str, file_type: FileType) -> str:
return os.path.join(self.get_dir(file_type), file_name)

def get_path_by_save_type(self, filename, save_type):
return os.path.join(self.get_dir_by_type(save_type), filename)

def check_for_file_location(self, basename: str, type: FileType) -> Tuple[str, str]:
if type is FileType.IMAGE:
return self.find_image(basename)
if type is FileType.NONE:
return None
extension = type.get_extension(None)
filename = os.path.join(self.get_dir(type),
f"{tts.util.strip_filename(basename)}{extension}")
return (filename, extension) if os.path.isfile(filename) else None

def find_image(self, basename: str) -> str:
result = None
stripname = tts.util.strip_filename(basename)
for image_format in ['.png', '.jpg', '.bmp']:
filename = os.path.join(self._images, stripname + image_format)
if os.path.isfile(filename):
result = filename, image_format
break
return result

def get_save_filenames(self):
files = get_filenames_in(self._saves)
if files and 'SaveFileInfos' in files:
files.remove('SaveFileInfos')
return files

def get_workshop_filenames(self):
files = get_filenames_in(self._workshop)
if files and 'WorkshopFileInfos' in files:
files.remove('WorkshopFileInfos')
return files

def get_chest_filenames(self):
return get_filenames_in(self._chest)

def get_filenames_by_type(self, save_type):
if save_type == tts.SaveType.workshop:
return self.get_workshop_filenames()
if save_type == tts.SaveType.save:
return self.get_save_filenames()
if save_type == tts.SaveType.chest:
return self.get_chest_filenames()
# TODO: error handling here
return None

def get_json_filename(self, basename):
return get_json_filename_from(basename, [self._workshop, self._saves, self._chest])

def get_json_filename_for_type(self, basename, save_type):
return get_json_filename_from(basename, [self.get_dir_by_type(save_type)])

def get_json_filename_type(self, basename):
if os.path.isfile(os.path.join(self._workshop, basename + '.json')):
return tts.SaveType.workshop
if os.path.isfile(os.path.join(self._saves, basename + '.json')):
return tts.SaveType.save
if os.path.isfile(os.path.join(self._chest, basename + '.json')):
return tts.SaveType.chest
# TODO: error handling here
return None

def __str__(self):
return "Saves: {} Mods: {}".format(self.basepath, self.modpath)
Loading