Difference between revisions of "Ark Workshop Scripts for Linux"

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

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)
Retrieved from "https://help.tcadmin.com/index.php?title=Ark_Workshop_Scripts_for_Linux&oldid=2267"