This commit is contained in:
Amazed 2021-12-23 00:25:10 +01:00
commit 0287dca42f
10 changed files with 947 additions and 0 deletions

125
lib/constants.py Normal file
View File

@ -0,0 +1,125 @@
class AppGUID:
Release = b"\x10\x5e\x62\xa7\x96\x1a\xd2\x11\x9a\xfc\x00\x60\x08\x45\xe5\x71"
Beta = b'j\xfbF/Le\xedJ\x8c\x0b\x06@\x14f\x9b\xfd'
class MapID:
ID = {
"MvM_L1": 19,
"MvM_L2": 20,
"MvM_L3": 21,
"RvR_L1": 22,
"RvR_L2": 23,
"RvR_L3": 24,
"3W_L1": 25,
"3W_L2": 26,
"3W_L3": 27,
"MvM_LW1": 28,
"RvR_LW1": 29,
"3W_LW1": 30,
"MvM_L6": 31,
}
@staticmethod
def get_id(name):
if name in MapID.ID:
return MapID.ID[name]
else:
return 0xFF
@staticmethod
def get_name(map_id):
for _map_name in MapID.ID:
_map_id = MapID.ID[_map_name]
if _map_id == map_id:
return _map_name
return None
class Teams:
@staticmethod
def get_name_by_id(team_id):
for prop in Teams.__dict__:
if Teams.__dict__[prop] == team_id:
return prop
return "Unknown"
@staticmethod
def get_groupid_by_team_id(_id, teams):
print("get_groupid_by_team_id: %s %s" % (_id, teams))
if teams == Teams.MvK:
if _id == 1:
return 2
if _id == 2:
return 1
if teams == Teams.MvMvM or teams == Teams.MvM:
return 2
if teams == Teams.RvR:
return 3
if teams == Teams.MvR:
if _id == 1:
return 2
if _id == 2:
return 3
if teams == Teams.MvRvK:
if _id == 1:
return 2
if _id == 2:
return 3
if _id == 3:
return 1
if teams == Teams.RvK:
if _id == 1:
return 3
if _id == 2:
return 1
return 0
MvM = 0x00
MvMvM = 0x01
RvR = 0x02
MvR = 0x03
MvRvK = 0x04
MvK = 0x05
RvK = 0x06
TeamB = 0x07
TeamB = 0x08
TeamB = 0x0c
TeamB = 0x10
class GameTypes:
@staticmethod
def get_name_by_id(gametype_id):
for prop in GameTypes.__dict__:
if GameTypes.__dict__[prop] == gametype_id:
return prop
return "Unknown"
# 00: Team Deathmatch
# 01: Team Deathmatch with full base
# 02: Capture Smartie
# 03: Capture Smartie with full base
# 04: Base Build Deathmatch
# 05: Base Build and Capture the Smartie
# 06: Defend Base
# 07: Defend Base and Capture the Smartie
# 08: GTypeStone
# 09: GTypeWood
# 0a: crash to desktop
# 0b: crash to desktop
# 0c: GType(null)
# 0d: crash to desktop
# 0e: crash to desktop
# 0f: crash to desktop
# 10: crash to desktop
# aa: crash to desktop
# ff: crash to desktop
TeamDeathmatch = 0x00
TeamDeathmatchWithFullBase = 0x01
CaptureSmartie = 0x02
CaptureSmartieWithFullBase = 0x03
BaseBuildDeathmatch = 0x04
BaseBuildCaptureSmartie = 0x05
DefendBase = 0x06
DefendBaseCaptureSmartie = 0x07
GTypeStone = 0x08
GTypeWood = 0x09
Crash = 0x0a
GTypeNull = 0x0c

47
lib/enumquery.py Normal file
View File

