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 miner/fsd_built.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def get_data(self, container_name, language=None, verbose=False, **kwargs):
except KeyError:
self._container_not_found(container_name)
else:
if os.name != 'nt' or struct.calcsize('P') * 8 != 64:
msg = 'need 64-bit python under Windows to execute loader'
if not self._platform_supported(os.name, sys.platform, struct.calcsize('P') * 8):
msg = 'need 64-bit Python on Windows or macOS to execute loader'
raise PlatformError(msg)
loader_filename = loader_respath.split('/')[-1]
loader_info = self._resbrowser.get_file_info(loader_respath)
Expand Down Expand Up @@ -87,7 +87,11 @@ def _contname_fsdfiles_map(self):
loaders = {}
datas = {}
for resource_path in self._resbrowser.respath_iter():
m = re.match(r'^app:/bin64/(\w+/)*(?P<name>\w+)Loader.pyd$', resource_path, flags=re.UNICODE)
m = re.match(
r'^app:/(?:.+/)?bin64/(\w+/)*(?P<name>\w+)Loader\.(?:pyd|so)$',
resource_path,
flags=re.UNICODE,
)
if m:
loaders[m.group('name').lower()] = resource_path
continue
Expand Down Expand Up @@ -124,6 +128,12 @@ def _compare_files(self, file1_path, file2_path):
with open(file1_path, 'rb') as f1, open(file2_path, 'rb') as f2:
return f1.read() == f2.read()

@staticmethod
def _platform_supported(os_name, sys_platform, pointer_bits):
if pointer_bits != 64:
return False
return os_name == 'nt' or sys_platform == 'darwin'


class PlatformError(Exception):
"""Raised when FSD binary miner is used on incorrect platform."""
Expand Down
4 changes: 3 additions & 1 deletion miner/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def get_data(self, container_name, **kwargs):
if container_name != self._container_name:
self._container_not_found(container_name)
else:
file_info = self._resbrowser.get_file_info('app:/start.ini')
file_info = self._resbrowser.get_file_info(
self._resbrowser.find_resource_path('start.ini', prefix='app:/')
)
field_names = ('field_name', 'field_value')
container_data = []
# Read client version
Expand Down
177 changes: 177 additions & 0 deletions tests/test_frontier_resource_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import configparser
import importlib.util
import os
import sys
import tempfile
import types
import unittest


ROOT = os.path.dirname(os.path.dirname(__file__))


class cachedproperty(object):
def __init__(self, method):
self.__method = method

def __get__(self, instance, owner):
if instance is None:
return self
value = self.__method(instance)
setattr(instance, self.__method.__name__, value)
return value


