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