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
@@ -0,0 +1,72 @@
1
+ module RSGuitarTech
2
+ class UnpackedPSARC
3
+
4
+ def self.from_psarc(psarc, opts)
5
+ raise ArgumentError, "A block is required" unless block_given?
6
+ raise ArgumentError, "#{psarc} not found!" unless File.exist?(psarc)
7
+
8
+ puts "Unpacking psarc to temporary location..."
9
+ # Use pyrocksmith to unpack the file, and check that it worked..
10
+ CommandRunner.run! ["pyrocksmith", "--unpack", psarc, "--no-crypto"]
11
+ expected_extraction = File.basename psarc, ".*"
12
+ raise ArgumentError unless File.exist?(expected_extraction + "/appid.appid")
13
+
14
+ # Move it to a tmpdir, and yield
15
+ Dir.mktmpdir do |dir|
16
+ FileUtils.mv expected_extraction, dir
17
+ yield self.new(psarc, dir, opts)
18
+ end
19
+ end
20
+
21
+ attr_accessor :psarc, :tmpdir, :opts
22
+
23
+ def initialize(psarc, tmpdir, opts)
24
+ @psarc = psarc
25
+ @tmpdir = tmpdir
26
+ @opts = opts
27
+ end
28
+
29
+ def repack!
30
+ puts "Repacking altered psarc..."
31
+ CommandRunner.run! ["pyrocksmith", "--no-crypto", "--pack", "#{tmpdir}/#{expected_extraction}"]
32
+ puts "Done! ✅"
33
+ end
34
+
35
+ def expected_extraction
36
+ File.basename psarc, ".*"
37
+ end
38
+
39
+ def manifest(type)
40
+ Dir.glob("#{tmpdir}/#{expected_extraction}/manifests/*/*.json").detect { |filename| filename.include? type.to_s }
41
+ end
42
+
43
+ def sng_bin(type)
44
+ Dir.glob("#{tmpdir}/#{expected_extraction}/songs/bin/macos/*.sng").detect { |filename| filename.include? type.to_s }
45
+ end
46
+
47
+ def sng_xml
48
+ Dir.glob("#{tmpdir}/#{expected_extraction}/songs/bin/macos/*.xml").first
49
+ end
50
+
51
+ def audio_track(track: :main)
52
+ audio_tracks[track]
53
+ end
54
+
55
+ def ogg_track
56
+ audio_tracks[:main].gsub ".wem", ".ogg"
57
+ end
58
+
59
+ private
60
+
61
+ def audio_tracks
62
+ @audio_tracks ||= begin
63
+ both_wems = Dir.glob("#{tmpdir}/#{expected_extraction}/audio/mac/*.wem").sort_by { |wem| File.stat(wem).size }
64
+
65
+ {
66
+ main: both_wems.last,
67
+ preview: both_wems.first
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module RSGuitarTech
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ module RSGuitarTech
2
+ class VocalsExtractor
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
+ puts ".. Extracting vocals from sng to xml..."
13
+ UnpackedPSARC.from_psarc(psarc, opts) do |unpacked|
14
+ CommandRunner.run! RSCustomSongToolkit.sng2xml(
15
+ manifest: unpacked.manifest(:vocals),
16
+ input: unpacked.sng_bin(:vocals)
17
+ )
18
+ raise StandardError, "Failed to extract vocals" unless File.exist? unpacked.sng_xml
19
+
20
+ FileUtils.cp unpacked.sng_xml, "."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ module RSGuitarTech
2
+ class WavShifter
3
+
4
+ attr_accessor :wav, :dir, :amount
5
+
6
+ def initialize(opts)
7
+ @wav = File.expand_path(opts.delete :wav)
8
+ @dir = opts[:dir]
9
+ @amount = opts[:amount].to_i
10
+ end
11
+
12
+ def shift!
13
+ case dir
14
+ when "forward" then forward!
15
+ when /back/ then backward!
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def forward!
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def backward!
26
+ Dir.mktmpdir do |tmpdir|
27
+ puts "Trimming #{amount}ms from beginning..."
28
+ status = CommandRunner.run! [
29
+ "ffmpeg", "-i", wav,
30
+ "-ss", start_trim_timestamp,
31
+ "-acodec", "copy",
32
+ "#{tmpdir}/shifted.wav"
33
+ ]
34
+ if status.success?
35
+ puts "Success!"
36
+ FileUtils.cp "#{tmpdir}/shifted.wav", wav
37
+ else
38
+ puts "Failed!"
39
+ end
40
+ end
41
+ end
42
+
43
+ TS_CONV = [1, 1000, 60000, 3600000]
44
+
45
+ def start_trim_timestamp
46
+ h = (amount / TS_CONV[3])
47
+ m = (amount % TS_CONV[3] / TS_CONV[2])
48
+ s = (amount % TS_CONV[2] / TS_CONV[1])
49
+ ms = (amount % TS_CONV[1])
50
+
51
+ "%02d:%02d:%02d.%03d" % [h, m, s, ms]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,99 @@
1
+ module RSGuitarTech
2
+ class WWiseConverter
3
+
4
+ TEMPLATE_TAR_BZ = "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/WwiseYEAR.tar.bz2"
5
+
6
+ attr_accessor :source_file, :wwise_version, :quality, :chorus, :dir
7
+
8
+ def initialize(source_file, opts = {})
9
+ @source_file = source_file
10
+ @wwise_version = opts[:wwise_ver]
11
+ @quality = opts[:quality]
12
+ @chorus = opts[:chorus]
13
+ end
14
+
15
+ def convert!
16
+ Dir.mktmpdir do |dir|
17
+ @dir = dir
18
+
19
+ extract_base_template
20
+ prepare_template
21
+ copy_source
22
+ ffmpeg_preview
23
+ run_wwise
24
+ if wwise_version[0..3].to_i >= 2016
25
+ downgrade_headers "#{template_dir}/GeneratedSoundBanks/Windows/144151451.wem"
26
+ downgrade_headers "#{template_dir}/GeneratedSoundBanks/Windows/309608343.wem"
27
+ end
28
+ yield self
29
+ end
30
+ end
31
+
32
+ def extract_base_template
33
+ just_tar = template_tar_bz.gsub(".bz2", "")
34
+ CommandRunner.run! ["bunzip2", "-k", template_tar_bz] # Unzip Wwise2016.tar.bz2 to Wwise2016.tar
35
+ CommandRunner.run! ["tar", "-xzvf", just_tar, "-C", dir] # Untar Wwise2016.tar to the workdir
36
+ end
37
+
38
+ def prepare_template
39
+ CommandRunner.run! ["mkdir", "-p", "#{template_dir}/.cache/Windows/SFX"]
40
+ CommandRunner.run! ["sed", "-i.bak", "'s/%QF1%/#{quality}/'", default_work_unit.shellescape], skip_escaping: true
41
+ CommandRunner.run! ["sed", "-i.bak", "'s/%QF2%/#{quality}/'", default_work_unit.shellescape], skip_escaping: true
42
+ end
43
+
44
+ def copy_source
45
+ FileUtils.cp source_file, "#{template_dir}/Originals/SFX/Audio.wav"
46
+ end
47
+
48
+ def ffmpeg_preview
49
+ CommandRunner.run! ["ffmpeg", "-i", source_file, "-ss", chorus, "-t", "30", "#{template_dir}/Originals/SFX/Audio_preview.wav"]
50
+ end
51
+
52
+ def run_wwise
53
+ wwise_cmd = [
54
+ wwise_cli,
55
+ "#{template_dir}/Template.wproj",
56
+ "-GenerateSoundBanks",
57
+ "-Platform",
58
+ "Windows",
59
+ "-NoWwiseDat",
60
+ "-ClearAudioFileCache",
61
+ "-Save"
62
+ ]
63
+ wwise_cmd << "-Verbose" if RSGuitarTech.verbose
64
+ CommandRunner.run! wwise_cmd
65
+ end
66
+
67
+ def downgrade_headers(file)
68
+ CommandRunner.run! %Q{printf "$(printf '\\x%02X' 03)" | dd of=#{file} bs=1 seek=40 count=1 conv=notrunc}, skip_escaping: true
69
+ CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=41 count=1 conv=notrunc}, skip_escaping: true
70
+ CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=42 count=1 conv=notrunc}, skip_escaping: true
71
+ CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=43 count=1 conv=notrunc}, skip_escaping: true
72
+ end
73
+
74
+ def results
75
+ {
76
+ main: "#{template_dir}/GeneratedSoundBanks/Windows/144151451.wem",
77
+ preview: "#{template_dir}/GeneratedSoundBanks/Windows/309608343.wem"
78
+ }
79
+ end
80
+
81
+ private
82
+
83
+ def template_dir
84
+ "#{dir}/Template"
85
+ end
86
+
87
+ def template_tar_bz
88
+ TEMPLATE_TAR_BZ.dup.gsub "YEAR", wwise_version[0..3]
89
+ end
90
+
91
+ def wwise_cli
92
+ "/Applications/Audiokinetic/Wwise #{wwise_version}/Wwise.app/Contents/Tools/WwiseCLI.sh"
93
+ end
94
+
95
+ def default_work_unit
96
+ "#{template_dir}/Interactive Music Hierarchy/Default Work Unit.wwu"
97
+ end
98
+ end
99
+ end
data/rsgt.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "rsgt/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.platform = Gem::Platform::RUBY
7
+ s.name = "rsgt"
8
+ s.version = RSGuitarTech::VERSION
9
+ s.authors = ["Justin Aiken"]
10
+ s.email = ["60tonangel@gmail.com"]
11
+ s.license = "MIT"
12
+ s.homepage = "https://github.com/JustinAiken/rsgt"
13
+ s.summary = %q{Rocksmith}
14
+ s.description = %q{Rocksmith}
15
+
16
+ s.rubyforge_project = "rsgt"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
20
+ s.require_paths = ["lib"]
21
+ s.bindir = "bin"
22
+ s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
23
+
24
+ s.add_dependency "bindata"
25
+ s.add_dependency "trollop", "~> 2.1"
26
+ s.add_dependency "rainbow", "~> 2.2"
27
+ s.add_dependency "activesupport", ">= 3.0"
28
+
29
+ s.add_development_dependency "rspec", "~> 3.5"
30
+ s.add_development_dependency "pry"
31
+ end
@@ -0,0 +1,14 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::AudioExtractor do
4
+ let(:extractor) { described_class.new psarc: psarc }
5
+ let(:psarc) { "foo_m.psarc" }
6
+
7
+ describe "#extract!" do
8
+ it "extracts" do
9
+ allow(extractor).to receive :puts
10
+ expect(RSGuitarTech::UnpackedPSARC).to receive :from_psarc
11
+ extractor.extract!
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::CommandRunner do
4
+ let(:runner) { described_class }
5
+ let(:args) { "ls" }
6
+ let(:skip_escaping) { false }
7
+ subject(:run!) { runner.run! args, skip_escaping: skip_escaping }
8
+
9
+ it { should be_success }
10
+
11
+ context "with spaces" do
12
+ let(:args) { "ls -al" }
13
+
14
+ after { run! }
15
+
16
+ it "escapes the command" do
17
+ expect(Open3).to receive(:capture3).with "ls\\ -al"
18
+ end
19
+
20
+ context "but skip_escaping: true" do
21
+ let(:skip_escaping) { true }
22
+
23
+ it "runs the command as-is" do
24
+ expect(Open3).to receive(:capture3).with "ls -al"
25
+ end
26
+ end
27
+ end
28
+
29
+ context "verbosely" do
30
+ before { allow(RSGuitarTech).to receive(:verbose).and_return true }
31
+
32
+ it "does some puts" do
33
+ expect_any_instance_of(described_class).to receive(:puts).at_least :once
34
+ run!
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::Repacker do
4
+ let(:repacker) { described_class.new psarc: "foo_m.psarc", vocals_xml: vocals_xml, audio: audio }
5
+ let(:vocals_xml) { "foo_vocals.xml" }
6
+ let(:audio) { "audio.wav" }
7
+
8
+ let(:unpacked) { double RSGuitarTech::UnpackedPSARC, manifest: "manifest.json", audio_track: "track.wem" }
9
+ let(:converter) { double RSGuitarTech::WWiseConverter, results: {main: "main.wav"} }
10
+
11
+ describe "#repack!" do
12
+ before { allow(repacker).to receive :puts }
13
+
14
+ it "repacks" do
15
+ expect(RSGuitarTech::UnpackedPSARC).to receive(:from_psarc).and_yield unpacked
16
+ expect_any_instance_of(RSGuitarTech::WWiseConverter).to receive(:convert!).and_yield converter
17
+ expect(FileUtils).to receive(:cp).with "main.wav", "track.wem"
18
+ expect(RSGuitarTech::CommandRunner).to receive :run!
19
+ expect(unpacked).to receive :repack!
20
+
21
+ repacker.repack!
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::RSCustomSongToolkit do
4
+ let(:rs_custom) { described_class }
5
+
6
+ it ".ww2ogg" do
7
+ expect(rs_custom.ww2ogg "foo.wem").to eq [
8
+ "wine",
9
+ "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/ww2ogg.exe",
10
+ "foo.wem",
11
+ "--pcb",
12
+ described_class::PCB_PATH
13
+ ]
14
+ end
15
+
16
+ it ".revorb" do
17
+ expect(rs_custom.revorb "foo.ogg").to eq [
18
+ "wine",
19
+ "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/revorb.exe",
20
+ "foo.ogg"
21
+ ]
22
+ end
23
+
24
+ it ".sng2xml" do
25
+ expect(rs_custom.sng2xml manifest: "manifest.json", input: "foo.sng").to eq [
26
+ "wine",
27
+ "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/sng2014.exe",
28
+ "--sng2xml",
29
+ "--manifest=manifest.json",
30
+ "--input=foo.sng",
31
+ "--arrangement=Vocal",
32
+ "--platform=Mac"
33
+ ]
34
+ end
35
+
36
+ it ".xml2sng" do
37
+ expect(rs_custom.xml2sng manifest: "manifest.json", input: "foo.xml").to eq [
38
+ "wine",
39
+ "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/sng2014.exe",
40
+ "--xml2sng",
41
+ "--manifest=manifest.json",
42
+ "--input=foo.xml",
43
+ "--arrangement=Vocal",
44
+ "--platform=Mac"
45
+ ]
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::SavedGame::LearnASong::Song do
4
+ pending ".from"
5
+
6
+ describe "#==" do
7
+ subject { described_class.new id: "123", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
8
+ let(:song_b) { described_class.new id: "123", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
9
+ let(:song_c) { described_class.new id: "456", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
10
+
11
+ it { should eq song_b }
12
+ it { should_not eq song_c }
13
+ it { should_not eq nil }
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe RSGuitarTech::SavedGame do
4
+ let(:saved_game) { described_class.new json, :filename }
5
+
6
+ pending ".from"
7
+
8
+ describe "#options" do
9
+ subject { saved_game.options }
10
+ let(:json) { {"Options" => {"foo" => "bar"}} }
11
+ it { should eq "foo" => "bar" }
12
+ end
13
+
14
+ describe "#statistics" do
15
+ subject { saved_game.statistics }
16
+ let(:json) { {"Stats" => {"foo" => "bar"}} }
17
+ it { should be_a RSGuitarTech::SavedGame::Statistics }
18
+ end
19
+
20
+ describe "#song_statistics" do
21
+ subject(:song_statistics) { saved_game.song_statistics }
22
+ let(:json) { {"Stats" => {"Songs" => {"abc" => "songdata"}}} }
23
+ it { should be_a RSGuitarTech::SavedGame::SongCollection }
24
+
25
+ it "is a keyed collection of Statistics::Song's" do
26
+ expect(song_statistics["abc"]).to be_a RSGuitarTech::SavedGame::Statistics::Song
27
+ end
28
+ end
29
+
30
+ describe "#score_attack" do
31
+ subject(:score_attack) { saved_game.score_attack }
32
+ let(:json) { {"SongsSA" => {"abc" => "songdata"}} }
33
+ it { should be_a RSGuitarTech::SavedGame::SongCollection }
34
+
35
+ it "is a keyed collection of Statistics::Song's" do
36
+ expect(score_attack["abc"]).to be_a RSGuitarTech::SavedGame::ScoreAttack::Song
37
+ end
38
+ end
39
+
40
+ describe "#learn_a_song" do
41
+ subject(:learn_a_song) { saved_game.learn_a_song }
42
+ let(:json) { {"Songs" => {"abc" => "songdata"}} }
43
+ it { should be_a RSGuitarTech::SavedGame::SongCollection }
44
+
45
+ it "is a keyed collection of Statistics::Song's" do
46
+ expect(learn_a_song["abc"]).to be_a RSGuitarTech::SavedGame::LearnASong::Song
47
+ end
48
+ end
49
+ end