def load_module(module_name, relpath, extra_modules=None):
extra_modules = extra_modules or {}
saved = {}
for name, module in extra_modules.items():
saved[name] = sys.modules.get(name)
sys.modules[name] = module
try:
spec = importlib.util.spec_from_file_location(
module_name,
os.path.join(ROOT, relpath),
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
finally:
for name, module in extra_modules.items():
if saved[name] is None:
del sys.modules[name]
else:
sys.modules[name] = saved[name]


class FrontierPathFixTests(unittest.TestCase):
def test_resource_browser_supports_nested_frontier_paths(self):
util_module = types.ModuleType('util')
util_module.cachedproperty = cachedproperty
resource_browser = load_module(
'test_resource_browser',
'util/resource_browser.py',
{'util': util_module},
)

with tempfile.TemporaryDirectory() as eve_dir:
nested_index = os.path.join(
eve_dir,
'stillness',
'EVE.app',
'Contents',
'Resources',
'build',
)
os.makedirs(nested_index)
expected = os.path.join(nested_index, 'resfileindex.txt')
with open(expected, 'w'):
pass

browser = resource_browser.ResourceBrowser(eve_dir, 'stillness')
browser._resource_index = {
'app:/EVE.app/Contents/Resources/build/start.ini': None,
}

self.assertEqual(browser._resolve_resfileindex_path(), expected)
self.assertEqual(
browser.find_resource_path('start.ini', prefix='app:/'),
'app:/EVE.app/Contents/Resources/build/start.ini',
)

def test_metadata_uses_suffix_lookup_for_start_ini(self):
configparser_module = types.ModuleType('ConfigParser')
configparser_module.ConfigParser = configparser.ConfigParser
miner_base_module = types.ModuleType('miner.base')

class BaseMiner(object):
def _container_not_found(self, container_name):
raise AssertionError(container_name)

miner_base_module.BaseMiner = BaseMiner

metadata_module = load_module(
'miner.metadata_test',
'miner/metadata.py',
{
'ConfigParser': configparser_module,
'miner.base': miner_base_module,
},
)

class Browser(object):
def __init__(self):
self.lookup_calls = []
self.info_calls = []

def find_resource_path(self, suffix, prefix=None):
self.lookup_calls.append((suffix, prefix))
return 'app:/EVE.app/Contents/Resources/build/start.ini'

def get_file_info(self, resource_path):
self.info_calls.append(resource_path)
return types.SimpleNamespace(file_abspath=self.start_ini_path)

with tempfile.TemporaryDirectory() as temp_dir:
start_ini_path = os.path.join(temp_dir, 'start.ini')
with open(start_ini_path, 'w') as start_ini:
start_ini.write('[main]\nbuild = 12345\n')

browser = Browser()
browser.start_ini_path = start_ini_path
miner = metadata_module.MetadataMiner(browser)
miner.get_data('metadata')

self.assertEqual(browser.lookup_calls, [('start.ini', 'app:/')])
self.assertEqual(
browser.info_calls,
['app:/EVE.app/Contents/Resources/build/start.ini'],
)

def test_fsd_built_accepts_macos_loader_paths(self):
util_module = types.ModuleType('util')
util_module.cachedproperty = cachedproperty

class EveNormalizer(object):
def run(self, fsd_data, loader_module=None):
return fsd_data

util_module.EveNormalizer = EveNormalizer
miner_base_module = types.ModuleType('miner.base')

class BaseMiner(object):
def _container_not_found(self, container_name):
raise AssertionError(container_name)

miner_base_module.BaseMiner = BaseMiner

fsd_built_module = load_module(
'miner.fsd_built_test',
'miner/fsd_built.py',
{
'util': util_module,
'miner.base': miner_base_module,
},
)

class Browser(object):
def respath_iter(self):
return iter((
'app:/EVE.app/Contents/Resources/build/bin64/foo/ExampleLoader.so',
'res:/staticdata/foo/Example.fsdbinary',
))

miner = fsd_built_module.FsdBuiltMiner(Browser(), translator=None)

self.assertTrue(fsd_built_module.FsdBuiltMiner._platform_supported('posix', 'darwin', 64))
self.assertEqual(
miner._contname_fsdfiles_map['example'],
(
'app:/EVE.app/Contents/Resources/build/bin64/foo/ExampleLoader.so',
'res:/staticdata/foo/Example.fsdbinary',
),
)


if __name__ == '__main__':
unittest.main()
30 changes: 29 additions & 1 deletion util/resource_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ def get_file_info(self, resource_path):
self.__verify_data(data=data, file_info=file_info)
return file_info

def find_resource_path(self, suffix, prefix=None):
target_suffix = '/{}'.format(suffix.lstrip('/'))
for resource_path in self._resource_index:
if prefix is not None and not resource_path.startswith(prefix):
continue
if resource_path.endswith(target_suffix):
return resource_path
raise KeyError(suffix)

def __verify_data(self, data, file_info):
if len(data) != file_info.file_size:
raise FileIntegrityError(u'file size mismatch when reading {}'.format(file_info.resource_path))
Expand All @@ -82,7 +91,7 @@ def __verify_data(self, data, file_info):
@cachedproperty
def _resource_index(self):
index = {}
res_index_path = os.path.join(self._eve_path, self._server_alias, 'resfileindex.txt')
res_index_path = self._resolve_resfileindex_path()
with open(res_index_path) as f:
for resource_path, file_relpath, file_hash, file_size, compressed_size in csv.reader(f):
index[resource_path] = FileInfo(
Expand All @@ -104,6 +113,25 @@ def _resource_index(self):
compressed_size=int(compressed_size))
return index

def _resolve_resfileindex_path(self):
legacy_path = os.path.join(self._eve_path, self._server_alias, 'resfileindex.txt')
if os.path.exists(legacy_path):
return legacy_path

frontier_path = os.path.join(
self._eve_path,
self._server_alias,
'EVE.app',
'Contents',
'Resources',
'build',
'resfileindex.txt',
)
if os.path.exists(frontier_path):
return frontier_path

return legacy_path


class FileIntegrityError(Exception):
pass