rsgt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/README.markdown +139 -0
- data/Rakefile +10 -0
- data/bin/rsgt +5 -0
- data/lib/rsgt.rb +42 -0
- data/lib/rsgt/audio_extractor.rb +25 -0
- data/lib/rsgt/cli.rb +89 -0
- data/lib/rsgt/command_runner.rb +41 -0
- data/lib/rsgt/encrypted_steam_file.rb +44 -0
- data/lib/rsgt/multipacker.rb +114 -0
- data/lib/rsgt/multipacker/config_processor.rb +44 -0
- data/lib/rsgt/repacker.rb +37 -0
- data/lib/rsgt/rs_custom_song_toolkit.rb +41 -0
- data/lib/rsgt/saved_game.rb +37 -0
- data/lib/rsgt/saved_game/learn_a_song/song.rb +46 -0
- data/lib/rsgt/saved_game/score_attack/song.rb +38 -0
- data/lib/rsgt/saved_game/song_collection.rb +16 -0
- data/lib/rsgt/saved_game/statistics.rb +91 -0
- data/lib/rsgt/saved_game/statistics/song.rb +58 -0
- data/lib/rsgt/steam.rb +45 -0
- data/lib/rsgt/unpacked_psarc.rb +72 -0
- data/lib/rsgt/version.rb +3 -0
- data/lib/rsgt/vocals_extractor.rb +24 -0
- data/lib/rsgt/wav_shifter.rb +54 -0
- data/lib/rsgt/wwise_converter.rb +99 -0
- data/rsgt.gemspec +31 -0
- data/spec/lib/rsgt/audio_extractor_spec.rb +14 -0
- data/spec/lib/rsgt/command_runner_spec.rb +37 -0
- data/spec/lib/rsgt/repacker_spec.rb +24 -0
- data/spec/lib/rsgt/rs_custom_song_toolkit_spec.rb +47 -0
- data/spec/lib/rsgt/saved_game/learn_a_song/song_spec.rb +15 -0
- data/spec/lib/rsgt/saved_game_spec.rb +49 -0
- data/spec/lib/rsgt/unpacked_psarc_spec.rb +35 -0
- data/spec/lib/rsgt/vocals_extractor_spec.rb +14 -0
- data/spec/lib/rsgt/wav_shifter_spec.rb +30 -0
- data/spec/lib/rsgt/wwise_converter_spec.rb +22 -0
- data/spec/spec_helper.rb +11 -0
- 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
|