@ -0,0 +1,47 @@
from lib.packet import Packet
import random
class EnumQuery:
LEAD = 0x00
COMMAND = 0x02
NO_APPLICATION_GUID = 0x02
HAS_APPLICATION_GUID = 0x01
def __init__(self):
self.Lead = EnumQuery.LEAD
self.Command = EnumQuery.COMMAND
self.Payload = random.getrandbits(16)
self.Type = None
self.ApplicationGUID = None
self.ApplicationPayload = None
@classmethod
def parse(cls, packet: Packet):
enumquery = cls()
packet.seek(0)
enumquery.Lead = packet.get_byte()
enumquery.Command = packet.get_byte()
enumquery.Payload = packet.get_short()
enumquery.Type = packet.get_byte()
if enumquery.Type == EnumQuery.HAS_APPLICATION_GUID:
enumquery.ApplicationGUID = packet.get_bytes(16)
enumquery.ApplicationPayload = packet.read()
elif enumquery.Type == EnumQuery.NO_APPLICATION_GUID:
enumquery.ApplicationPayload = packet.read()
return enumquery
def to_packet(self):
packet = Packet()
packet.put_byte(EnumQuery.LEAD)
packet.put_byte(EnumQuery.COMMAND)
packet.put_short(self.Payload)
if self.ApplicationGUID:
packet.put_byte(EnumQuery.HAS_APPLICATION_GUID)
packet.put_bytes(self.ApplicationGUID)
else:
packet.put_byte(EnumQuery.NO_APPLICATION_GUID)
if self.ApplicationPayload:
packet.put_bytes(self.ApplicationPayload)
return packet

117
lib/enumresponse.py Normal file
View File

@ -0,0 +1,117 @@
from lib.packet import Packet
class EnumResponse:
LEAD = 0x00
COMMAND = 0x03
def __init__(self):
self.Lead = EnumResponse.LEAD
self.Command = EnumResponse.COMMAND
self.Payload = b''
self.ReplyOffset = b''
self.ReplySize = b''
self.ApplicationDescSize = b''
self.ApplicationDescFlags = b''
self.MaxPlayers = b''
self.CurrentPlayers = b''
self.SessionNameOffset = b''
self.SessionNameSize = b''
self.PasswordOffset = b''
self.PasswordSize = b''
self.ReservedDataOffset = b''
self.ReservedDataSize = b''
self.ApplicationReservedDataOffset = b''
self.ApplicationReservedDataSize = b''
self.ApplicationInstanceGUID = b''
self.ApplicationGUID = b''
self.SessionName = ""
self.Password = b''
self.ReservedData = b''
self.ApplicationReservedData = b''
self.ApplicationData = b''
@classmethod
def parse(cls, packet: Packet):
enumresponse = cls()
packet.seek(0)
enumresponse.Lead = packet.get_byte()
if enumresponse.Lead != 0xcf:
enumresponse.Command = packet.get_byte()
enumresponse.Payload = packet.get_short()
enumresponse.ReplyOffset = packet.get_ulong()
enumresponse.ReplySize = packet.get_ulong()
else:
packet.get_3bytes_int() # cause we were supposed to read a full int
enumresponse.ApplicationDescSize = packet.get_ulong()
enumresponse.ApplicationDescFlags = packet.get_ulong()
enumresponse.MaxPlayers = packet.get_ulong()
enumresponse.CurrentPlayers = packet.get_ulong()
enumresponse.SessionNameOffset = packet.get_ulong()
enumresponse.SessionNameSize = packet.get_ulong()
enumresponse.PasswordOffset = packet.get_ulong()
enumresponse.PasswordSize = packet.get_ulong()
enumresponse.ReservedDataOffset = packet.get_ulong()
enumresponse.ReservedDataSize = packet.get_ulong()
enumresponse.ApplicationReservedDataOffset = packet.get_ulong()
enumresponse.ApplicationReservedDataSize = packet.get_ulong()
enumresponse.ApplicationInstanceGUID = packet.get_bytes(16)
enumresponse.ApplicationGUID = packet.get_bytes(16)
enumresponse.SessionName = packet.getvalue()[enumresponse.SessionNameOffset+4:enumresponse.SessionNameOffset+enumresponse.SessionNameSize+1].decode("utf8").replace("\x00", "")
enumresponse.ApplicationReservedData = packet.getvalue()[enumresponse.ApplicationReservedDataOffset+4:enumresponse.ApplicationReservedDataOffset+enumresponse.ApplicationReservedDataSize+4]
return enumresponse
def to_packet(self):
varpos = 88
self.SessionNameSize = len(self.SessionName.encode("utf-16"))
self.SessionNameOffset = varpos if self.SessionName else 0
varpos += self.SessionNameSize if self.SessionName else 0
self.PasswordSize = len(self.Password)
self.PasswordOffset = varpos if self.Password else 0
varpos += self.PasswordSize if self.Password else 0
self.ReservedDataSize = len(self.ReservedData)
self.ReservedDataOffset = varpos if self.ReservedData else 0
varpos += self.ReservedDataSize if self.ReservedData else 0
self.ApplicationReservedDataSize = len(self.ApplicationReservedData)
self.ApplicationReservedDataOffset = varpos if self.ApplicationReservedData else 0
varpos += self.ApplicationReservedDataSize if self.ApplicationReservedData else 0
self.ReplySize = len(self.ApplicationData)
self.ReplyOffset = varpos if self.ApplicationData else 0
varpos += self.ReplySize if self.ApplicationData else 0
packet = Packet()
packet.put_byte(EnumResponse.LEAD)
packet.put_byte(EnumResponse.COMMAND)
packet.put_short(self.Payload)
packet.put_ulong(self.ReplyOffset)
packet.put_ulong(self.ReplySize)
packet.put_ulong(self.ApplicationDescSize)
packet.put_ulong(self.ApplicationDescFlags)
packet.put_ulong(self.MaxPlayers)
packet.put_ulong(self.CurrentPlayers)
packet.put_ulong(self.SessionNameOffset)
packet.put_ulong(self.SessionNameSize)
packet.put_ulong(self.PasswordOffset)
packet.put_ulong(self.PasswordSize)
packet.put_ulong(self.ReservedDataOffset)
packet.put_ulong(self.ReservedDataSize)
packet.put_ulong(self.ApplicationReservedDataOffset)
packet.put_ulong(self.ApplicationReservedDataSize)
packet.put_bytes(self.ApplicationInstanceGUID)
packet.put_bytes(self.ApplicationGUID)
if self.SessionName:
packet.put_bytes((self.SessionName+'\x00').encode("utf-16-le"))
if self.Password:
packet.put_bytes(self.Password)
if self.ReservedData:
packet.put_bytes(self.ReservedData)
if self.ApplicationReservedData:
packet.put_bytes(self.ApplicationReservedData)
if self.ApplicationData:
packet.put_bytes(self.ApplicationData)
return packet

