diff --git a/.gitlab/issue_templates/Feature request.md b/.gitlab/issue_templates/Feature request.md
index 3df7f30c22cb8608f7e4dd1d093fd8e1aa94d408..ceb5b1b838ee376b6119f444c3bc644d59a02e11 100644
--- a/.gitlab/issue_templates/Feature request.md	
+++ b/.gitlab/issue_templates/Feature request.md	
@@ -5,7 +5,7 @@
 *What would it do?*
 
 # How would it enhance the experience?
-*Describe the  motivation for the change: how would the experience be better with this change?*
+*Describe the motivation for the change: how would the experience be better with this change?*
 
 # Checks to do
 *File or features to check would not be impacted by the changes*
diff --git a/.gitlab/merge_request_templates/Bug fix.md b/.gitlab/merge_request_templates/Bug fix.md
index ad335a008a015449de73ec230afcf4298a921cac..f993c2fc65664f8847e52fff71bc894bab5a59c3 100644
--- a/.gitlab/merge_request_templates/Bug fix.md	
+++ b/.gitlab/merge_request_templates/Bug fix.md	
@@ -5,7 +5,7 @@
 *Give a quick summary of the changes you have done and why*
 
 # Checks
-I have verifed the following:
+I have verified the following:
 - [ ] Fixes the issue
 - [ ] Does not introduces an obvious bug or vulnerability
 - [ ] Code has been duely tested
diff --git a/README.md b/README.md
index 5ea21ae009da3e629eca30044671352b89a0b020..d3979f838260f9d833739314ad9be176d58deb1a 100644
--- a/README.md
+++ b/README.md
@@ -14,13 +14,3 @@ Grey Hack is a great game offering an internal programming language, GreyScript,
 A few things are missing to fully automate the actions you can do in the game, so we decided to create the tools we needed.
 
 In the repo you can find a very simple preprocessor which allows us to include scripts into others, a JSON parser originally made for MiniScript we adapted to GreyScript, and some other tools to automate actions in the game.
