From 26b758a7235c3a3635e0e411c968c0d4b0e4ee31 Mon Sep 17 00:00:00 2001 From: Joshua Murphy <2576504+murphyjt@users.noreply.github.com> Date: Mon, 19 May 2025 00:14:53 -0400 Subject: [PATCH] Replace shell backticks with Process.new for better escaping This avoids issues with filenames containing spaces and improves reliability when passing arguments to external tools. --- cia-unix.cr | 69 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/cia-unix.cr b/cia-unix.cr index 8dbc673..a1bdab8 100644 --- a/cia-unix.cr +++ b/cia-unix.cr @@ -1,7 +1,7 @@ require "colorize" -log : File = File.new "cia-unix.log", "w" -log.puts Time.utc.to_s +LOG = File.new "cia-unix.log", "w" +LOG.puts Time.utc.to_s # dependencies check tools = ["./ctrtool", "./ctrdecrypt", "./makerom", "seeddb.bin"] @@ -9,13 +9,13 @@ tools.each do |tool| case tool when "./ctrtool", "./ctrdecrypt", "./makerom" if !File.exists? %x[which #{tool}].chomp - log.delete if File.exists? "cia-unix.log" + LOG.delete if File.exists? "cia-unix.log" download_dep abort "#{tool.lchop("./").colorize.mode(:bold)} not found. Make sure it's located in the #{"same directory".colorize.mode(:underline)}" if !File.exists? tool end when "seeddb.bin" if !File.exists? tool - log.delete if File.exists? "cia-unix.log" + LOG.delete if File.exists? "cia-unix.log" download_dep abort "#{tool.colorize.mode(:bold)} not found. Make sure it's located in the #{"same directory".colorize.mode(:underline)}" if !File.exists? tool end @@ -31,10 +31,19 @@ end # roms presence check if Dir["*.cia"].size.zero? && Dir["*.3ds"].size.zero? - log.delete if File.exists? "cia-unix.log" + LOG.delete if File.exists? "cia-unix.log" abort "No #{"CIA".colorize.mode(:bold)}/#{"3DS".colorize.mode(:bold)} roms were found." end +def run_tool(name : String, args : Array(String)) : String + process = Process.new("./#{name}", args: args, output: Process::Redirect::Pipe) + content = process.output.gets_to_end + LOG.puts content + exit_code = process.wait.exit_code + raise "#{name} failed with exit code #{exit_code}" if exit_code != 0 + content +end + def check_decrypt(name : String, ext : String) if File.exists? "#{name}-decrypted.#{ext}" puts "Decryption completed\n".colorize.mode(:underline) @@ -43,11 +52,11 @@ def check_decrypt(name : String, ext : String) end end -def gen_args(name : String, part_count : Int32) : String - args : String = "" +def gen_args(name : String, part_count : Int32) : Array(String) + args = [] of String part_count.times do |partition| if File.exists? "#{name}.#{partition}.ncch" - args += "-i '#{name}.#{partition}.ncch:#{partition}:#{partition}' " + args += ["-i", "#{name}.#{partition}.ncch:#{partition}:#{partition}"] end end return args @@ -60,18 +69,16 @@ def remove_cache Dir["*.ncch"].each do |fname| File.delete(fname) end end -args : String = "" - # 3ds decrypting Dir["*.3ds"].each do |ds| next if ds.includes? "decrypted" - args = "" i : UInt8 = 0 dsn : String = ds.chomp ".3ds" + args = ["-f", "cci", "-ignoresign", "-target", "p", "-o", "#{dsn}-decrypted.3ds"] puts "Decrypting: #{ds.colorize.mode(:bold)}..." - log.puts %x[./ctrdecrypt '#{ds}'] + run_tool("ctrdecrypt", [ds]) Dir["#{dsn}.*.ncch"].each do |ncch| case ncch @@ -92,10 +99,10 @@ Dir["*.3ds"].each do |ds| when "#{dsn}.UpdateData.ncch" i = 7 end - args += "-i '#{ncch}:#{i}:#{i}' " + args += ["-i", "#{ncch}:#{i}:#{i}"] end puts "Building decrypted #{dsn} 3DS..." - log.puts %x[./makerom -f cci -ignoresign -target p -o '#{dsn}-decrypted.3ds' #{args}] + run_tool("makerom", args) check_decrypt(dsn, "3ds") remove_cache end @@ -106,39 +113,41 @@ Dir["*.cia"].each do |cia| puts "Decrypting: #{cia.colorize.mode(:bold)}..." cutn : String = cia.chomp ".cia" - args = "" - content = %x[./ctrtool --seeddb=seeddb.bin '#{cia}'] + content = run_tool("ctrtool", ["--seeddb=seeddb.bin", cia]) # game if content.match /T.*d.*00040000/ puts "CIA Type: Game" - log.puts %x[./ctrdecrypt '#{cia}'] - + run_tool("ctrdecrypt", [cia]) + + args = ["-f", "cia", "-ignoresign", "-target", "p", "-o", "#{cutn}-decfirst.cia"] i : UInt8 = 0 Dir["*.ncch"].sort.each do |ncch| - args += "-i '#{ncch}:#{i}:#{i}' " + args += ["-i", "#{ncch}:#{i}:#{i}"] i += 1 end - log.puts %x[./makerom -f cia -ignoresign -target p -o '#{cutn}-decfirst.cia' #{args}] + run_tool("makerom", args) # patch elsif content.match /T.*d.*0004000(e|E)/ puts "CIA Type: #{"Patch".colorize.mode(:bold)}" - log.puts %x[./ctrdecrypt '#{cia}'] + run_tool("ctrdecrypt", [cia]) + args = ["-f", "cia", "-ignoresign", "-target", "p", "-o", "#{cutn} (Patch)-decrypted.cia"] patch_parts : Int32 = Dir["#{cutn}.*.ncch"].size - args = gen_args(cutn, patch_parts) + args += gen_args(cutn, patch_parts) - log.puts %x[./makerom -f cia -ignoresign -target p -o '#{cutn} (Patch)-decrypted.cia' #{args}] + run_tool("makerom", args) check_decrypt("#{cutn} (Patch)", "cia") # dlc elsif content.match /T.*d.*0004008(c|C)/ puts "CIA Type: #{"DLC".colorize.mode(:bold)}" - log.puts %x[./ctrdecrypt '#{cia}'] + run_tool("ctrdecrypt", [cia]) + args = ["-f", "cia", "-dlc", "-ignoresign", "-target", "p", "-o", "#{cutn} (DLC)-decrypted.cia"] dlc_parts : Int32 = Dir["#{cutn}.*.ncch"].size - args = gen_args(cutn, dlc_parts) - - log.puts %x[./makerom -f cia -dlc -ignoresign -target p -o '#{cutn} (DLC)-decrypted.cia' #{args}] + args += gen_args(cutn, dlc_parts) + + run_tool("makerom", args) check_decrypt("#{cutn} (DLC)", "cia") else puts "Unsupported CIA" @@ -148,13 +157,13 @@ Dir["*.cia"].each do |cia| cutn = decfirst.chomp "-decfirst.cia" puts "Building decrypted #{cutn} CCI..." - log.puts %x[./makerom -ciatocci '#{decfirst}' -o '#{cutn}-decrypted.cci'] + run_tool("makerom", ["-ciatocci", decfirst, "-o", "#{cutn}-decrypted.cci"]) check_decrypt(cutn, "cci") end remove_cache end -log.flush -log.close +LOG.flush +LOG.close puts "Log saved"