rsgt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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