-
-## How does the preprocessor work?
-
-It is very simple, it detects the `"#import file.gs”` pattern and includes the file at this place (it consists of `#import` with a file, between quotes, and must be at the begining of a line).
-
-We decided that the “importable” files should have a `.gs` extension, and the result of the preprocessor is stored as `file_pp.src`. Then it is compiled and the source file is removed. The executable file is created with no extension.
-
-The preprocessor is also able to remove everything located after a line starting with `“#UnitTests“` to be able to add tests to our files without including them into final executable files.
-
-Warning: the preprocessor does not remove the Grey Hack limitation of 80000 characters for a compilable script.
diff --git a/documentation/resources.txt b/documentation/resources.txt
deleted file mode 100644
index 992a3903c67c90d2f7291d4f236e01998db0af61..0000000000000000000000000000000000000000
--- a/documentation/resources.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-You are here : https://usine.solution-libre.fr/qtg/grey-hack
-QTG discord : https://discord.gg/VaQUGv7
-
-GreyHack discord : https://discord.gg/greyhack
-GHTools discord : TBA
-
-Source code of all vanilla commands : https://steamcommunity.com/sharedfiles/filedetails/?id=1906204725
-Useful snippets for beginners : https://steamcommunity.com/sharedfiles/filedetails/?id=2027217968
-
-GHTools CodeDocs : https://codedocs.ghtools.xyz/
-GHtools challenge : https://ghtools.xyz/unauth
-
-man program : https://pastebin.com/9Kd5Qtuh
-english manual : https://justpaste.it/38j0e
-french manual : https://justpaste.it/edit/29222846/ff00ea51daa2faae
\ No newline at end of file
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..615adc9436f9555a7b1fb83c551374ee27e1bf0c
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,33 @@
+## Install steps  
+
+0) Go into the folder in which you want to create the qtg repo  
+1) Copy tools/archive.src into CodeEditor, then as archive.src  
+2) Execute:  
+build archive.src /bin  
+- archive is now available on the system  
+You can delete archive.src  
+3) Copy archives/qtg.tar.part1 and .part2 into Notepad, then as REPO_NAME.tar.part1 and .part2
+- archive checks for the .tar extention!  
+4) Execute:  
+archive REPO_NAME  
+- The folder REPO_NAME is now created  
+You can now delete the tar files
+5) Copy the libs folder into all folders to make sure the .gs scripts are able to find them  
+6) Execute:  
+build preprocess.src /bin  
+preprocess is now available on the system to process .gs files  
+7) Execute:  
+cd tools  
+preprocess archive.src  
+This way, you'll have an archive tool which use the libs from the archive :P  
+
+
+## How does the preprocessor work?
+
+It is very simple, it detects the `"#import file.gs”` pattern and includes the file at this place (it consists of `#import` with a file, between quotes, and must be at the begining of a line).
+
+We decided that the “importable” files should have a `.gs` extension, and the result of the preprocessor is stored as `file_pp.src`. Then it is compiled and the source file is removed. The executable file is created with no extension.
+
+The preprocessor is also able to remove everything located after a line starting with `“#UnitTests“` to be able to add tests to our files without including them into final executable files.
+
+Warning: the preprocessor does not remove the Grey Hack limitation of 80000 characters for a compilable script.
\ No newline at end of file
diff --git a/scripts/archives/README.md b/scripts/archives/README.md
deleted file mode 100644
index c78bd8733ba55a69807d861b2ba8602eb06b64e7..0000000000000000000000000000000000000000
--- a/scripts/archives/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-0) Go into the folder in which you want to create the qtg repo  
-1) Copy tools/archive.src into CodeEditor, then as archive.src  
-2) Execute:   
-build archive.src /bin  
-- archive is now available on the system  
-You can delete archive.src  
-3) Copy archives/qtg.txt into Notepad, then as REPO_NAME.tar  
-- archive checks for the .tar extention!  
-4) Execute:  
-archive REPO_NAME  
-- The folder REPO_NAME is now created  
-You can delete REPO_NAME.tar  
-5) Go into the repo, then copy archives/libs.txt as (REPO_NAME/)libs.tar  
-6) Execute:  
-cd REPO_NAME  
-archive libs  
-- The libs are now available on the repo  
-You can delete libs.tar  
-7) Copy the libs folder into all folders to make sure the .gs scripts are able to find them  
-8) Execute:  
-build preprocess.src /bin  
-preprocess is now available on the system to process .gs files  
-9) Execute:  
-cd tools  
-preprocess archive.src  
-This way, you'll have an archive tool which use the libs from the archive :P  
\ No newline at end of file
diff --git a/scripts/archives/qtg.txt b/scripts/archives/qtg.tar.part1
similarity index 90%
rename from scripts/archives/qtg.txt
rename to scripts/archives/qtg.tar.part1
index c0242a02bb17288dea10e75e1f1f668ed3f424bc..dd3e2c5f4a22781b5a17c1b1007490ec9c76cd21 100644
--- a/scripts/archives/qtg.txt
+++ b/scripts/archives/qtg.tar.part1
@@ -563,79 +563,94 @@ print("Connected to " + essid + ", password : "+password)
 file.delete()
 
 "#archive tools/archive.src"
-"#import includes/utils.inc.src"
-"#import includes/file.inc.src"
+"#import libs/utils.inc.src"
+"#import libs/file.inc.src"
 
 // Used by passing the path of the (txt) archive, then the path of all the files to add
 // More information at https://usine.solution-libre.fr/qtg/grey-hack/-/issues/6
 
 "#ifbuild"