View File

@ -0,0 +1,89 @@
from lib.enumresponse import EnumResponse, Packet
from lib.constants import Teams, GameTypes, MapID
class GiantsEnumResponseParser:
def __init__(self):
super().__init__()
self.server_name = None
self.max_players = None
self.current_players = None
self.game_type = None
self.teams = None
self.version = None
self.points_per_capture = None
self.points_per_kill = None
self.base_level = None
self.allow_joiners = None
self.damage_teammates = None
self.lock_teams = None
self.is_dedicated = None
self.vimps_disabled = None
self.weap_bits = None
self.high_bandwidth = None
self.no_voting = None
self.smartie_difficulty = None
self.vimp_difficulty = None
self.capture_prevent_count = None
self.current_cap_prev_time = None
self.map_id = None
self.map_checksum = None
self.map_name = None
self.build = None
self.revision = None
@classmethod
def parse(cls, enumresp: EnumResponse):
g_enumresponse = cls()
g_enumresponse.max_players = enumresp.MaxPlayers
g_enumresponse.current_players = enumresp.CurrentPlayers
g_enumresponse.server_name = enumresp.SessionName
game_data = enumresp.ApplicationReservedData
# print(game_data)
p = Packet(game_data)
g_enumresponse.map_id = p.get_byte()
g_enumresponse.game_type = p.get_byte()
g_enumresponse.teams = p.get_byte()
g_enumresponse.base_level = p.get_byte()
g_enumresponse.version = p.get_short()
bitmask = p.get_short()
b = bin(bitmask)[2:].zfill(16)
g_enumresponse.allow_joiners = b[0]
g_enumresponse.damage_teammates = b[1]
g_enumresponse.lock_teams = b[2]
g_enumresponse.is_dedicated = b[3]
g_enumresponse.vimps_disabled = b[4]
g_enumresponse.weap_bits = b[5]
g_enumresponse.high_bandwidth = b[6]
g_enumresponse.no_voting = b[7]
g_enumresponse.smartie_difficulty = b[8:12]
g_enumresponse.vimp_difficulty = b[12:16]
g_enumresponse.points_per_capture = p.get_short()
g_enumresponse.points_per_kill = p.get_short()
g_enumresponse.capture_prevent_count = p.get_short()
g_enumresponse.current_cap_prev_time = p.get_short()
g_enumresponse.map_checksum = p.get_ulong()
g_enumresponse.map_name = p.get_string(32)
if g_enumresponse.version >= 1498:
g_enumresponse.build = p.get_byte()
g_enumresponse.revision = p.get_byte()
if map_name := MapID.get_name(g_enumresponse.map_id):
g_enumresponse.map_name = map_name
return g_enumresponse
def __str__(self):
return "[%s] Players:%d/%d, Map: %s, Type: %s, Teams: %s, Version: %d, Build: %s, AllowJoiners: %s" % \
(self.server_name, self.current_players, self.max_players, self.map_name, GameTypes.get_name_by_id(self.game_type), Teams.get_name_by_id(self.teams), self.version, self.build, self.allow_joiners)

