Difference between revisions of "Ark Workshop Scripts for Linux"
From TCAdmin 2.0 Documentation
(4 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | * Updated 2020/08/24. Fix for file ids > 2147483647 after game was fixed | |
− | + | * Updated 2020/7/7. Fix for file ids > 2147483647 | |
− | + | * Updated 2020/6/9. Some Linux mods are placed in /downloads/ instead of /content/ | |
− | + | ||
− | + | === After Workshop Install === | |
− | + | ||
− | + | <source lang="python">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)) | file=Path.Combine(Path.GetDirectoryName(zfile), Path.GetFileNameWithoutExtension(zfile)) | ||
z_unpack(zfile, file) | z_unpack(zfile, file) | ||
Line 210: | Line 230: | ||
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. | |
− | + | # 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}", modid)) | |
− | + | 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() | |
− | #Create .mod | + | filecontents=filecontents.Replace(match.Groups["ActiveMods"].Value, activemods) |
− | parse_base_info(FileId.ToString()) | + | 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(modid.ToString()) | |
+ | parse_meta_data(modid.ToString()) | ||
+ | create_mod_file(modid.ToString()) | ||
+ | |||
+ | # Delete folder | ||
+ | if Directory.Exists(InstallPath) : | ||
+ | Directory.Delete(InstallPath, True)</source> | ||
+ | |||
+ | === After Workshop Update === | ||
+ | This script is the same as the install script except it does not update the .ini so it keeps the mod order. | ||
+ | |||
+ | <source lang="python">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}", modid)) | ||
+ | if Directory.Exists(modfolder) : | ||
+ | Directory.Delete(modfolder, True) | ||
+ | Directory.Move(Path.Combine(InstallPath, oseditor), modfolder) | ||
+ | |||
+ | #Create .mod | ||
+ | parse_base_info(modid.ToString()) | ||
+ | parse_meta_data(modid.ToString()) | ||
+ | create_mod_file(modid.ToString()) | ||
+ | |||
+ | # Delete folder | ||
+ | if Directory.Exists(InstallPath) : | ||
+ | Directory.Delete(InstallPath, True)</source> |
Latest revision as of 12:14, 24 August 2020
- Updated 2020/08/24. Fix for file ids > 2147483647 after game was fixed
- Updated 2020/7/7. Fix for file ids > 2147483647
- Updated 2020/6/9. Some Linux mods are placed in /downloads/ instead of /content/
After Workshop Install
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}", modid)) 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(modid.ToString()) parse_meta_data(modid.ToString()) create_mod_file(modid.ToString()) # Delete folder if Directory.Exists(InstallPath) : Directory.Delete(InstallPath, True)
After Workshop Update
This script is the same as the install script except it does not update the .ini so it keeps the mod order.
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}", modid)) if Directory.Exists(modfolder) : Directory.Delete(modfolder, True) Directory.Move(Path.Combine(InstallPath, oseditor), modfolder) #Create .mod parse_base_info(modid.ToString()) parse_meta_data(modid.ToString()) create_mod_file(modid.ToString()) # Delete folder if Directory.Exists(InstallPath) : Directory.Delete(InstallPath, True)