-imports = {}
-
-// The methods here are "good enough" to work in those cases
-// For example, the ParamLength normally discards flag-style args
-// But I don't want to give you all my tricks... yet, at least ;)
-imports.utils = {}
-imports.utils.ParamLength = function(params)
-	if params == null then return 0
-	return params.len
-end function
-imports.utils.GetParam = function(params, index)
-	if index < 0 or index >= imports.utils.ParamLength(params) then return null
-	return params[index]
-end function
-imports.utils.HasFlag = function(params, short, long)
-	if imports.utils.ParamLength(params) == 0 then return null
-	param = params[0]
-	return param == "-"+short or param == "-"+long
-end function
-// Dynamically getting the name of the command is left as an exercise to the reader :)
-imports.utils.GetProgName = function()
-	return program_path.split("/")[-1]
-end function
-imports.utils.Print = function(reason)
-	print(imports.utils.GetProgName()+": "+reason)
-end function
-
-imports.file = {}
-imports.file.NEW_LINE = char(10)
-// Disclaimer : brand new method created for this script, may not handle special cases 
-imports.file.AbsolutePath = function(path)
-	if path[0] != "/" then path = get_shell.host_computer.current_path + "/" + path
-	return path
-end function
-// Should return [] for unavailable files (binary or no read perms)
-imports.file.ReadAllLines = function(file)
-	if typeof(file) == "string" then
-		file = get_shell.host_computer.File(file)
-	end if
-	if not imports.file.FileAccess(file,"r") then return []
-	if file.is_binary then return []
-	return file.content.split(imports.file.NEW_LINE)
-end function
-imports.file.FileAccess = function(file, perm)
-	if not file then return false
-	if file.has_permission(perm) then return true
-	imports.utils.Print("Error: can't use ('"+perm+"') contents of '"+file.path+"'")
-	return false
-end function
-imports.file.save = function(content, path, name)
-	data = name.split("/")
-	if data.len > 1 then
-		for index in range(0, data.len-2)
-			name = data[index]
-			comp.create_folder(path, name)
-			path = path +"/"+ name
+if not globals.hasIndex("imports") then
+	imports = {}
+
+	// The methods here are "good enough" to work in those cases
+	// For example, the ParamLength normally discards flag-style args
+	// But I don't want to give you all my tricks... yet, at least ;)
+	imports.utils = {}
+	imports.utils.ParamLength = function(params)
+		if params == null then return 0
+		return params.len
+	end function
+	imports.utils.GetParam = function(params, index)
+		if index < 0 or index >= imports.utils.ParamLength(params) then return null
+		return params[index]
+	end function
+	imports.utils.HasFlag = function(params, short, long)
+		if imports.utils.ParamLength(params) == 0 then return null
+		param = params[0]
+		return param == "-"+short or param == "-"+long
+	end function
+	// Dynamically getting the name of the command is left as an exercise to the reader :)
+	imports.utils.GetProgName = function()
+		return program_path.split("/")[-1]
+	end function
+	imports.utils.Print = function(reason)
+		print(imports.utils.GetProgName()+": "+reason)
+	end function
+	imports.utils.PrintList = function(list, separator)
+		result = ""
+		nextLine = false
+		for item in list
+			if nextLine then result = result + separator else nextLine = true
+			result = result + item
 		end for
-	end if
-	name = data[data.len-1]
-	comp.touch(path, name)
-	comp.File(path+"/"+name).set_content(content)
-end function
+		return result
+	end function
+
+	imports.file = {}
+	imports.file.NEW_LINE = char(10)
+	// Disclaimer : brand new method created for this script, may not handle special cases 
+	imports.file.AbsolutePath = function(path)
+		if path[0] != "/" then path = get_shell.host_computer.current_path + "/" + path
+		return path
+	end function
+	// Should return [] for unavailable files (binary or no read perms)
+	imports.file.ReadAllLines = function(file)
+		if typeof(file) == "string" then
+			file = get_shell.host_computer.File(file)
+		end if
+		if not imports.file.FileAccess(file,"r") then return []
+		if file.is_binary then return []
+		return file.content.split(imports.file.NEW_LINE)
+	end function
+	imports.file.FileAccess = function(file, perm)
+		if not file then return false
+		if file.has_permission(perm) then return true
+		imports.utils.Print("Error: can't use ('"+perm+"') contents of '"+file.path+"'")
+		return false
+	end function
+	imports.file.save = function(content, path, name)
+		data = name.split("/")
+		if data.len > 1 then
+			for index in range(0, data.len-2)
+				name = data[index]
+				comp.create_folder(path, name)
+				path = path +"/"+ name
+			end for
+		end if
+		name = data[data.len-1]
+		comp.touch(path, name)
+		comp.File(path+"/"+name).set_content(content)
+	end function
+end if
 "#endif"
 