70
lib/packet.py Normal file
View File

@ -0,0 +1,70 @@
import io
import struct
class Packet(io.BytesIO):
def put_byte(self, val) -> None:
self.write(struct.pack('<B', val % 256))
def get_byte(self) -> int:
return struct.unpack('<B', self.read(1))[0]
def put_bytes(self, b) -> None:
for byte in b:
self.write(struct.pack('<B', byte))
def get_bytes(self, num) -> bytes:
return bytes(struct.unpack('<'+str(num)+'B', self.read(num)))
def put_short(self, val) -> None:
self.write(struct.pack('<H', val))
def get_short(self) -> int:
return struct.unpack('<H', self.read(2))[0]
def put_long(self, val) -> None:
self.write(struct.pack('<l', val))
def put_ulong(self, val) -> None:
self.write(struct.pack('<L', val))
def get_long(self) -> int:
return struct.unpack('<l', self.read(4))[0]
def get_ulong(self) -> int:
return struct.unpack('<L', self.read(4))[0]
def put_longlong(self, val) -> None:
self.write(struct.pack('<Q', val))
def get_longlong(self) -> int:
return struct.unpack('<Q', self.read(8))[0]
def put_float(self, val) -> None:
self.write(struct.pack('<f', val))
def get_float(self) -> float:
return struct.unpack('<f', self.read(4))[0]
def put_string(self, val) -> None:
self.write(val + b'\x00')
def put_string_size(self, val, size) -> None:
self.write(val.encode("utf8") + b"\x00" * (size - len(val)))
def get_string_until_none(self) -> str:
s = ""
c = self.get_byte()
while c != 0x00:
s += chr(c)
c = self.get_byte()
return s
def get_string(self, size) -> str:
return self.get_bytes(size).decode("utf8").replace("\x00", "")
def get_3bytes_int(self) -> int:
return struct.unpack("<L", self.read(3)+b"\x00")[0]
def put_3bytes_int(self, val) -> None:
self.write(struct.pack("<L", val)[:-1])

272
main.py Normal file
View File

