Difference between revisions of "Ark Workshop Scripts for Linux"
Line 1: | Line 1: | ||
+ | * Updated 2020/7/7. Fix for file ids > 2147483647 | ||
* Updated 2020/6/9. Some Linux mods are placed in /downloads/ instead of /content/ | * Updated 2020/6/9. Some Linux mods are placed in /downloads/ instead of /content/ | ||
Line 4: | Line 5: | ||
<source lang="python">import clr | <source lang="python">import clr | ||
− | + | ||
from System.IO import Directory, File, Path, SearchOption | from System.IO import Directory, File, Path, SearchOption | ||
from System import Environment, PlatformID, String, Exception | from System import Environment, PlatformID, String, Exception | ||
from System.Text.RegularExpressions import Regex, RegexOptions, Match | from System.Text.RegularExpressions import Regex, RegexOptions, Match | ||
− | + | ||
extractedcount=0 | extractedcount=0 | ||
totalfilecount=0 | totalfilecount=0 | ||
lastfileprogress=0 | lastfileprogress=0 | ||
− | + | ||
######################################## | ######################################## | ||
# https://github.com/TheCherry/ark-server-manager # | # https://github.com/TheCherry/ark-server-manager # | ||
Line 19: | Line 20: | ||
import zlib | import zlib | ||
import sys | import sys | ||
− | + | ||
def str_to_l(st): | def str_to_l(st): | ||
return struct.unpack('q', st)[0] | return struct.unpack('q', st)[0] | ||
− | + | ||
def z_unpack(src, dst): | def z_unpack(src, dst): | ||
global extractedcount, totalfilecount, lastfileprogress | global extractedcount, totalfilecount, lastfileprogress | ||
Line 51: | Line 52: | ||
lastfileprogress=progress | lastfileprogress=progress | ||
ThisTaskStep.UpdateProgress(progress) | ThisTaskStep.UpdateProgress(progress) | ||
− | + | ||
####################################################################### | ####################################################################### | ||
# https://github.com/barrycarey/Ark_Mod_Downloader/blob/master/Ark_Mod_Downloader.py # | # https://github.com/barrycarey/Ark_Mod_Downloader/blob/master/Ark_Mod_Downloader.py # | ||
Line 62: | Line 63: | ||
temp_mod_path = os.path.join(ThisService.RootDirectory, "ShooterGame/Content/Mods") | temp_mod_path = os.path.join(ThisService.RootDirectory, "ShooterGame/Content/Mods") | ||
meta_data = OrderedDict([]) | meta_data = OrderedDict([]) | ||
− | + | ||
def parse_base_info(modid): | def parse_base_info(modid): | ||
− | + | ||
Script.WriteToConsole("[+] Collecting Mod Details From mod.info") | Script.WriteToConsole("[+] Collecting Mod Details From mod.info") | ||
− | + | ||
mod_info = os.path.join(temp_mod_path, modid, "mod.info") | mod_info = os.path.join(temp_mod_path, modid, "mod.info") | ||
− | + | ||
if not os.path.isfile(mod_info): | if not os.path.isfile(mod_info): | ||
Script.WriteToConsole("[x] Failed to locate mod.info. Cannot Continue. Aborting") | Script.WriteToConsole("[x] Failed to locate mod.info. Cannot Continue. Aborting") | ||
return False | return False | ||
− | + | ||
with open(mod_info, "rb") as f: | with open(mod_info, "rb") as f: | ||
read_ue4_string(f) | read_ue4_string(f) | ||
map_count = struct.unpack('i', f.read(4))[0] | map_count = struct.unpack('i', f.read(4))[0] | ||
− | + | ||
for i in range(map_count): | for i in range(map_count): | ||
cur_map = read_ue4_string(f) | cur_map = read_ue4_string(f) | ||
if cur_map: | if cur_map: | ||
map_names.append(cur_map) | map_names.append(cur_map) | ||
− | + | ||
return True | return True | ||
− | + | ||
def parse_meta_data(modid): | def parse_meta_data(modid): | ||
""" | """ | ||
− | + | Parse the modmeta.info files and extract the key value pairs need to for the .mod file. | |
− | + | How To Parse modmeta.info: | |
− | + | 1. Read 4 bytes to tell how many key value pairs are in the file | |
− | + | 2. Read next 4 bytes tell us how many bytes to read ahead to get the key | |
− | + | 3. Read ahead by the number of bytes retrieved from step 2 | |
− | + | 4. Read next 4 bytes to tell how many bytes to read ahead to get value | |
− | + | 5. Read ahead by the number of bytes retrieved from step 4 | |
− | + | 6. Start at step 2 again | |
− | + | :return: Dict | |
− | + | """ | |
− | + | ||
print("[+] Collecting Mod Meta Data From modmeta.info") | print("[+] Collecting Mod Meta Data From modmeta.info") | ||
print("[+] Located The Following Meta Data:") | print("[+] Located The Following Meta Data:") | ||
− | + | ||
mod_meta = os.path.join(temp_mod_path, modid, "modmeta.info") | mod_meta = os.path.join(temp_mod_path, modid, "modmeta.info") | ||
if not os.path.isfile(mod_meta): | if not os.path.isfile(mod_meta): | ||
Script.WriteToConsole("[x] Failed To Locate modmeta.info. Cannot continue without it. Aborting") | Script.WriteToConsole("[x] Failed To Locate modmeta.info. Cannot continue without it. Aborting") | ||
return False | return False | ||
− | + | ||
with open(mod_meta, "rb") as f: | with open(mod_meta, "rb") as f: | ||
− | + | ||
total_pairs = struct.unpack('i', f.read(4))[0] | total_pairs = struct.unpack('i', f.read(4))[0] | ||
− | + | ||
for i in range(total_pairs): | for i in range(total_pairs): | ||
− | + | ||
key, value = "", "" | key, value = "", "" | ||
− | + | ||
key_bytes = struct.unpack('i', f.read(4))[0] | key_bytes = struct.unpack('i', f.read(4))[0] | ||
key_flag = False | key_flag = False | ||
Line 118: | Line 119: | ||
key_flag = True | key_flag = True | ||
key_bytes -= 1 | key_bytes -= 1 | ||
− | + | ||
if not key_flag and key_bytes > 0: | if not key_flag and key_bytes > 0: | ||
− | + | ||
raw = f.read(key_bytes) | raw = f.read(key_bytes) | ||
key = raw[:-1].decode() | key = raw[:-1].decode() | ||
− | + | ||
value_bytes = struct.unpack('i', f.read(4))[0] | value_bytes = struct.unpack('i', f.read(4))[0] | ||
value_flag = False | value_flag = False | ||
Line 129: | Line 130: | ||
value_flag = True | value_flag = True | ||
value_bytes -= 1 | value_bytes -= 1 | ||
− | + | ||
if not value_flag and value_bytes > 0: | if not value_flag and value_bytes > 0: | ||
raw = f.read(value_bytes) | raw = f.read(value_bytes) | ||
value = raw[:-1].decode() | value = raw[:-1].decode() | ||
− | + | ||
# TODO This is a potential issue if there is a key but no value | # TODO This is a potential issue if there is a key but no value | ||
if key and value: | if key and value: | ||
Script.WriteToConsole("[!] " + key + ":" + value) | Script.WriteToConsole("[!] " + key + ":" + value) | ||
meta_data[key] = value | meta_data[key] = value | ||
− | + | ||
return True | return True | ||
− | + | ||
def create_mod_file(modid): | def create_mod_file(modid): | ||
""" | """ | ||
− | + | Create the .mod file. | |
− | + | This code is an adaptation of the code from Ark Server Launcher. All credit goes to Face Wound on Steam | |
− | + | :return: | |
− | + | """ | |
if not parse_base_info(modid) or not parse_meta_data(modid): | if not parse_base_info(modid) or not parse_meta_data(modid): | ||
return False | return False | ||
− | + | ||
print("[+] Writing .mod File") | print("[+] Writing .mod File") | ||
with open(os.path.join(temp_mod_path, modid + ".mod"), "w+b") as f: | with open(os.path.join(temp_mod_path, modid + ".mod"), "w+b") as f: | ||
− | + | ||
modid = int(modid) | modid = int(modid) | ||
+ | if modid > 2147483647: | ||
+ | diff = modid-2147483647 | ||
+ | modid = -2147483647 + diff - 2 | ||
f.write(struct.pack('ixxxx', modid)) # Needs 4 pad bits | f.write(struct.pack('ixxxx', modid)) # Needs 4 pad bits | ||
write_ue4_string("ModName", f) | write_ue4_string("ModName", f) | ||
write_ue4_string("", f) | write_ue4_string("", f) | ||
− | + | ||
map_count = len(map_names) | map_count = len(map_names) | ||
f.write(struct.pack("i", map_count)) | f.write(struct.pack("i", map_count)) | ||
− | + | ||
for m in map_names: | for m in map_names: | ||
write_ue4_string(m, f) | write_ue4_string(m, f) | ||
− | + | ||
# Not sure of the reason for this | # Not sure of the reason for this | ||
num2 = 4280483635 | num2 = 4280483635 | ||
Line 169: | Line 173: | ||
num3 = 2 | num3 = 2 | ||
f.write(struct.pack('i', num3)) | f.write(struct.pack('i', num3)) | ||
− | + | ||
if "ModType" in meta_data: | if "ModType" in meta_data: | ||
mod_type = b'1' | mod_type = b'1' | ||
else: | else: | ||
mod_type = b'0' | mod_type = b'0' | ||
− | + | ||
# TODO The packing on this char might need to be changed | # TODO The packing on this char might need to be changed | ||
f.write(struct.pack('p', mod_type)) | f.write(struct.pack('p', mod_type)) | ||
meta_length = len(meta_data) | meta_length = len(meta_data) | ||
f.write(struct.pack('i', meta_length)) | f.write(struct.pack('i', meta_length)) | ||
− | + | ||
for k, v in meta_data.items(): | for k, v in meta_data.items(): | ||
write_ue4_string(k, f) | write_ue4_string(k, f) | ||
write_ue4_string(v, f) | write_ue4_string(v, f) | ||
− | + | ||
return True | return True | ||
− | + | ||
def read_ue4_string(file): | def read_ue4_string(file): | ||
count = struct.unpack('i', file.read(4))[0] | count = struct.unpack('i', file.read(4))[0] | ||
Line 192: | Line 196: | ||
flag = True | flag = True | ||
count -= 1 | count -= 1 | ||
− | + | ||
if flag or count <= 0: | if flag or count <= 0: | ||
return "" | return "" | ||
− | + | ||
return file.read(count)[:-1].decode() | return file.read(count)[:-1].decode() | ||
− | + | ||
def write_ue4_string(string_to_write, file): | def write_ue4_string(string_to_write, file): | ||
string_length = len(string_to_write) + 1 | string_length = len(string_to_write) + 1 | ||
Line 204: | Line 208: | ||
file.write(barray) | file.write(barray) | ||
file.write(struct.pack('p', b'0')) | file.write(struct.pack('p', b'0')) | ||
− | + | ||
########################################### | ########################################### | ||
########################################### | ########################################### | ||
Line 211: | Line 215: | ||
if not Directory.Exists(InstallPath) : | if not Directory.Exists(InstallPath) : | ||
InstallPath=InstallPath.Replace("/content/", "/downloads/") | InstallPath=InstallPath.Replace("/content/", "/downloads/") | ||
− | + | ||
# Always use Windows files. Linux files cause the game server to crash at startup. | # Always use Windows files. Linux files cause the game server to crash at startup. | ||
oseditor="WindowsNoEditor" | oseditor="WindowsNoEditor" | ||
noeditor=Path.Combine(InstallPath, oseditor ) | noeditor=Path.Combine(InstallPath, oseditor ) | ||
− | + | ||
# Extract and delete all .z files | # Extract and delete all .z files | ||
zfiles=Directory.GetFiles(noeditor, "*.z", SearchOption.AllDirectories); | zfiles=Directory.GetFiles(noeditor, "*.z", SearchOption.AllDirectories); | ||
Line 225: | Line 229: | ||
File.Delete(zfile) | File.Delete(zfile) | ||
File.Delete(zfile + ".uncompressed_size") | File.Delete(zfile + ".uncompressed_size") | ||
− | + | ||
# Move folder to correct location. Delete if it already exists. | # Move folder to correct location. Delete if it already exists. | ||
+ | # Define modid before FileId is altered so we write the correct id to inifile | ||
+ | modid = FileId | ||
+ | if FileId > 2147483647: | ||
+ | diff = FileId-2147483647 | ||
+ | FileId = -2147483647 + diff - 2 | ||
+ | |||
modfolder=Path.Combine(ThisService.RootDirectory, String.Format("ShooterGame/Content/Mods/{0}", FileId)) | modfolder=Path.Combine(ThisService.RootDirectory, String.Format("ShooterGame/Content/Mods/{0}", FileId)) | ||
if Directory.Exists(modfolder) : | if Directory.Exists(modfolder) : | ||
Directory.Delete(modfolder, True) | Directory.Delete(modfolder, True) | ||
Directory.Move(Path.Combine(InstallPath, oseditor), modfolder) | Directory.Move(Path.Combine(InstallPath, oseditor), modfolder) | ||
− | + | ||
# Update ini file | # Update ini file | ||
serveros = "WindowsServer" if Environment.OSVersion.Platform == PlatformID.Win32NT else "LinuxServer" | serveros = "WindowsServer" if Environment.OSVersion.Platform == PlatformID.Win32NT else "LinuxServer" | ||
Line 240: | Line 250: | ||
if match.Success : | if match.Success : | ||
activemods = match.Groups["ActiveMods"].Value | activemods = match.Groups["ActiveMods"].Value | ||
− | if String.IsNullOrEmpty(activemods) or activemods.IndexOf( | + | if String.IsNullOrEmpty(activemods) or activemods.IndexOf(modid.ToString()) == -1 : |
if activemods.Length > 0 : | if activemods.Length > 0 : | ||
activemods = activemods + "," | activemods = activemods + "," | ||
− | activemods = activemods + | + | activemods = activemods + modid.ToString() |
filecontents=filecontents.Replace(match.Groups["ActiveMods"].Value, activemods) | filecontents=filecontents.Replace(match.Groups["ActiveMods"].Value, activemods) | ||
else : | else : | ||
− | activemods = | + | activemods = modid.ToString() |
filecontents = filecontents.Substring(0, match.Groups["ActiveMods"].Index) + activemods + filecontents.Substring(match.Groups["ActiveMods"].Index) | filecontents = filecontents.Substring(0, match.Groups["ActiveMods"].Index) + activemods + filecontents.Substring(match.Groups["ActiveMods"].Index) | ||
File.WriteAllText(inifile, filecontents) | File.WriteAllText(inifile, filecontents) | ||
− | + | ||
#Create .mod | #Create .mod | ||
parse_base_info(FileId.ToString()) | parse_base_info(FileId.ToString()) | ||
parse_meta_data(FileId.ToString()) | parse_meta_data(FileId.ToString()) | ||
create_mod_file(FileId.ToString()) | create_mod_file(FileId.ToString()) | ||
− | + | ||
# Delete folder | # Delete folder | ||
if Directory.Exists(InstallPath) : | if Directory.Exists(InstallPath) : | ||
Directory.Delete(InstallPath, True)</source> | Directory.Delete(InstallPath, True)</source> |
Revision as of 16:31, 7 July 2020
- Updated 2020/7/7. Fix for file ids > 2147483647
- Updated 2020/6/9. Some Linux mods are placed in /downloads/ instead of /content/
import clr from System.IO import Directory, File, Path, SearchOption from System import Environment, PlatformID, String, Exception from System.Text.RegularExpressions import Regex, RegexOptions, Match extractedcount=0 totalfilecount=0 lastfileprogress=0 ######################################## # https://github.com/TheCherry/ark-server-manager # ######################################## import struct import zlib import sys def str_to_l(st): return struct.unpack('q', st)[0] def z_unpack(src, dst): global extractedcount, totalfilecount, lastfileprogress with open(src, 'rb') as f_src: with open(dst, 'wb') as f_dst: f_src.read(8) size1 = str_to_l(f_src.read(8)) f_src.read(8) size2 = str_to_l(f_src.read(8)) if(size1 == -1641380927): size1 = 131072L runs = (size2 + size1 - 1L) / size1 array = [] for i in range(runs): array.append(f_src.read(8)) f_src.read(8) for i in range(runs): to_read = array[i] compressed = f_src.read(str_to_l(to_read)) decompressed = zlib.decompress(compressed) f_dst.write(decompressed) Script.WriteToConsole("Extracted " + dst.Replace(ThisService.RootDirectory, "")) File.Delete(src) File.Delete(src + ".uncompressed_size") extractedcount=extractedcount+1 progress=round((float(extractedcount)/totalfilecount)*100,0) if progress > lastfileprogress + 4: lastfileprogress=progress ThisTaskStep.UpdateProgress(progress) ####################################################################### # https://github.com/barrycarey/Ark_Mod_Downloader/blob/master/Ark_Mod_Downloader.py # ####################################################################### import os import struct from collections import OrderedDict map_names = [] map_count=0 temp_mod_path = os.path.join(ThisService.RootDirectory, "ShooterGame/Content/Mods") meta_data = OrderedDict([]) def parse_base_info(modid): Script.WriteToConsole("[+] Collecting Mod Details From mod.info") mod_info = os.path.join(temp_mod_path, modid, "mod.info") if not os.path.isfile(mod_info): Script.WriteToConsole("[x] Failed to locate mod.info. Cannot Continue. Aborting") return False with open(mod_info, "rb") as f: read_ue4_string(f) map_count = struct.unpack('i', f.read(4))[0] for i in range(map_count): cur_map = read_ue4_string(f) if cur_map: map_names.append(cur_map) return True def parse_meta_data(modid): """ Parse the modmeta.info files and extract the key value pairs need to for the .mod file. How To Parse modmeta.info: 1. Read 4 bytes to tell how many key value pairs are in the file 2. Read next 4 bytes tell us how many bytes to read ahead to get the key 3. Read ahead by the number of bytes retrieved from step 2 4. Read next 4 bytes to tell how many bytes to read ahead to get value 5. Read ahead by the number of bytes retrieved from step 4 6. Start at step 2 again :return: Dict """ print("[+] Collecting Mod Meta Data From modmeta.info") print("[+] Located The Following Meta Data:") mod_meta = os.path.join(temp_mod_path, modid, "modmeta.info") if not os.path.isfile(mod_meta): Script.WriteToConsole("[x] Failed To Locate modmeta.info. Cannot continue without it. Aborting") return False with open(mod_meta, "rb") as f: total_pairs = struct.unpack('i', f.read(4))[0] for i in range(total_pairs): key, value = "", "" key_bytes = struct.unpack('i', f.read(4))[0] key_flag = False if key_bytes < 0: key_flag = True key_bytes -= 1 if not key_flag and key_bytes > 0: raw = f.read(key_bytes) key = raw[:-1].decode() value_bytes = struct.unpack('i', f.read(4))[0] value_flag = False if value_bytes < 0: value_flag = True value_bytes -= 1 if not value_flag and value_bytes > 0: raw = f.read(value_bytes) value = raw[:-1].decode() # TODO This is a potential issue if there is a key but no value if key and value: Script.WriteToConsole("[!] " + key + ":" + value) meta_data[key] = value return True def create_mod_file(modid): """ Create the .mod file. This code is an adaptation of the code from Ark Server Launcher. All credit goes to Face Wound on Steam :return: """ if not parse_base_info(modid) or not parse_meta_data(modid): return False print("[+] Writing .mod File") with open(os.path.join(temp_mod_path, modid + ".mod"), "w+b") as f: modid = int(modid) if modid > 2147483647: diff = modid-2147483647 modid = -2147483647 + diff - 2 f.write(struct.pack('ixxxx', modid)) # Needs 4 pad bits write_ue4_string("ModName", f) write_ue4_string("", f) map_count = len(map_names) f.write(struct.pack("i", map_count)) for m in map_names: write_ue4_string(m, f) # Not sure of the reason for this num2 = 4280483635 f.write(struct.pack('I', num2)) num3 = 2 f.write(struct.pack('i', num3)) if "ModType" in meta_data: mod_type = b'1' else: mod_type = b'0' # TODO The packing on this char might need to be changed f.write(struct.pack('p', mod_type)) meta_length = len(meta_data) f.write(struct.pack('i', meta_length)) for k, v in meta_data.items(): write_ue4_string(k, f) write_ue4_string(v, f) return True def read_ue4_string(file): count = struct.unpack('i', file.read(4))[0] flag = False if count < 0: flag = True count -= 1 if flag or count <= 0: return "" return file.read(count)[:-1].decode() def write_ue4_string(string_to_write, file): string_length = len(string_to_write) + 1 file.write(struct.pack('i', string_length)) barray = bytearray(string_to_write, "utf-8") file.write(barray) file.write(struct.pack('p', b'0')) ########################################### ########################################### ########################################### # If the content folder doesn't exist use downloads if not Directory.Exists(InstallPath) : InstallPath=InstallPath.Replace("/content/", "/downloads/") # Always use Windows files. Linux files cause the game server to crash at startup. oseditor="WindowsNoEditor" noeditor=Path.Combine(InstallPath, oseditor ) # Extract and delete all .z files zfiles=Directory.GetFiles(noeditor, "*.z", SearchOption.AllDirectories); totalfilecount=zfiles.Count for zfile in zfiles: file=Path.Combine(Path.GetDirectoryName(zfile), Path.GetFileNameWithoutExtension(zfile)) z_unpack(zfile, file) Script.WriteToConsole("Extracted " + file) File.Delete(zfile) File.Delete(zfile + ".uncompressed_size") # Move folder to correct location. Delete if it already exists. # Define modid before FileId is altered so we write the correct id to inifile modid = FileId if FileId > 2147483647: diff = FileId-2147483647 FileId = -2147483647 + diff - 2 modfolder=Path.Combine(ThisService.RootDirectory, String.Format("ShooterGame/Content/Mods/{0}", FileId)) if Directory.Exists(modfolder) : Directory.Delete(modfolder, True) Directory.Move(Path.Combine(InstallPath, oseditor), modfolder) # Update ini file serveros = "WindowsServer" if Environment.OSVersion.Platform == PlatformID.Win32NT else "LinuxServer" inifile = Path.Combine(ThisService.RootDirectory, String.Format("ShooterGame/Saved/Config/{0}/GameUserSettings.ini", serveros)) pattern="ActiveMods[ \t]*=[ \t]*(?<ActiveMods>[0-9, \t]*)" filecontents = File.ReadAllText(inifile) match = Regex.Match(filecontents, pattern, RegexOptions.IgnoreCase) if match.Success : activemods = match.Groups["ActiveMods"].Value if String.IsNullOrEmpty(activemods) or activemods.IndexOf(modid.ToString()) == -1 : if activemods.Length > 0 : activemods = activemods + "," activemods = activemods + modid.ToString() filecontents=filecontents.Replace(match.Groups["ActiveMods"].Value, activemods) else : activemods = modid.ToString() filecontents = filecontents.Substring(0, match.Groups["ActiveMods"].Index) + activemods + filecontents.Substring(match.Groups["ActiveMods"].Index) File.WriteAllText(inifile, filecontents) #Create .mod parse_base_info(FileId.ToString()) parse_meta_data(FileId.ToString()) create_mod_file(FileId.ToString()) # Delete folder if Directory.Exists(InstallPath) : Directory.Delete(InstallPath, True)