-// To avoid bruning our eyes with escaping
+// To avoid burning our eyes with escaping
 QUOTE = """"
+// To identify archived data
+TAG = "#archive "
+// To signify an compressed archive
+EXT = ".tar"
 
 // Metadatas are encoded as 'unused' strings
 // Extracted straight from my preprocessor as that's too specific for an include IMHO
@@ -657,9 +672,6 @@ StringInstruction = function(str)
 	return str[1:str.len]
 end function
 
-// To identify archived data
-TAG = "#archive "
-
 ArchiveName = function(str)
 	str = StringInstruction(str)
 	if str == null then return null
@@ -673,30 +685,44 @@ length = imports.utils.ParamLength(params)
 if length == 0 or imports.utils.HasFlag(params, "h", "help") then exit("<b>Usage: "+imports.utils.GetProgName()+" [file to compress/decompress] [...file paths]</b>")
 comp = get_shell.host_computer
 
-mainPath = imports.file.AbsolutePath(imports.utils.GetParam(params, 0) + ".tar")
-mainFile = comp.File(mainPath)
-
+mainPath = imports.file.AbsolutePath(imports.utils.GetParam(params, 0))
 temp = mainPath.lastIndexOf("/")
 path = mainPath[:temp]
 name = mainPath[temp+1:]
 
-if mainFile == null then
+archives = []
+mainFile = comp.File(mainPath+EXT)
+if mainFile != null then
+	archives.push(mainFile)
+else
+	i = 1
+	while true
+		mainFile = comp.File(mainPath+EXT+".part"+i)
+		if mainFile == null then break
+		i=i+1
+		archives.push(mainFile)
+	end while
+end if
+
+if archives.len == 0 then
 	imports.utils.Print("Combining files")
 	
 	// Creating the file
-	comp.touch(path, name)
-	mainFile = comp.File(mainPath)
-	
-	// Flag to avoid a new line on the first line
-	nextLine = false
-	content = ""
-	
+	comp.touch(path, name+EXT)
+	mainFile = comp.File(mainPath+EXT)
+		
+	storage = []
+		
 	// Read all files in order and add them
 	for i in range(1,length-1)
 		path = imports.file.AbsolutePath(imports.utils.GetParam(params, i))
 		// Get the name of the file
 		name = path[path.lastIndexOf("/")+1:]
 		
+		data = []
+		// add "#archive filename.ext" then the file
+		data.push(QUOTE+TAG+name+QUOTE)
+			
 		lines = imports.file.ReadAllLines(path)
 		// Sanity checking the file is left as an exercise to the reader :) 
 		if lines.len == 0 then
@@ -704,61 +730,51 @@ if mainFile == null then
 			continue
 		end if
 		
-		if nextLine then
-			content = content + imports.file.NEW_LINE
-		else
-			nextLine = true
-		end if
-		
-		// add "#archive filename.ext" then the file
-		content = content + QUOTE+TAG+name+QUOTE
 		for line in lines
-			content = content + imports.file.NEW_LINE + line
+			data.push(line)
 		end for
+		
+		storage.push(imports.utils.PrintList(data,imports.file.NEW_LINE))
 	end for
-	
-	mainFile.set_content(content)
-	print("Archive is now available")
-else
-	print("Unarchiving")
-	
-	// Using the archive name as a new folder
-	if temp > 1 then name = name[:name.lastIndexOf(".")]
-	comp.create_folder(path, name)
-	path = path + "/" + name
-	
-	lines = imports.file.ReadAllLines(mainPath)
-	if ArchiveName(lines[0]) == null then exit("Not a valid archive file")
-	
+		
+	mainFile.set_content(imports.utils.PrintList(storage,imports.file.NEW_LINE))
+	exit("Archive is now available")
+end if
+
+// Using the archive name as a new folder
+comp.create_folder(path, name)
+path = path + "/" + name
+
+for archive in archives
+	print("Unarchiving "+archive.name)
+		
+	lines = imports.file.ReadAllLines(archive)
+	// Sanity check : archive tag as first line
+	if ArchiveName(lines[0]) == null then
+		print("Not a valid archive file")
+		continue
+	end if
+		
 	currentName = null
-	content = ""
-	nextLine = false
-	
+	content = []
+		
 	for line in lines
 		temp = ArchiveName(line)
-		// New file
+		// Switching to a new file
 		if temp != null then
-			if currentName != null then imports.file.save(content, path, currentName)
-			
+			if currentName != null then imports.file.save(imports.utils.PrintList(content,imports.file.NEW_LINE), path, currentName)
+				
 			currentName = temp
-			content = ""
-			nextLine = false
+			content = []
 			continue
 		end if
-		
-		if nextLine then
-			content = content + imports.file.NEW_LINE
-		else
-			nextLine = true
-		end if
-		
-		content = content + line
-		
+			
+		content.push(line)		
 	end for
-	
-	imports.file.save(content, path, currentName)
-	print("Unarchiving completed")
-end if
+		
+	imports.file.save(imports.utils.PrintList(content,imports.file.NEW_LINE), path, currentName)
+end for
+print("Unarchiving completed")
 
 "#archive tools/breakhash.src"
 "#import libs/utils.inc.src"
diff --git a/scripts/archives/libs.txt b/scripts/archives/qtg.tar.part2
similarity index 99%
rename from scripts/archives/libs.txt
rename to scripts/archives/qtg.tar.part2
index 109fec4719981d16beff649a247250ed7d4706b9..4e1791eb281b5708955cc8a42fc358d2248dba30 100644
--- a/scripts/archives/libs.txt
+++ b/scripts/archives/qtg.tar.part2
@@ -1,4 +1,4 @@
-"#archive file.inc.src"
+"#archive libs/file.inc.src"
 "#ifbuild"
 if not globals.hasIndex("imports") then imports = {}
 imports.temp = {}
@@ -293,7 +293,7 @@ imports.file.RemoveLib = function(includeData)
 	return imports.file.Delete(get_shell.host_computer.File(path))
 end function
 
-"#archive filereaper.inc.src"
+"#archive libs/filereaper.inc.src"
 "#import libs/utils.src"
 "#import libs/file.src"
 
@@ -519,7 +519,7 @@ imports.filereaper.exploitFile = function(pars, file, targetRoot=true)
 	end if
 end function
 
-"#archive passwords.inc.src"
+"#archive libs/passwords.inc.src"
 "#import libs/utils.inc.src"
 "#import libs/file.inc.src"
 
@@ -567,7 +567,7 @@ imports.passwords.AddUser = function(pass, line)
 	return true
 end function
 
-"#archive qtglogo.inc.src"
+"#archive libs/qtglogo.inc.src"
 "#import libs/ui.inc.src"
 
 printLogo = function()
@@ -638,7 +638,7 @@ printLogo = function()
     print(B(R(" ",17)+R("%",L-17-16)+R(" ",16)))
 end function
 
-"#archive remote.inc.src"
+"#archive libs/remote.inc.src"
 "#import libs/file.inc.src"
 
 "#ifbuild"
@@ -929,7 +929,7 @@ imports.remote.RandomName = function(serverComp, targetComp, destPath, name)
 	end while
 end function
 
-"#archive ui.inc.src"
+"#archive libs/ui.inc.src"
 "#import libs/utils.inc.src"
 "#import libs/file.inc.src"
 
@@ -1048,7 +1048,7 @@ imports.ui.Bold = function(text)
 	return imports.ui.BOLD+text+imports.ui.BOLD_END
 end function
 
-"#archive utils.inc.src"
+"#archive libs/utils.inc.src"
 // Only used for ONE method, choice left to the main script
 //"#import libs/dict.inc.src"
 //"#import libs/remote.inc.src"
@@ -1160,7 +1160,7 @@ imports.utils.FirstMapWrite = function(map, index, value)
 	if not map.hasIndex(index) then map[index] = value
 	return map[index]
 end function
-imports.utils.PrintList = function(list, separator=imports.file.NEW_LINE)
+imports.utils.PrintList = function(list, separator)
 	result = ""
 	nextLine = false
 	for item in list
@@ -1276,7 +1276,7 @@ imports.utils.SelfDestruct = function(prompt=true)
     get_shell.host_computer.File(program_path).delete
 end function
 
-"#archive versioning.inc.src"
+"#archive libs/versioning.inc.src"
 "#import libs/utils.inc.src"
 
 "#ifbuild"
@@ -1363,7 +1363,7 @@ imports.versioning.compare = function(verA, verB)
 	return result
 end function
 
-"#archive models/jsonparser.gs"
+"#archive libs/models/jsonparser.gs"
 // JSON (JavaScript Object Notation) is a common format for exchanging data
 // between different computers or programs.  See: https://json.org/
 // The data types in JSON are number, string, object, and array, which
@@ -1758,7 +1758,7 @@ if locals == globals then
 
 end if
 
-"#archive models/utils.gs"
+"#archive libs/models/utils.gs"
 // Check mx
 metaxploit = include_lib("/lib/metaxploit.so")
 if not metaxploit then
@@ -1821,7 +1821,7 @@ for file in libDirectory.get_files
 	print (format_columns(lib.lib_name + " " + lib.version))
 end for
 
-"#archive models/vuln.gs"
+"#archive libs/models/vuln.gs"
 //Defining classes
 //Vulnerability
 Vulnerability = {}
diff --git a/scripts/defense/wanssh.src b/scripts/defense/wanssh.src
new file mode 100644
index 0000000000000000000000000000000000000000..89af8eaaf2449e367580bf9bed7cdd932dca9c9b
--- /dev/null
+++ b/scripts/defense/wanssh.src
@@ -0,0 +1,19 @@
+// Must be renamed as "ssh" and installed on machines not meant to have LAN redirection capability
+// In particular, public proxies for the "ssh rental" missions
+if params.len < 2 or params.len > 3 then exit(command_info("ssh_usage"))
+
+// Only modification : blocking LAN adresses
+if active_user != "root" and is_lan_ip(params[1]) then exit("Access refused: this server is intended as a WAN proxy")
+
+credentials = params[0].split("@")
+user = credentials[0]
+password = credentials[1]
+
+port = 22
+// params is a list of strings, so you have to convert it to integer, which is what connect_service accepts
+if params.len == 3 then port = params[2].to_int
+if typeof(port) != "number" then exit("Invalid port: " + port)
+print("Connecting...")
+
+shell = get_shell.connect_service(params[1], port, user, password, "ssh")
+if shell then shell.start_terminal
\ No newline at end of file
diff --git a/scripts/tools/archive.src b/scripts/tools/archive.src
index eb8ed8b0b3109961434c75307b489c60b5d7903e..c740aac3e8a8681a0073fc658733b9cd0a4a3705 100644
--- a/scripts/tools/archive.src
+++ b/scripts/tools/archive.src
@@ -1,76 +1,91 @@
-"#import includes/utils.inc.src"
-"#import includes/file.inc.src"
+"#import libs/utils.inc.src"
+"#import libs/file.inc.src"
 
 // Used by passing the path of the (txt) archive, then the path of all the files to add
 // More information at https://usine.solution-libre.fr/qtg/grey-hack/-/issues/6
 
 "#ifbuild"
-imports = {}
+if not globals.hasIndex("imports") then
+	imports = {}
 
-// The methods here are "good enough" to work in those cases
-// For example, the ParamLength normally discards flag-style args
-// But I don't want to give you all my tricks... yet, at least ;)
-imports.utils = {}
-imports.utils.ParamLength = function(params)
-	if params == null then return 0
-	return params.len
-end function
-imports.utils.GetParam = function(params, index)
-	if index < 0 or index >= imports.utils.ParamLength(params) then return null
-	return params[index]
-end function
-imports.utils.HasFlag = function(params, short, long)
-	if imports.utils.ParamLength(params) == 0 then return null
-	param = params[0]
-	return param == "-"+short or param == "-"+long
-end function
-// Dynamically getting the name of the command is left as an exercise to the reader :)
-imports.utils.GetProgName = function()
-	return program_path.split("/")[-1]
-end function
-imports.utils.Print = function(reason)
-	print(imports.utils.GetProgName()+": "+reason)
-end function
-
-imports.file = {}
-imports.file.NEW_LINE = char(10)
-// Disclaimer : brand new method created for this script, may not handle special cases 
-imports.file.AbsolutePath = function(path)
-	if path[0] != "/" then path = get_shell.host_computer.current_path + "/" + path
-	return path
-end function
-// Should return [] for unavailable files (binary or no read perms)
-imports.file.ReadAllLines = function(file)
-	if typeof(file) == "string" then
-		file = get_shell.host_computer.File(file)
-	end if
-	if not imports.file.FileAccess(file,"r") then return []
-	if file.is_binary then return []
-	return file.content.split(imports.file.NEW_LINE)
-end function
-imports.file.FileAccess = function(file, perm)
-	if not file then return false
-	if file.has_permission(perm) then return true
-	imports.utils.Print("Error: can't use ('"+perm+"') contents of '"+file.path+"'")
-	return false
-end function
-imports.file.save = function(content, path, name)
-	data = name.split("/")
-	if data.len > 1 then
-		for index in range(0, data.len-2)
-			name = data[index]
-			comp.create_folder(path, name)
-			path = path +"/"+ name
+	// The methods here are "good enough" to work in those cases
+	// For example, the ParamLength normally discards flag-style args
+	// But I don't want to give you all my tricks... yet, at least ;)
+	imports.utils = {}
+	imports.utils.ParamLength = function(params)
+		if params == null then return 0
+		return params.len
+	end function
+	imports.utils.GetParam = function(params, index)
+		if index < 0 or index >= imports.utils.ParamLength(params) then return null
+		return params[index]
+	end function
+	imports.utils.HasFlag = function(params, short, long)
+		if imports.utils.ParamLength(params) == 0 then return null
+		param = params[0]
+		return param == "-"+short or param == "-"+long
+	end function
+	// Dynamically getting the name of the command is left as an exercise to the reader :)
+	imports.utils.GetProgName = function()
+		return program_path.split("/")[-1]
+	end function
+	imports.utils.Print = function(reason)
+		print(imports.utils.GetProgName()+": "+reason)
+	end function
+	imports.utils.PrintList = function(list, separator)
+		result = ""
+		nextLine = false
+		for item in list
+			if nextLine then result = result + separator else nextLine = true
+			result = result + item
 		end for
-	end if
-	name = data[data.len-1]
-	comp.touch(path, name)
-	comp.File(path+"/"+name).set_content(content)
-end function
+		return result
+	end function
+
+	imports.file = {}
+	imports.file.NEW_LINE = char(10)
+	// Disclaimer : brand new method created for this script, may not handle special cases 
+	imports.file.AbsolutePath = function(path)
+		if path[0] != "/" then path = get_shell.host_computer.current_path + "/" + path
+		return path
+	end function
+	// Should return [] for unavailable files (binary or no read perms)
+	imports.file.ReadAllLines = function(file)
+		if typeof(file) == "string" then
+			file = get_shell.host_computer.File(file)
+		end if
+		if not imports.file.FileAccess(file,"r") then return []
+		if file.is_binary then return []
+		return file.content.split(imports.file.NEW_LINE)
+	end function
+	imports.file.FileAccess = function(file, perm)
+		if not file then return false
+		if file.has_permission(perm) then return true
+		imports.utils.Print("Error: can't use ('"+perm+"') contents of '"+file.path+"'")
+		return false
+	end function
+	imports.file.save = function(content, path, name)
+		data = name.split("/")
+		if data.len > 1 then
+			for index in range(0, data.len-2)
+				name = data[index]
+				comp.create_folder(path, name)
+				path = path +"/"+ name
+			end for
+		end if
+		name = data[data.len-1]
+		comp.touch(path, name)
+		comp.File(path+"/"+name).set_content(content)
+	end function
+end if
 "#endif"
 
-// To avoid bruning our eyes with escaping
+// To avoid burning our eyes with escaping
 QUOTE = """"