@ -0,0 +1,272 @@
import base64
import datetime
import os.path
import pathlib
import socket
import zlib
import sys
from PySide6.QtCore import QThread, QTimer, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from lib.enumresponse import EnumResponse
from lib.giantsenumresponseparser import GiantsEnumResponseParser
from lib.packet import Packet
from maps_gui import Ui_MainWindow
import requests
from lib.enumquery import EnumQuery
from lib.constants import AppGUID
import yaml
installed_maps = {}
API_BASE_URL = "https://gckmaps.hipstercat.fr"
SLEEP_TIME = 20 * 1000
CONFIG = {}
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
ip = s.getsockname()[0]
except:
ip = '127.0.0.1'
finally:
s.close()
return ip
LOCAL_IP = get_ip()
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.timer = QTimer(self)
self.timer.setInterval(SLEEP_TIME)
self.timer.timeout.connect(self.timer_timeout)
self.background_task = MapsBackground()
self.background_task.finished.connect(self.background_task_finished)
self.background_task.msg.connect(self.message_received)
if CONFIG["automatic_download"]:
self.ui.automaticMapDownloadCheckBox.setChecked(True)
self.timer.start()
self.log("Background task started")
else:
self.ui.automaticMapDownloadCheckBox.setChecked(False)
self.ui.automaticMapDownloadCheckBox.stateChanged.connect(self.checkboxchanged)
self.ui.pushButton.clicked.connect(self.btnclicked)
def timer_timeout(self):
self.run_background_task()
def run_background_task(self):
if not self.background_task.isRunning():
self.ui.pushButton.setEnabled(False)
self.background_task.start()
def background_task_finished(self):
self.ui.pushButton.setEnabled(True)
def checkboxchanged(self):
checked = self.ui.automaticMapDownloadCheckBox.isChecked()
if checked:
self.timer.start()
self.log("Background task started")
else:
self.timer.stop()
self.log("Background task stopped")
CONFIG["automatic_download"] = checked
save_configuration()
def btnclicked(self):
self.run_background_task()
def message_received(self, m):
self.log(m)
def log(self, message: str) -> None:
full_msg = f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}"
self.ui.logEdit.appendPlainText(full_msg)
print(full_msg)
def get_maps_dir() -> pathlib.Path:
return get_giants_directory() / "Bin" / "Worlds"
def load_installed_maps() -> None:
maps_dir = get_maps_dir()
def map_crc(map_file):
prev = 0
with open(map_file, "rb") as f:
b = f.read()
prev = zlib.crc32(b, prev)
return prev & 0xffffffff
installed_maps.clear()
for map_path in os.listdir(maps_dir):
if map_path.endswith(".cache") or map_path.endswith(".gck"):
map_path_s = pathlib.Path(maps_dir / map_path)
crc = map_crc(map_path_s)
installed_maps[crc] = map_path_s
def config_file_path() -> pathlib.Path:
datadir = get_datadir() / "giants-maps-downloader"
config_file = datadir / "config.yml"
try:
datadir.mkdir(parents=True)
except FileExistsError:
pass
return config_file
def read_configuration():
config_file = config_file_path()
global CONFIG
if not os.path.exists(config_file):
# create it
giants_exe_path, _filename = QFileDialog.getOpenFileName(None, caption="Open Giants.exe", filter="Giants.exe")
giants_dir = os.path.dirname(giants_exe_path)
CONFIG = {"giants_directory": giants_dir, "automatic_download": True}
save_configuration()
print(f"Config file: {config_file}")
with open(config_file, "r") as fp:
CONFIG = yaml.load(fp, Loader=yaml.Loader)
def save_configuration():
config_file = config_file_path()
with open(config_file, "w") as fp:
yaml.dump(CONFIG, fp)
print(f"Wrote configuration file to {config_file}")
def get_giants_directory() -> pathlib.Path:
return pathlib.Path(CONFIG["giants_directory"])
def get_datadir() -> pathlib.Path:
home = pathlib.Path.home()
if sys.platform == "win32":
return home / "AppData/Roaming"
elif sys.platform == "linux":
return home / ".local/share"
elif sys.platform == "darwin":
return home / "Library/Application Support"
class MapsBackground(QThread):
msg = Signal(str)
def run(self) -> None:
self.log("Background task starting")
try:
self.server_up_or_dl_missing_map(LOCAL_IP, 19711, True, timeout=2)
except:
self.log("No local server found")
self.all_servers_download()
def log(self, message: str):
self.msg.emit(message)
def server_up_or_dl_missing_map(self, server_ip: str, server_port: int, is_local: bool, timeout=5) -> None:
enum_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
enum_socket.setblocking(True)
enum_socket.settimeout(timeout)
eq = EnumQuery()
eq.Type = EnumQuery.HAS_APPLICATION_GUID
eq.ApplicationGUID = AppGUID.Release
p = eq.to_packet().getvalue()
enum_socket.sendto(p, (server_ip, server_port))
response = enum_socket.recv(1024)
enumresp = EnumResponse.parse(Packet(response))
giantsenumresp = GiantsEnumResponseParser.parse(enumresp)
if is_local:
self.upload_map(installed_maps[giantsenumresp.map_checksum])
elif giantsenumresp.map_checksum not in installed_maps:
self.log(f"Map {giantsenumresp.map_name} not found locally, downloading it")
self.download_map(giantsenumresp.map_checksum)
def download_map(self, crc: int) -> None:
self.log(f"Downloading map {API_BASE_URL}/map?crc={crc}")
try:
req = requests.get(f"{API_BASE_URL}/map?crc={crc}")
except Exception as e:
self.log(f"Error while fetching map info: {e}")
return
if req.status_code != 200:
self.log(f"Could not download map from server, got status_code {req.status_code}")
return
j = req.json()
blob_location = j["blob_location"]
final_map_file = get_maps_dir() / j["name"]
with requests.get(blob_location, stream=True) as stream:
with open(final_map_file, 'wb') as f:
for chunk in stream.iter_content(chunk_size=8192):
f.write(chunk)
load_installed_maps()
self.log(f"Download succesful: {final_map_file}")
def upload_map(self, map_path: pathlib.Path) -> None:
with open(map_path, "rb") as fp:
content_bytes = fp.read()
def crc(map_content_bytes):
prev = zlib.crc32(map_content_bytes, 0)
return prev & 0xffffffff
map_crc = crc(content_bytes)
# check if map is known by server
self.log(f"Trying {API_BASE_URL}/map?crc={map_crc}")
try:
req = requests.get(f"{API_BASE_URL}/map?crc={map_crc}")
except Exception as e:
self.log(f"Error while fetching map info: {e}")
return
if req.status_code == 200:
self.log(f"API already knows this map, got response {req.status_code}")
return
map_name = os.path.basename(map_path)
self.log(f"Local server found hosting map {map_name}, uploading it")
b64_data = base64.b64encode(content_bytes).decode("utf8")
map_data = {"name": map_name, "b64_data": b64_data}
try:
req = requests.post(f"{API_BASE_URL}/maps", json=map_data)
req.raise_for_status()
except Exception as e:
self.log(f"There was an error while uploading map: {e}")
return
self.log("Upload successful")
def all_servers_download(self) -> None:
all_servers = requests.get("https://giants.azurewebsites.net/api/Servers").json()
for server in all_servers:
try:
self.server_up_or_dl_missing_map(server["hostIpAddress"], server["port"], False)
except socket.timeout:
self.log(f"Could not connect to {server['hostIpAddress']}")
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(True)
read_configuration()
load_installed_maps()
window = MainWindow()
window.show()
sys.exit(app.exec())

