from flask import ( Blueprint, send_file, g, redirect, url_for, make_response, abort, current_app ) from flask_user import login_required from app.models import CharacterInfo import glob import os from wand import image from wand.exceptions import BlobError as BE import pathlib import json import sqlite3 import xml.etree.ElementTree as ET luclient_blueprint = Blueprint('luclient', __name__) locale = {} @luclient_blueprint.route('/get_dds_as_png/') @login_required def get_dds_as_png(filename): if filename.split('.')[-1] != 'dds': return (404, "NO") cache = f'cache/{filename.split(".")[0]}.png' if not os.path.exists(cache): root = f"{current_app.config['CLIENT_LOCATION']}res/" path = glob.glob( root + f'**/{filename}', recursive=True )[0] with image.Image(filename=path) as img: img.compression = "no" img.save(filename=current_app.config["CACHE_LOCATION"] + filename.split('.')[0] + '.png') return send_file(cache) @luclient_blueprint.route('/get_dds/') @login_required def get_dds(filename): if filename.split('.')[-1] != 'dds': return 404 root = f"{current_app.config['CLIENT_LOCATION']}res/" dds = glob.glob( root + f'**/{filename}', recursive=True )[0] return send_file(dds) @luclient_blueprint.route('/get_icon_lot/') @login_required def get_icon_lot(id): if id is None: redirect(url_for('luclient.unknown')) render_component_id = query_cdclient( 'select component_id from ComponentsRegistry where component_type = 2 and id = ?', [id], one=True ) if render_component_id is not None: render_component_id = render_component_id[0] else: return redirect(url_for('luclient.unknown')) # find the asset from rendercomponent given the component id filename = query_cdclient( 'select icon_asset from RenderComponent where id = ?', [render_component_id], one=True )[0] if filename: filename = filename.replace("..\\", "").replace("\\", "/") else: return redirect(url_for('luclient.unknown')) cache = f'{current_app.config["CACHE_LOCATION"]}{filename.split(".")[0]}.png' if not os.path.exists(cache): root = f"{current_app.config['CLIENT_LOCATION']}res/" try: pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True) with image.Image(filename=f'{root}{filename}'.lower()) as img: img.compression = "no" img.save(filename=cache) except BE: return redirect(url_for('luclient.unknown')) return send_file(pathlib.Path(cache).resolve()) @luclient_blueprint.route('/get_icon_iconid/') @login_required def get_icon_iconid(id): filename = query_cdclient( 'select IconPath from Icons where IconID = ?', [id], one=True )[0] filename = filename.replace("..\\", "").replace("\\", "/") cache = f'{current_app.config["CACHE_LOCATION"]}{filename.split(".")[0]}.png' if not os.path.exists(cache): root = f"{current_app.config['CLIENT_LOCATION']}res/" try: pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True) with image.Image(filename=f'{root}{filename}'.lower()) as img: img.compression = "no" img.save(filename=cache) except BE: return redirect(url_for('luclient.unknown')) return send_file(pathlib.Path(cache).resolve()) @luclient_blueprint.route('/ldddb/') @login_required def brick_list(): brick_list = [] if len(brick_list) == 0: suffixes = [".g", ".g1", ".g2", ".g3", ".xml"] cache = pathlib.Path(f"{current_app.config['CACHE_LOCATION']}") # Load g files for path in cache.rglob("*.*"): if str(path.suffix) in suffixes: brick_list.append( { "type": "file", "name": str(path.as_posix()).replace(f"{current_app.config['CACHE_LOCATION']}", "") } ) response = make_response(json.dumps(brick_list)) response.headers.set('Content-Type', 'application/json') return response @luclient_blueprint.route('/ldddb/', defaults={'req_path': ''}) @luclient_blueprint.route('/ldddb/') def dir_listing(req_path): # Joining the base and the requested path rel_path = pathlib.Path(str(pathlib.Path(f"{current_app.config['CACHE_LOCATION']}/{req_path}").resolve())) # Return 404 if path doesn't exist if not rel_path.exists(): return abort(404) # Check if path is a file and serve if rel_path.is_file(): return send_file(rel_path) else: return abort(404) @luclient_blueprint.route('/unknown') @login_required def unknown(): filename = "textures/ui/inventory/unknown.dds" cache = f'{current_app.config["CACHE_LOCATION"]}{filename.split(".")[0]}.png' if not os.path.exists(cache): root = f"{current_app.config['CLIENT_LOCATION']}res/" try: pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True) with image.Image(filename=f'{root}{filename}'.lower()) as img: img.compression = "no" img.save(filename=cache) except BE: return redirect(url_for('luclient.unknown')) return send_file(pathlib.Path(cache).resolve()) def get_cdclient(): """Connect to CDClient from file system Relative Path Args: None """ cdclient = getattr(g, '_cdclient', None) if cdclient is None: path = pathlib.Path(f"{current_app.config['CD_SQLITE_LOCATION']}cdclient.sqlite") if path.is_file(): cdclient = g._database = sqlite3.connect(f"{current_app.config['CD_SQLITE_LOCATION']}cdclient.sqlite") return cdclient path = pathlib.Path(f"{current_app.config['CD_SQLITE_LOCATION']}CDServer.sqlite") if path.is_file(): cdclient = g._database = sqlite3.connect(f"{current_app.config['CD_SQLITE_LOCATION']}CDServer.sqlite") return cdclient return cdclient def query_cdclient(query, args=(), one=False): """Run sql queries on CDClient Args: query (string) : SQL query args (list) : List of args to place in query one (bool) : Return only on result or all results """ cur = get_cdclient().execute(query, args) rv = cur.fetchall() cur.close() return (rv[0] if rv else None) if one else rv def translate_from_locale(trans_string): """Finds the string translation from locale.xml Args: trans_string (string) : ID to find translation """ if not trans_string: return "INVALID STRING" global locale locale_data = "" if not locale: locale_path = f"{current_app.config['CLIENT_LOCATION']}locale/locale.xml" with open(locale_path, 'r') as file: locale_data = file.read() locale_xml = ET.XML(locale_data) for item in locale_xml.findall('.//phrase'): translation = "" for translation_item in item.findall('.//translation'): if translation_item.attrib["locale"] == "en_US": translation = translation_item.text locale[item.attrib['id']] = translation if trans_string in locale: return locale[trans_string] else: return trans_string def get_lot_name(lot_id): if not lot_id: return "Missing" name = translate_from_locale(f'Objects_{lot_id}_name') if name == f'Objects_{lot_id}_name': intermed = query_cdclient( 'select * from Objects where id = ?', [lot_id], one=True ) if intermed: name = intermed[7] if (intermed[7] != "None" and intermed[7] != "" and intermed[7] is None) else intermed[1] return name def register_luclient_jinja_helpers(app): @app.template_filter('get_zone_name') def get_zone_name(zone_id): if not zone_id: return "Missing" return translate_from_locale(f'ZoneTable_{zone_id}_DisplayDescription') @app.template_filter('get_skill_desc') def get_skill_desc(skill_id): return translate_from_locale( f'SkillBehavior_{skill_id}_descriptionUI' ).replace( "%(DamageCombo)", "Damage Combo: " ).replace( "%(AltCombo)", "
Skeleton Combo: " ).replace( "%(Description)", "
" ).replace( "%(ChargeUp)", "
Charge-up: " ) @app.template_filter('parse_lzid') def parse_lzid(lzid): if not lzid: return [1000, 1000, 1000] return[ (int(lzid) & ((1 << 16) - 1)), ((int(lzid) >> 16) & ((1 << 16) - 1)), ((int(lzid) >> 32) & ((1 << 30) - 1)) ] @app.template_filter('parse_other_player_id') def parse_other_player_id(other_player_id): char_id = (int(other_player_id) & 0xFFFFFFFF) character = CharacterInfo.query.filter(CharacterInfo.id == char_id).first() if character: return[character.id, character.name] else: return None @app.template_filter('get_lot_name') def jinja_get_lot_name(lot_id): return get_lot_name(lot_id) @app.template_filter('get_lot_rarity') def get_lot_rarity(lot_id): if not lot_id: return "Missing" render_component_id = query_cdclient( 'select component_id from ComponentsRegistry where component_type = 11 and id = ?', [lot_id], one=True ) if render_component_id: render_component_id = render_component_id[0] rarity = query_cdclient( 'select rarity from ItemComponent where id = ?', [render_component_id], one=True ) if rarity: rarity = rarity[0] return rarity @app.template_filter('get_lot_desc') def get_lot_desc(lot_id): if not lot_id: return "Missing" desc = translate_from_locale(f'Objects_{lot_id}_description') if desc == f'Objects_{lot_id}_description': desc = query_cdclient( 'select description from Objects where id = ?', [lot_id], one=True ) if desc in ("", None): desc = None else: desc = desc[0] if desc in ("", None): desc = None if desc: desc = desc.replace('"', "“") return desc @app.template_filter('get_item_set') def check_if_in_set(lot_id): if not lot_id: return None item_set = query_cdclient( 'select * from ItemSets where itemIDs like ? or itemIDs like ? or itemIDs like ?', [f'{lot_id}%', f'%, {lot_id}%', f'%,{lot_id}%'], one=True ) if item_set in ("", None): return None else: return item_set @app.template_filter('get_lot_stats') def get_lot_stats(lot_id): if not lot_id: return None stats = query_cdclient( 'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\ SELECT skillID FROM ObjectSkills WHERE objectTemplate=?\ )', [lot_id] ) return consolidate_stats(stats) @app.template_filter('get_set_stats') def get_set_stats(lot_id): if not lot_id: return "Missing" stats = query_cdclient( 'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\ SELECT skillID FROM ItemSetSkills WHERE SkillSetID=?\ )', [lot_id] ) return consolidate_stats(stats) @app.template_filter('query_cdclient') def jinja_query_cdclient(query, items): print(query, items) return query_cdclient( query, items, one=True )[0] @app.template_filter('lu_translate') def lu_translate(to_translate): return translate_from_locale(to_translate) def consolidate_stats(stats): if len(stats) > 1: consolidated_stats = {"im": 0, "life": 0, "armor": 0, "skill": []} for stat in stats: if stat[0]: consolidated_stats["im"] += stat[0] if stat[1]: consolidated_stats["life"] += stat[1] if stat[2]: consolidated_stats["armor"] += stat[2] if stat[3]: consolidated_stats["skill"].append([stat[3], stat[4]]) stats = consolidated_stats elif len(stats) == 1: stats = { "im": stats[0][0] if stats[0][0] else 0, "life": stats[0][1] if stats[0][1] else 0, "armor": stats[0][2] if stats[0][2] else 0, "skill": [[stats[0][3], stats[0][4]]] if stats[0][3] else None, } else: stats = None return stats