+// To identify archived data
+TAG = "#archive "
+// To signify an compressed archive
+EXT = ".tar"
 
 // Metadatas are encoded as 'unused' strings
 // Extracted straight from my preprocessor as that's too specific for an include IMHO
@@ -92,9 +107,6 @@ StringInstruction = function(str)
 	return str[1:str.len]
 end function
 
-// To identify archived data
-TAG = "#archive "
-
 ArchiveName = function(str)
 	str = StringInstruction(str)
 	if str == null then return null
@@ -108,30 +120,44 @@ length = imports.utils.ParamLength(params)
 if length == 0 or imports.utils.HasFlag(params, "h", "help") then exit("<b>Usage: "+imports.utils.GetProgName()+" [file to compress/decompress] [...file paths]</b>")
 comp = get_shell.host_computer
 
-mainPath = imports.file.AbsolutePath(imports.utils.GetParam(params, 0) + ".tar")
-mainFile = comp.File(mainPath)
-
+mainPath = imports.file.AbsolutePath(imports.utils.GetParam(params, 0))
 temp = mainPath.lastIndexOf("/")
 path = mainPath[:temp]
 name = mainPath[temp+1:]
 
-if mainFile == null then
+archives = []
+mainFile = comp.File(mainPath+EXT)
+if mainFile != null then
+	archives.push(mainFile)
+else
+	i = 1
+	while true
+		mainFile = comp.File(mainPath+EXT+".part"+i)
+		if mainFile == null then break
+		i=i+1
+		archives.push(mainFile)
+	end while
+end if
+
+if archives.len == 0 then
 	imports.utils.Print("Combining files")
 	
 	// Creating the file