40
main.spec Normal file
View File

@ -0,0 +1,40 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['PySide6.QtQml'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )

96
maps_gui.py Normal file
View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'maps_gui.ui'
##
## Created by: Qt User Interface Compiler version 6.2.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QFormLayout, QGridLayout,
QLabel, QMainWindow, QMenuBar, QPlainTextEdit,
QPushButton, QSizePolicy, QStatusBar, QWidget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(449, 230)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.centralwidget.setEnabled(True)
self.gridLayout_2 = QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.formLayout = QFormLayout()
self.formLayout.setObjectName(u"formLayout")
self.automaticMapDownloadLabel = QLabel(self.centralwidget)
self.automaticMapDownloadLabel.setObjectName(u"automaticMapDownloadLabel")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.automaticMapDownloadLabel)
self.automaticMapDownloadCheckBox = QCheckBox(self.centralwidget)
self.automaticMapDownloadCheckBox.setObjectName(u"automaticMapDownloadCheckBox")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.automaticMapDownloadCheckBox)
self.gridLayout.addLayout(self.formLayout, 0, 0, 1, 1)
self.pushButton = QPushButton(self.centralwidget)
self.pushButton.setObjectName(u"pushButton")
self.pushButton.setEnabled(True)
self.gridLayout.addWidget(self.pushButton, 0, 1, 1, 1)
self.logEdit = QPlainTextEdit(self.centralwidget)
self.logEdit.setObjectName(u"logEdit")
self.logEdit.setReadOnly(True)
self.gridLayout.addWidget(self.logEdit, 1, 0, 1, 2)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 449, 21))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"GMD - Giants Maps Downloader", None))
self.automaticMapDownloadLabel.setText(QCoreApplication.translate("MainWindow", u"Automatic map download", None))
#if QT_CONFIG(statustip)
self.automaticMapDownloadCheckBox.setStatusTip(QCoreApplication.translate("MainWindow", u"Every few minutes, all servers will be queried and missing maps will be downloaded", None))
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(statustip)
self.pushButton.setStatusTip(QCoreApplication.translate("MainWindow", u"Immediately query servers now, download missing maps and upload hosted ones", None))
#endif // QT_CONFIG(statustip)
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"Download currently missing maps\n"
"hosted by servers", None))
#if QT_CONFIG(statustip)
self.logEdit.setStatusTip(QCoreApplication.translate("MainWindow", u"Log messages", None))
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(statustip)
self.statusbar.setStatusTip(QCoreApplication.translate("MainWindow", u"By Amazed#0001", None))
#endif // QT_CONFIG(statustip)
# retranslateUi

87
maps_gui.ui Normal file
View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>230</height>
</rect>
</property>
<property name="windowTitle">
<string>GMD - Giants Maps Downloader</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="automaticMapDownloadLabel">
<property name="text">
<string>Automatic map download</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="automaticMapDownloadCheckBox">
<property name="statusTip">
<string>Every few minutes, all servers will be queried and missing maps will be downloaded</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="statusTip">
<string>Immediately query servers now, download missing maps and upload hosted ones</string>
</property>
<property name="text">
<string>Download currently missing maps
hosted by servers</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QPlainTextEdit" name="logEdit">
<property name="statusTip">
<string>Log messages</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="statusTip">
<string>By Amazed#0001</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
PySide6
requests
pyyaml
pyinstaller