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
@@ -0,0 +1,44 @@
1
+ require "yaml"
2
+
3
+ module RSGuitarTech
4
+ class Multipacker
5
+ class ConfigProcessor
6
+
7
+ attr_accessor :config
8
+
9
+ def initialize(opts)
10
+ @config = YAML.load_file opts[:config]
11
+ @packed = []
12
+ end
13
+
14
+ def process!
15
+ config["repacks"].each do |config_data|
16
+ packer = packer_for config_data
17
+ packer.process!
18
+ packer.send(:psarcs).each do |packed_psarc|
19
+ @packed << packed_psarc.split("/").last.split(" _m").first
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def packer_for(config_data)
27
+ RSGuitarTech::Multipacker.new(
28
+ title: config_data["title"],
29
+ directory: config_data["directory"],
30
+ unpack_dir: config_data["unpack_dir"],
31
+ repack_dir: config_data["repack_dir"],
32
+ dest_dir: config["destination"],
33
+ options: {
34
+ reset_unpack: config_data["options"]["reset_unpack"],
35
+ reset_repack: config_data["options"]["reset_repack"]
36
+ },
37
+ filters: {
38
+ reject: @packed
39
+ }
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ module RSGuitarTech
2
+ class Repacker
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 repack!
12
+ UnpackedPSARC.from_psarc(psarc, opts) do |unpacked|
13
+ repack_vocals(unpacked) if opts[:vocals_xml]
14
+ repack_audio(unpacked) if opts[:audio]
15
+ unpacked.repack!
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def repack_vocals(unpacked)
22
+ puts ".. Converting .xml to .sng"
23
+ CommandRunner.run! RSCustomSongToolkit.xml2sng(
24
+ manifest: unpacked.manifest(:vocals),
25
+ input: opts[:vocals_xml]
26
+ )
27
+ end
28
+
29
+ def repack_audio(unpacked)
30
+ puts ".. Converting audio to .wem"
31
+ WWiseConverter.new(opts[:audio], opts).convert! do |converter|
32
+ FileUtils.cp converter.results[:main], unpacked.audio_track
33
+ FileUtils.cp converter.results[:preview], unpacked.audio_track(track: :preview) if opts[:preview]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module RSGuitarTech
2
+ class RSCustomSongToolkit
3
+
4
+ PCB_PATH = "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/packed_codebooks_aoTuV_603.bin"
5
+
6
+ def self.ww2ogg(file)
7
+ base_cmd "ww2ogg", [file, "--pcb", PCB_PATH]
8
+ end
9
+
10
+ def self.revorb(file)
11
+ base_cmd "revorb", [file]
12
+ end
13
+
14
+ def self.sng2xml(manifest:, input:)
15
+ sng2014 "--sng2xml", manifest: manifest, input: input
16
+ end
17
+
18
+ def self.xml2sng(manifest:, input:)
19
+ sng2014 "--xml2sng", manifest: manifest, input: input
20
+ end
21
+
22
+ private
23
+
24
+ def self.sng2014(cmd, manifest:, input:, arrangement: "Vocal", platform: "Mac")
25
+ base_cmd "sng2014", [
26
+ cmd,
27
+ "--manifest=#{manifest}",
28
+ "--input=#{input}",
29
+ "--arrangement=#{arrangement}",
30
+ "--platform=#{platform}"
31
+ ]
32
+ end
33
+
34
+ def self.base_cmd(tool, args)
35
+ [
36
+ "wine",
37
+ "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/#{tool}.exe"
38
+ ] + args
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+
4
+ attr_accessor :json, :filename
5
+
6
+ def self.from(filename)
7
+ file = File.open filename
8
+ profile = RSGuitarTech::EncryptedSteamFile.read(file)
9
+ self.new(profile.uncompressed_json, filename)
10
+ end
11
+
12
+ def initialize(json, filename)
13
+ @json = json
14
+ @filename = filename
15
+ end
16
+
17
+ def options
18
+ json["Options"]
19
+ end
20
+
21
+ def statistics
22
+ @statistics ||= Statistics.new(json["Stats"])
23
+ end
24
+
25
+ def song_statistics
26
+ @song_statistics ||= SongCollection.new(Statistics, json["Stats"]["Songs"])
27
+ end
28
+
29
+ def score_attack
30
+ @score_attack ||= SongCollection.new(ScoreAttack, json["SongsSA"])
31
+ end
32
+
33
+ def learn_a_song
34
+ @learn_a_song ||= SongCollection.new(LearnASong, json["Songs"])
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+ module LearnASong
4
+
5
+ DynamicDifficulty = Struct.new(:avg, :phrases, :level_up) do
6
+ # Avg = one of [0.0, 1.0, 2.0, 3.0, 4.0]
7
+ # Phrases = a whole big thing
8
+ # LevelUp = array of tiny floats
9
+ end
10
+
11
+ PlaynextStats = Struct.new(:timestamp, :phrase_iterations, :chords, :articulations, :phrases) do
12
+ # TimeStamp = duh
13
+ # PhraseIterations
14
+ # Chords
15
+ # Articulations
16
+ # Phrases
17
+ end
18
+
19
+ class Song
20
+ attr_accessor :id, :timestamp, :dynamic_difficulty, :play_next_stats
21
+
22
+ def self.from(key, json)
23
+ self.new(
24
+ id: key,
25
+ timestamp: json["TimeStamp"],
26
+ dynamic_difficulty: json["DynamicDifficulty"],
27
+ play_next_stats: json["PlaynextStats"]
28
+ )
29
+ end
30
+
31
+ def initialize(id:, timestamp:, dynamic_difficulty:, play_next_stats:)
32
+ @id = id
33
+ @timestamp = timestamp
34
+ @dynamic_difficulty = dynamic_difficulty
35
+ @play_next_stats = play_next_stats
36
+ end
37
+
38
+ def ==(other)
39
+ return false if other == nil
40
+
41
+ id == other.id
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+ module ScoreAttack
4
+
5
+ PICKS = {
6
+ 2.0 => "Bronze",
7
+ 3.0 => "Silver",
8
+ 4.0 => "Gold",
9
+ 5.0 => "Platinum",
10
+ }
11
+
12
+ DIFFICULTIES = %w{Easy Medium Hard Master}
13
+
14
+ class Song
15
+ attr_accessor :id, :timestamp, :play_count, :picks, :high_scores
16
+
17
+ def self.from(key, json)
18
+ self.new(
19
+ id: key,
20
+ timestamp: json["TimeStamp"],
21
+ play_count: json["PlayCount"],
22
+ picks: json["Badges"],
23
+ high_scores: json["HighScores"],
24
+ )
25
+ end
26
+
27
+ def initialize(id:, timestamp:, play_count:, picks:, high_scores:)
28
+ @id, @timestamp, @play_count, @picks, @high_scores = id, timestamp, play_count, picks, high_scores
29
+ end
30
+
31
+ def ==(other)
32
+ return false if other == nil
33
+ id == other.id
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+ class SongCollection
4
+ attr_accessor :collection
5
+
6
+ delegate :values, :[], to: :collection
7
+
8
+ def initialize(klass, json)
9
+ @collection = json.inject({}) do |memo, (key, value)|
10
+ memo[key] = klass::Song.from(key, value)
11
+ memo
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+ class Statistics
4
+
5
+ attr_accessor :json
6
+
7
+ def initialize(json)
8
+ @json = json
9
+ end
10
+
11
+ # Timing Section
12
+ def session_seconds
13
+ json['SessionTime']
14
+ end
15
+
16
+ def las_seconds
17
+ json['TimePlayed']
18
+ end
19
+
20
+ def games_seconds
21
+ json['GuitarcadePlayedTime'].map(&:values).flatten.inject(&:+)
22
+ end
23
+
24
+ def lesson_second
25
+ json['TimePlayedAndLesson'] - las_seconds
26
+ end
27
+
28
+ def rs_seconds
29
+ session_seconds + las_seconds + games_seconds + lesson_second
30
+ end
31
+
32
+ # / Timing Section
33
+
34
+ def missions_completed
35
+ json['TotalNumMissionCompletions'].to_i
36
+ end
37
+
38
+ def session_count
39
+ json['SessionCnt'].to_i
40
+ end
41
+
42
+ def songs_played_count
43
+ json['SongsPlayedCount'].to_i
44
+ end
45
+
46
+ def songs_mastered_count
47
+ json['NumSongsMastered'].to_i
48
+ end
49
+
50
+ def dlc_played_count
51
+ json['DLCPlayedCount'].to_i
52
+ end
53
+
54
+ def songs_lead_played_count
55
+ json['SongsLeadPlayedCount'].to_i
56
+ end
57
+
58
+ def songs_rhythm_played_count
59
+ json['SongsRhythmPlayedCount'].to_i
60
+ end
61
+
62
+ def songs_bass_played_count
63
+ json['SongsBassPlayedCount'].to_i
64
+ end
65
+
66
+ def session_mission_time
67
+ json['SessionMissionTime']
68
+ end
69
+
70
+ def longest_streak
71
+ json['Streak'].to_i
72
+ end
73
+
74
+ def sa_songs_played_hard
75
+ json['SASongsPlayedHardAndMore'].to_i
76
+ end
77
+
78
+ def sa_songs_played_master
79
+ json['SASongsPlayedMaster'].to_i
80
+ end
81
+
82
+ def sa_songs_cleared_hard
83
+ json['SANumSongsClearedHard'].to_i
84
+ end
85
+
86
+ def sa_songs_cleared_master
87
+ json['SANumSongsClearedMaster'].to_i
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,58 @@
1
+ module RSGuitarTech
2
+ class SavedGame
3
+ class Statistics
4
+
5
+ class Song
6
+ ATTRIBUTES = *%i{
7
+ id date_sa sa_platinum_point_awarded mastery_last sa_play_count played_count accuracy_global date_las accuracy_chords
8
+ mastery_previous mastery_peak mastery_previous_peak streak sa_win_count sa_general_point_awarded articulation_accuracy
9
+ chords_accuracies sa_fail_count score_attack_play_count
10
+ }
11
+
12
+ attr_accessor *ATTRIBUTES
13
+
14
+ def self.from(key, json)
15
+ sa_play_count = json["SAPlayCount"].map(&:values).flatten rescue []
16
+ self.new(
17
+ id: key,
18
+ date_sa: json["DateSA"],
19
+ sa_platinum_point_awarded: json["SAPlatinumPointAwarded"],
20
+ mastery_last: json["MasteryLast"],
21
+ sa_play_count: sa_play_count,
22
+ played_count: json["PlayedCount"],
23
+ accuracy_global: json["AccuracyGlobal"],
24
+ date_las: json["DateLAS"],
25
+ accuracy_chords: json["AccuracyChords"],
26
+ mastery_previous: json["MasteryPrevious"],
27
+ mastery_peak: json["MasteryPeak"],
28
+ mastery_previous_peak: json["MasteryPreviousPeak"],
29
+ streak: json["Streak"],
30
+ sa_win_count: json["SAWinCount"],
31
+ sa_general_point_awarded: json["SAGeneralPointAwarded"],
32
+ articulation_accuracy: json["ArticulationAccuracy"],
33
+ chords_accuracies: json["ChordsAccuracies"],
34
+ sa_fail_count: json["SAFailCount"],
35
+ score_attack_play_count: json["ScoreAttack_PlayCount"]
36
+ )
37
+ end
38
+
39
+ def self.nil_song
40
+ self.new(
41
+ ATTRIBUTES.inject({}) { |memo, attr| memo[attr] = nil; memo }
42
+ )
43
+ end
44
+
45
+ def initialize(args)
46
+ args.each do |k, v|
47
+ self.send("#{k}=", v)
48
+ end
49
+ end
50
+
51
+ def ==(other)
52
+ return false if other == nil
53
+ id == other.id
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/rsgt/steam.rb ADDED
@@ -0,0 +1,45 @@
1
+ module RSGuitarTech
2
+ class Steam
3
+
4
+ ROCKSMITH2014_APP_ID = 221680
5
+
6
+ def save_file
7
+ "#{base_path}#{save_filename}"
8
+ end
9
+
10
+ def save_filename
11
+ "#{unique_id}_PRFLDB"
12
+ end
13
+
14
+ def local_profiles
15
+ "#{base_path}LocalProfiles.json"
16
+ end
17
+
18
+ # Uplay junk
19
+ def crd
20
+ "#{base_path}crd"
21
+ end
22
+
23
+ def unique_id
24
+ @unique_id ||= begin
25
+ file = File.open local_profiles
26
+ data = RSGuitarTech::EncryptedSteamFile.read(file)
27
+ data.uncompressed_json['Profiles'].first['UniqueID']
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def base_path
34
+ "#{steam_path}#{steam_id}/#{ROCKSMITH2014_APP_ID}/remote/"
35
+ end
36
+
37
+ def steam_path
38
+ "/Users/#{`whoami`.strip}/Library/Application\ Support/Steam/userdata/"
39
+ end
40
+
41
+ def steam_id
42
+ Dir["#{steam_path}/*"].first.match(/\/(\d+)$/)[1]
43
+ end
44
+ end
45
+ end