-	comp.touch(path, name)
-	mainFile = comp.File(mainPath)
-	
-	// Flag to avoid a new line on the first line
-	nextLine = false
-	content = ""
-	
+	comp.touch(path, name+EXT)
+	mainFile = comp.File(mainPath+EXT)
+		
+	storage = []
+		
 	// Read all files in order and add them
 	for i in range(1,length-1)
 		path = imports.file.AbsolutePath(imports.utils.GetParam(params, i))
 		// Get the name of the file
 		name = path[path.lastIndexOf("/")+1:]
 		
+		data = []
+		// add "#archive filename.ext" then the file
+		data.push(QUOTE+TAG+name+QUOTE)
+			
 		lines = imports.file.ReadAllLines(path)
 		// Sanity checking the file is left as an exercise to the reader :) 
 		if lines.len == 0 then
@@ -139,58 +165,48 @@ if mainFile == null then
 			continue
 		end if
 		
-		if nextLine then
-			content = content + imports.file.NEW_LINE
-		else
-			nextLine = true
-		end if
-		
-		// add "#archive filename.ext" then the file
-		content = content + QUOTE+TAG+name+QUOTE
 		for line in lines
-			content = content + imports.file.NEW_LINE + line
+			data.push(line)
 		end for
+		
+		storage.push(imports.utils.PrintList(data,imports.file.NEW_LINE))
 	end for
