rsgt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +6 -0
  5. data/README.markdown +139 -0
  6. data/Rakefile +10 -0
  7. data/bin/rsgt +5 -0
  8. data/lib/rsgt.rb +42 -0
  9. data/lib/rsgt/audio_extractor.rb +25 -0
  10. data/lib/rsgt/cli.rb +89 -0
  11. data/lib/rsgt/command_runner.rb +41 -0
  12. data/lib/rsgt/encrypted_steam_file.rb +44 -0
  13. data/lib/rsgt/multipacker.rb +114 -0
  14. data/lib/rsgt/multipacker/config_processor.rb +44 -0
  15. data/lib/rsgt/repacker.rb +37 -0
  16. data/lib/rsgt/rs_custom_song_toolkit.rb +41 -0
  17. data/lib/rsgt/saved_game.rb +37 -0
  18. data/lib/rsgt/saved_game/learn_a_song/song.rb +46 -0
  19. data/lib/rsgt/saved_game/score_attack/song.rb +38 -0
  20. data/lib/rsgt/saved_game/song_collection.rb +16 -0
  21. data/lib/rsgt/saved_game/statistics.rb +91 -0
  22. data/lib/rsgt/saved_game/statistics/song.rb +58 -0
  23. data/lib/rsgt/steam.rb +45 -0
  24. data/lib/rsgt/unpacked_psarc.rb +72 -0
  25. data/lib/rsgt/version.rb +3 -0
  26. data/lib/rsgt/vocals_extractor.rb +24 -0
  27. data/lib/rsgt/wav_shifter.rb +54 -0
  28. data/lib/rsgt/wwise_converter.rb +99 -0
  29. data/rsgt.gemspec +31 -0
  30. data/spec/lib/rsgt/audio_extractor_spec.rb +14 -0
  31. data/spec/lib/rsgt/command_runner_spec.rb +37 -0
  32. data/spec/lib/rsgt/repacker_spec.rb +24 -0
  33. data/spec/lib/rsgt/rs_custom_song_toolkit_spec.rb +47 -0
  34. data/spec/lib/rsgt/saved_game/learn_a_song/song_spec.rb +15 -0
  35. data/spec/lib/rsgt/saved_game_spec.rb +49 -0
  36. data/spec/lib/rsgt/unpacked_psarc_spec.rb +35 -0
  37. data/spec/lib/rsgt/vocals_extractor_spec.rb +14 -0
  38. data/spec/lib/rsgt/wav_shifter_spec.rb +30 -0
  39. data/spec/lib/rsgt/wwise_converter_spec.rb +22 -0
  40. data/spec/spec_helper.rb +11 -0
  41. metadata +168 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4cfc5355379a4cbe1f82dbc8e1804e17d39cd59c
