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,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