-	
-	mainFile.set_content(content)
-	print("Archive is now available")
-else
-	print("Unarchiving")
-	
-	// Using the archive name as a new folder
-	if temp > 1 then name = name[:name.lastIndexOf(".")]
-	comp.create_folder(path, name)
-	path = path + "/" + name
-	
-	lines = imports.file.ReadAllLines(mainPath)
-	if ArchiveName(lines[0]) == null then exit("Not a valid archive file")
-	
+		
+	mainFile.set_content(imports.utils.PrintList(storage,imports.file.NEW_LINE))
+	exit("Archive is now available")
+end if
+
+// Using the archive name as a new folder
+comp.create_folder(path, name)
+path = path + "/" + name
+
+for archive in archives
+	print("Unarchiving "+archive.name)
+		
+	lines = imports.file.ReadAllLines(archive)
+	// Sanity check : archive tag as first line
+	if ArchiveName(lines[0]) == null then
+		print("Not a valid archive file")
+		continue
+	end if
+		
 	currentName = null
-	content = ""
-	nextLine = false
-	
+	content = []
+		
 	for line in lines
 		temp = ArchiveName(line)
-		// New file
+		// Switching to a new file
 		if temp != null then
-			if currentName != null then imports.file.save(content, path, currentName)
-			
+			if currentName != null then imports.file.save(imports.utils.PrintList(content,imports.file.NEW_LINE), path, currentName)
+				
 			currentName = temp
-			content = ""
-			nextLine = false
+			content = []
 			continue
 		end if
-		
-		if nextLine then
-			content = content + imports.file.NEW_LINE
-		else
-			nextLine = true
-		end if
-		
-		content = content + line
-		
+			
+		content.push(line)		
 	end for
-	
-	imports.file.save(content, path, currentName)
-	print("Unarchiving completed")
-end if
+		
+	imports.file.save(imports.utils.PrintList(content,imports.file.NEW_LINE), path, currentName)
+end for
+print("Unarchiving completed")
\ No newline at end of file