4
+ data.tar.gz: b4d7dfd5381f73ab1afb6777db0915e3cb2ca474
5
+ SHA512:
6
+ metadata.gz: 61f701a60a21e1cc8baf7c2fda41b0c7e86d100ea7c6f05072b4d25450afa067e294548e54ece30574d03cf861490247828e501cf75c9ac563fd0c9b85ca5d9f
7
+ data.tar.gz: 4c6bfe25aa2a2d62ca7e62de312fac64744e861db37ff697d6696ad6083fe6e17a3b83f8044b005303b6b0f5f0e58ec8e826be4092d998c34893b7fc810166da
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ tmp/
2
+ coverage/
3
+
4
+ Gemfile.lock
5
+
6
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "pry-coolline"
6
+ gem "coveralls"
data/README.markdown ADDED
@@ -0,0 +1,139 @@
1
+ # Rocksmith Guitar Tech
2
+
3
+ Ruby 💎 tool for working with rocksmith songs, DLC, and profile data.
4
+ Almost certainly only useful for Mac.
5
+
6
+ ## Installation
7
+
8
+ `gem install rsgt`
9
+
10
+ ### Dependencies
11
+
12
+ This tool relies on various bits of software to do the various bits of things it does...
13
+
14
+ - `pyrocksmith` (https://github.com/0x0L/rocksmith)
15
+ - Needed for anything involving unpacking/repacking
16
+ - Install by doing:
17
+ - `brew install pip`
18
+ - `pip3 install git+https://github.com/0x0L/rocksmith.git`
19
+ - WWise (https://www.audiokinetic.com/download/#macosx)
20
+ - Needed if you're going to repack audio
21
+ - [Rocksmith Custom Song Toolkit](https://www.rscustom.net/)
22
+ - Used for extracting audio out to ogg files
23
+ - Used for sng <-> xml conversion (like changing vocals)
24
+ - Installation:
25
+ - Move to `/Applications`
26
+ - `brew cask install mono-mdk`
27
+ - `brew install wine winetricks`
28
+ - `ffmpeg`
29
+ - `brew install ffmpeg`
30
+
31
+ ## Usage: Uncensoring
32
+
33
+ For example...
34
+
35
+ ```bash
36
+ # Extracts ragebomb_vocals.xml:
37
+ rsgt extract-vocals --psarc=ragekilling_m.psarc
38
+
39
+ # Edit the vocals file, putting back in missing words:
40
+ vim ragebomb_vocals.xml
41
+
42
+ # Extract the audio track to use as a guide track
43
+ # This creates output.ogg
44
+ rsgt extract-audio --psarc=ragekilling_m.psarc
45
+
46
+ # Using output.ogg as a guide track, take the real CD wav, line it up, save as fixed.wav
47
+ <AUDIO EDITING>
48
+
49
+ # Repack the fixed vocals and audio track back in:
50
+ rsgt \
51
+ repack \
52
+ --psarc=ragekilling_m.psarc \
53
+ --xml=ragebomb_vocals.xml \
54
+ --audio=fixed.wav
55
+ ```
56
+
57
+ #### Repacking Options
58
+
59
+ You can also repack the preview if you want:
60
+
61
+ ```bash
62
+ rsgt \
63
+ repack \
64
+ --psarc=ragekilling_m.psarc \
65
+ --xml=ragebomb_vocals.xml \
66
+ --audio=fixed.wav \
67
+ --preview \
68
+ --chorus=45
69
+ ```
70
+
71
+ ## Usage: Multipacker
72
+
73
+ The Multipacker can pack a bunch of psarcs into a single multipack.
74
+
75
+ For example, say you have this kind of folder structure:
76
+
77
+ ```
78
+ ~/rocksmith/songs
79
+ /uncensored
80
+ - Green Day - American Idiot_m.psarc
81
+ ... some other songs
82
+ /Official
83
+ - Green Day - American Idiot_m.psarc
84
+ ... hundreds of other songs
85
+ /CDLC
86
+ ... hundreds of custom songs
87
+ ```
88
+
89
+ Then you'd create a config file such as:
90
+
91
+ ```yaml
92
+ destination: ~/Library/Application\ Support/Steam/SteamApps/common/Rocksmith2014/dlc
93
+
94
+ repacks:
95
+ - title: Uncensored
96
+ directory: ~/rocksmith/songs/Uncensored
97
+ unpack_dir: ~/rocksmith/unpacks/uncensored
98
+ repack_dir: ~/rocksmith/repacks/uncensored
99
+ options:
100
+ reset_unpack: false
101
+ reset_repack: false
102
+
103
+ - title: Official
104
+ directory: ~/rocksmith/songs/Official
105
+ unpack_dir: ~/rocksmith/unpacks/official
106
+ repack_dir: ~/rocksmith/repacks/official
107
+ options:
108
+ reset_unpack: false
109
+ reset_repack: false
110
+
111
+ - title: Custom
112
+ directory: ~/rocksmith/songs/CDLC
113
+ unpack_dir: ~/rocksmith/unpacks/cdlc
114
+ repack_dir: ~/rocksmith/repacks/cdlc
115
+ options:
116
+ reset_unpack: false
117
+ reset_repack: false
118
+ ```
119
+
120
+ And running `rsgt multipack --config=thatfile.yml` would:
121
+ - Repack all of your uncensored songs into a single "Uncensored - n songs _m.psarc" file, and move it to your DLC folder.
122
+ - Repack all of your official songs into a single "Official - n songs _m.psarc" file, and move it to your DLC folder.
123
+ - Leaving out songs that it already got from uncensored
124
+ - Repack all of your custom songs into a single "Custom - n songs _m.psarc" file, and move it to your DLC folder.
125
+
126
+ ##### Notes:
127
+ - Make sure to check the RS DLC folder, and remove any duplicates - just leave the lastest of each multipack
128
+ - You can keep your `unpack`/`repack` directories around as a cache
129
+ - The "reset" options will nuke that folder:
130
+ - If you're just adding songs, keep it as false, and it'll be zippy
131
+ - If you're deleting/changing songs, change it to true, so stale copies don't get packed in
132
+
133
+ ## Usage: Saved Game exploration
134
+
135
+ ... this area is WIP, you probably don't want to mess with `RSGuitarTech::SavedGame` at all right now.
136
+
137
+ ## License
138
+
139
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core"
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = "spec/**/*_spec.rb"
7
+ spec.rspec_opts = "--color"
8
+ end
9
+
10
+ task default: :spec
data/bin/rsgt ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rsgt"
4
+
5
+ RSGuitarTech::CLI.parse ARGV
data/lib/rsgt.rb ADDED
@@ -0,0 +1,42 @@
1
+ require "tmpdir"
2
+ require "fileutils"
3
+ require "shellwords"
4
+ require "bindata"
5
+ require "zlib"
6
+ require "json"
7
+ require "openssl"
8
+ require "active_support"
9
+ require "active_support/core_ext"
10
+
11
+ begin
12
+ require "pry"
13
+ rescue LoadError
14
+ end
15
+
16
+ require "rsgt/version"
17
+ require "rsgt/command_runner"
18
+ require "rsgt/rs_custom_song_toolkit"
19
+ require "rsgt/unpacked_psarc"
20
+ require "rsgt/vocals_extractor"
21
+ require "rsgt/audio_extractor"
22
+ require "rsgt/wwise_converter"
23
+ require "rsgt/repacker"
24
+ require "rsgt/wav_shifter"
25
+ require "rsgt/multipacker/config_processor"
26
+ require "rsgt/steam"
27
+ require "rsgt/encrypted_steam_file"
28
+ require "rsgt/saved_game/song_collection"
29
+ require "rsgt/saved_game/score_attack/song"
30
+ require "rsgt/saved_game/statistics/song"
31
+ require "rsgt/saved_game/learn_a_song/song"
32
+ require "rsgt/saved_game/statistics"
33
+ require "rsgt/saved_game"
34
+ require "rsgt/multipacker"
35
+ require "rsgt/cli"
36
+
37
+ module RSGuitarTech
38
+ class << self
39
+ attr_accessor :verbose
40
+ @verbose = false
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ module RSGuitarTech
2
+ class AudioExtractor
3
+
4
+ attr_accessor :psarc, :opts, :unpacked
5
+
6
+ def initialize(opts)
7
+ @psarc = File.expand_path(opts.delete :psarc)
8
+ @opts = opts
9
+ end
10
+
11
+ def extract!
12
+ UnpackedPSARC.from_psarc(psarc, opts) do |unpacked|
13
+
14
+ # Convert main track from wem to ogg:
15
+ CommandRunner.run! RSCustomSongToolkit.ww2ogg(unpacked.audio_track)
16
+ raise StandardError unless File.exist?(unpacked.ogg_track)
17
+
18
+ # Revorb it so it doesn't sound bad...
19
+ CommandRunner.run! RSCustomSongToolkit.revorb(unpacked.ogg_track)
20
+
21
+ FileUtils.cp unpacked.ogg_track, "output.ogg"
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/rsgt/cli.rb ADDED
@@ -0,0 +1,89 @@
1
+ require "trollop"
2
+
3
+ module RSGuitarTech
4
+ class CLI
5
+
6
+ SUB_COMMANDS = %w(extract-vocals extract-audio repack shift multipack)
7
+
8
+ TOP_BANNER = <<~STRING
9
+ Tool to work with RS DLC and saves
10
+ USAGE: rsgt [options] command [command_options]
11
+
12
+ Options for all commands:
13
+
14
+ STRING
15
+
16
+ USAGE = <<~STRING
17
+
18
+ Available command are:
19
+ extract-vocals: Extract a .xml of the vocals for editing
20
+ extract-audio: Extract the audio track as an .ogg (ie, to use as guide track)
21
+ repack: Repack altered lyrics/audio into a psarc
22
+ shift: Shift a .wav by ms
23
+ multipack: Repack multiple PSARCs into a single PSARC
24
+ STRING
25
+
26
+ def self.parse(args)
27
+ global_opts = Trollop::options do
28
+ banner TOP_BANNER
29
+ opt :verbose, "More output", short: "-v"
30
+ banner USAGE
31
+
32
+ stop_on SUB_COMMANDS
33
+ end
34
+
35
+ cmd = args.shift # get the subcommand
36
+ cmd_opts = case cmd
37
+ when "extract-vocals"
38
+ Trollop::options do
39
+ banner "Extracts and converts vocals into an XML"
40
+ opt :psarc, "PSARC to extract", type: :string, required: true
41
+ opt :output, "Optional path to output the XML", type: :string
42
+ end
43
+ when "extract-audio"
44
+ Trollop::options do
45
+ banner "Extracts and converts the audio track into an .ogg"
46
+ opt :psarc, "PSARC to extract", type: :string, required: true
47
+ opt :output, "Optional path to output the .ogg", type: :string
48
+ end
49
+ when "repack"
50
+ Trollop::options do
51
+ banner "Repack a psarc with new vocals XML and/or audio tracks"
52
+ opt :psarc, "PSARC to repack files into", type: :string, required: true
53
+ opt :vocals_xml, "Vocals XML file to repack", type: :string
54
+ opt :audio, "Audio file to repack", type: :string
55
+ opt :wwise_ver, "WWise version to use", type: :string, default: "2016.2.4.6098"
56
+ opt :quality, "Quality of OGG encoding", type: :string, default: "6"
57
+ opt :preview, "Flag to regenerate the preview", type: :boolean, default: false
58
+ opt :chorus, "Seconds in to start the preview", type: :string, default: "30"
59
+ opt :output, "Optional place to output the repacked psarc", type: :string
60
+ end
61
+ when "shift"
62
+ Trollop::options do
63
+ banner "Shift a .WAV file by given MS"
64
+ opt :wav, "WAV file to shift", type: :string, required: true
65
+ opt :dir, "Direction to shift (forward or backward)", type: :string, required: true
66
+ opt :amount, "Amount in MS", type: :string, required: true
67
+ end
68
+ when "multipack"
69
+ Trollop::options do
70
+ banner "Repack multiple PSARCs into a single PSARC"
71
+ opt :config, "Config File", type: :string, required: true
72
+ end
73
+ else
74
+ Trollop::educate
75
+ end
76
+
77
+ RSGuitarTech.verbose = true if global_opts[:verbose]
78
+ RSGuitarTech.verbose = true if cmd_opts[:verbose]
79
+
80
+ case cmd
81
+ when "extract-vocals" then VocalsExtractor.new(cmd_opts).extract!
82
+ when "extract-audio" then AudioExtractor.new(cmd_opts).extract!
83
+ when "repack" then Repacker.new(cmd_opts).repack!
84
+ when "shift" then WavShifter.new(cmd_opts).shift!
85
+ when "multipack" then Multipacker::ConfigProcessor.new(cmd_opts).process!
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,41 @@
1
+ require "shellwords"
2
+ require "open3"
3
+
4
+ module RSGuitarTech
5
+ class CommandRunner
6
+
7
+ def self.run!(args, skip_escaping: false)
8
+ self.new(args, skip_escaping: skip_escaping).run!
9
+ end
10
+
11
+ attr_accessor :cmd
12
+
13
+ def initialize(args, skip_escaping: false)
14
+ if skip_escaping
15
+ @cmd = Array(args).join " "
16
+ else
17
+ @cmd = Shellwords.join Array(args)
18
+ end
19
+ end
20
+
21
+ def run!
22
+ if RSGuitarTech.verbose
23
+ puts "*" * 40
24
+ puts ""
25
+ puts cmd
26
+ puts ""
27
+ end
28
+
29
+ stdout, stderr, status = Open3.capture3(cmd)
30
+ if RSGuitarTech.verbose
31
+ stdout.split("\n").each do |line|
32
+ puts "--- #{line}"
33
+ end
34
+ stderr.split("\n").each do |line|
35
+ puts "!!! #{line}"
36
+ end
37
+ end
38
+ status
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ module RSGuitarTech
2
+ class EncryptedSteamFile < BinData::Record
3
+ endian :little
4
+
5
+ string :evas, read_length: 4 # Is always 'EVAS'
6
+ uint32 :version # Is always 1
7
+ uint64 :profile # Steam profile
8
+ uint32 :len
9
+ rest :payload
10
+
11
+ KEY = [
12
+ 0x72, 0x8B, 0x36, 0x9E, 0x24, 0xED, 0x01, 0x34,
13
+ 0x76, 0x85, 0x11, 0x02, 0x18, 0x12, 0xAF, 0xC0,
14
+ 0xA3, 0xC2, 0x5D, 0x02, 0x06, 0x5F, 0x16, 0x6B,
15
+ 0x4B, 0xCC, 0x58, 0xCD, 0x26, 0x44, 0xF2, 0x9E
16
+ ].pack('C*')
17
+
18
+ def decrypted
19
+ aes = OpenSSL::Cipher::Cipher.new 'AES-256-ECB'
20
+ aes.decrypt
21
+ aes.key = KEY
22
+ aes.padding = 0
23
+
24
+ aes.update(payload) + aes.final
25
+ end
26
+
27
+ def encrypted
28
+ aes = OpenSSL::Cipher::Cipher.new 'AES-256-ECB'
29
+ aes.encrypt
30
+ aes.key = KEY
31
+ aes.padding = 0
32
+
33
+ aes.update(payload) + aes.final
34
+ end
35
+
36
+ def uncompressed
37
+ Zlib::Inflate.inflate decrypted
38
+ end
39
+
40
+ def uncompressed_json
41
+ JSON.parse uncompressed.strip
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,114 @@
1
+ module RSGuitarTech
2
+ class Multipacker
3
+
4
+ def self.process!(*args)
5
+ self.new(*args).process!
6
+ end
7
+
8
+ attr_accessor *%i{title directory unpack_dir repack_dir dest_dir filters options count}
9
+
10
+ def initialize(title:, directory:, unpack_dir:, repack_dir:, dest_dir:, filters: {}, options: {})
11
+ @title = title
12
+ @directory = directory
13
+ @unpack_dir = unpack_dir
14
+ @repack_dir = repack_dir
15
+ @dest_dir = dest_dir
16
+ @filters = filters
17
+ @options = options
18
+ @count = {psarcs: 0, skipped: 0, failed: 0, total: 0}
19
+ end
20
+
21
+ def process!
22
+ reset! unpack_dir if options[:reset_unpack]
23
+ reset! repack_dir if options[:reset_repack]
24
+ ensure_dir! unpack_dir
25
+ ensure_dir! repack_dir
26
+ unpack!
27
+ if count[:skipped] >= count[:total]
28
+ puts "All done! No new files found to repack"
29
+ return
30
+ end
31
+ repack!
32
+ move!
33
+ end
34
+
35
+ def reset!(dir)
36
+ shell_out "rm -rf #{dir}"
37
+ end
38
+
39
+ def ensure_dir!(dir)
40
+ shell_out "mkdir -p #{dir}/"
41
+ end
42
+
43
+ def unpack!
44
+ filtered_psarscs.each do |dlc|
45
+ if unpack dlc
46
+ count[:total] = count[:total] + 1
47
+ else
48
+ count[:failed] = count[:failed] + 1
49
+ end
50
+ end
51
+ end
52
+
53
+ def repack!
54
+ puts "Packing #{count[:total]} songs into #{repack_dir}.psarc"
55
+ shell_out %Q{pyrocksmith --no-crypto --pack "#{repack_dir}"}
56
+ end
57
+
58
+ def move!
59
+ packed_psarc = repack_dir.split("/").last + ".psarc"
60
+ puts "Moving packed psarc (#{packed_psarc}) to: #{title} - #{count[:total]} songs _m.psarc"
61
+ shell_out "mv #{packed_psarc} #{dest_dir}/#{title}\\ \\-\\ #{count[:total]}\\ songs\\ \\_m.psarc"
62
+ end
63
+
64
+ private
65
+
66
+ def unpack(dlc)
67
+ count[:psarcs] = count[:psarcs] + 1
68
+ base_name = File.basename dlc, '.*'
69
+
70
+ if Dir.exists? "#{unpack_dir}/#{base_name}"
71
+ puts "Skipping #{dlc} (#{base_name})"
72
+ count[:skipped] = count[:skipped] + 1
73
+ return true
74
+ end
75
+
76
+ puts "Unpacking #{dlc} (#{base_name})"
77
+
78
+ return false unless shell_out %Q{pyrocksmith --no-crypto --unpack "#{dlc}"}
79
+ return false unless shell_out %Q{mv "#{base_name}/" #{unpack_dir}/}
80
+ return false unless shell_out %Q{ditto "#{unpack_dir}/#{base_name}/" "#{repack_dir}"}
81
+
82
+ true
83
+ end
84
+
85
+ def filtered_psarscs
86
+ @filtered_psarscs ||= begin
87
+ reject_filters = Array(filters[:reject]).flatten
88
+ select_filters = Array(filters[:select]).flatten
89
+ limit = options[:limit]
90
+
91
+ _fp = psarcs
92
+ _fp = _fp.take(limit) if limit
93
+ _fp = _fp.reject { |filename| reject_filters.include? simplfied(filename) } if reject_filters.any?
94
+ _fp = _fp.select { |filename| select_filters.include? simplfied(filename) } if select_filters.any?
95
+ _fp
96
+ end
97
+ end
98
+
99
+ def psarcs
100
+ @psarcs ||= Dir.glob("#{directory}/*.psarc")
101
+ end
102
+
103
+ def simplfied(filename)
104
+ filename.split("/").last.split(" _m").first
105
+ end
106
+
107
+ def shell_out(cmd)
108
+ puts cmd if options[:verbose]
109
+ return true if system(cmd)
110
+ puts "FAILED: #{cmd}"
111
+ false
112
+ end
113
+ end
114
+ end