mod_organizer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.md +31 -0
- data/README.md +68 -0
- data/lib/default_categories.dat +57 -0
- data/lib/mod_organizer/download.rb +76 -0
- data/lib/mod_organizer/mod.rb +104 -0
- data/lib/mod_organizer/source.rb +55 -0
- data/lib/mod_organizer/utils.rb +23 -0
- data/lib/mod_organizer/version.rb +5 -0
- data/lib/mod_organizer.rb +148 -0
- data/spec/mod_organizer_test/helpers.rb +115 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/download_spec.rb +55 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/mod_spec.rb +184 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/source_spec.rb +71 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer_spec.rb +292 -0
- data/spec/mod_organizer_test/scenarios/rubocop_spec.rb +31 -0
- data/spec/spec_helper.rb +100 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d69da7c062ed92dec29a95678101c8dee6b251c32232a2168e1d945f02cb709
|
4
|
+
data.tar.gz: 7231f3afddc4126af0210a2c9ac8f95dc3fd520bd803e753b5a30b46e8fadd4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a141e18fcba85d165e87a1c878503cfe7cbb7023feca45cba1afb9dec333fa70bf2d2f1452a1d06c9827ea557af2bcbf9f49970da15713e029fa481015a43039
|
7
|
+
data.tar.gz: f9c7e766e18fe3e652772b0eb617e2dccbdfd8ecbd0d6859b78c4c7e00400fb3b810b074d96aa6bbb295cab246ae82dec5b812bcea7b370e3bafdf3e7bf31f0d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# [v0.0.1](https://github.com/Muriel-Salvan/mod_organizer/compare/...v0.0.1) (2023-01-07 18:10:56)
|
2
|
+
|
3
|
+
### Patches
|
4
|
+
|
5
|
+
* [Added releaserc](https://github.com/Muriel-Salvan/mod_organizer/commit/4d802611296415ec0b5e4141d41874590664873c)
|
6
|
+
* [Plugins extension are case sensitive](https://github.com/Muriel-Salvan/mod_organizer/commit/1448f7cd127b9c10878cc9b7472617c60c65a2b2)
|
7
|
+
|
8
|
+
# 0.0.1
|
9
|
+
|
10
|
+
* Initial version
|
11
|
+
* Get mods list with plugin details and sources
|
12
|
+
* Get downloads information with NexusMods details
|
data/LICENSE.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
The license stated herein is a copy of the BSD License (modified on July 1999).
|
3
|
+
The AUTHOR mentionned below refers to the list of people involved in the
|
4
|
+
creation and modification of any file included in the delivered package.
|
5
|
+
This list is found in the file named AUTHORS.
|
6
|
+
The AUTHORS and LICENSE files have to be included in any release of software
|
7
|
+
embedding source code of this package, or using it as a derivative software.
|
8
|
+
|
9
|
+
Copyright (c) 2019 - 2023 Muriel Salvan (muriel@x-aeon.com)
|
10
|
+
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
13
|
+
|
14
|
+
1. Redistributions of source code must retain the above copyright notice,
|
15
|
+
this list of conditions and the following disclaimer.
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
18
|
+
and/or other materials provided with the distribution.
|
19
|
+
3. The name of the author may not be used to endorse or promote products
|
20
|
+
derived from this software without specific prior written permission.
|
21
|
+
|
22
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
23
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
24
|
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
25
|
+
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
26
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
27
|
+
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
28
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
29
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
30
|
+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
31
|
+
OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# Mod Organizer
|
2
|
+
|
3
|
+
Simple Ruby API letting you handle an instance of [Mod Organizer](https://www.nexusmods.com/skyrimspecialedition/mods/6194).
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
Via gem
|
8
|
+
|
9
|
+
``` bash
|
10
|
+
$ gem install mod_organizer
|
11
|
+
```
|
12
|
+
|
13
|
+
Via a Gemfile
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
$ gem 'mod_organizer'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
require 'mod_organizer'
|
23
|
+
|
24
|
+
mod_organizer = ModOrganizer.new('C:/Program Files/Mod Organizer')
|
25
|
+
mod_organizer.mod_names.each do |mod_name|
|
26
|
+
puts "Mod #{mod_name} has #{mod_organizer.mod(mod_name:).plugins.size} plugins"
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
In case your ModOrganizer instance is not installed as portable, then you have to specify the instance name:
|
31
|
+
``` ruby
|
32
|
+
mod_organizer = ModOrganizer.new('C:/Program Files/Mod Organizer', instance_name: 'MyInstance')
|
33
|
+
```
|
34
|
+
|
35
|
+
## Change log
|
36
|
+
|
37
|
+
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
|
38
|
+
|
39
|
+
## Testing
|
40
|
+
|
41
|
+
Automated tests are done using rspec.
|
42
|
+
|
43
|
+
Do execute them, first install development dependencies:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
bundle install
|
47
|
+
```
|
48
|
+
|
49
|
+
Then execute rspec
|
50
|
+
|
51
|
+
```bash
|
52
|
+
bundle exec rspec
|
53
|
+
```
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
Any contribution is welcome:
|
58
|
+
* Fork the github project and create pull requests.
|
59
|
+
* Report bugs by creating tickets.
|
60
|
+
* Suggest improvements and new features by creating tickets.
|
61
|
+
|
62
|
+
## Credits
|
63
|
+
|
64
|
+
- [Muriel Salvan][link-author]
|
65
|
+
|
66
|
+
## License
|
67
|
+
|
68
|
+
The BSD License. Please see [License File](LICENSE.md) for more information.
|
@@ -0,0 +1,57 @@
|
|
1
|
+
1|Animations|4,51|0
|
2
|
+
52|Poses|29|1
|
3
|
+
2|Armour|5,54|0
|
4
|
+
53|Power Armor|53|2
|
5
|
+
3|Audio|33,35,106|0
|
6
|
+
38|Music|34,61|0
|
7
|
+
39|Voice|36,107|0
|
8
|
+
5|Clothing|9,60|0
|
9
|
+
41|Jewelry|102|5
|
10
|
+
42|Backpacks|49|5
|
11
|
+
6|Collectables|10,92|0
|
12
|
+
28|Companions|11,66,96|0
|
13
|
+
7|Creatures, Mounts, & Vehicles|12,65,83,101|0
|
14
|
+
8|Factions|16,25|0
|
15
|
+
9|Gameplay|15,24|0
|
16
|
+
27|Combat|77|9
|
17
|
+
43|Crafting|50,100|9
|
18
|
+
48|Overhauls|24,79|9
|
19
|
+
49|Perks|27|9
|
20
|
+
54|Radio|31|9
|
21
|
+
55|Shouts|104|9
|
22
|
+
22|Skills & Levelling|46,73|9
|
23
|
+
58|Weather & Lighting|56|9
|
24
|
+
44|Equipment|44|43
|
25
|
+
45|Home/Settlement|45|43
|
26
|
+
10|Body, Face, & Hair|17,26|0
|
27
|
+
39|Tattoos|57|10
|
28
|
+
40|Character Presets|58|0
|
29
|
+
11|Items|27,85|0
|
30
|
+
32|Mercantile|23,69|0
|
31
|
+
37|Ammo|3|11
|
32
|
+
19|Weapons|41,55|11
|
33
|
+
36|Weapon & Armour Sets|42|11
|
34
|
+
23|Player Homes|28,67|0
|
35
|
+
25|Castles & Mansions|68|23
|
36
|
+
51|Settlements|48|23
|
37
|
+
12|Locations|20,21,22,30,47,70,88,89,90,91|0
|
38
|
+
4|Cities|53|12
|
39
|
+
31|Landscape Changes|58|0
|
40
|
+
29|Environment|14,74|0
|
41
|
+
30|Immersion|51,78|0
|
42
|
+
20|Magic|75,93,94|0
|
43
|
+
21|Models & Textures|19,29|0
|
44
|
+
33|Modders resources|18,82|0
|
45
|
+
13|NPCs|22,33,99|0
|
46
|
+
24|Bugfixes|6,95|0
|
47
|
+
14|Patches|25,84|24
|
48
|
+
35|Utilities|38,39|0
|
49
|
+
26|Cheats|8|0
|
50
|
+
15|Quests|30,35|0
|
51
|
+
16|Races & Classes|34|0
|
52
|
+
34|Stealth|76|0
|
53
|
+
17|UI|37,42|0
|
54
|
+
18|Visuals|40,62|0
|
55
|
+
50|Pip-Boy|52|18
|
56
|
+
46|Shader Presets|13,97,105|0
|
57
|
+
47|Miscellaneous|2,28|0
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'memoist'
|
2
|
+
|
3
|
+
class ModOrganizer
|
4
|
+
|
5
|
+
# Object storing information about a downloaded file and giving a lazy API on it to save resources
|
6
|
+
class Download
|
7
|
+
|
8
|
+
extend Memoist
|
9
|
+
|
10
|
+
# Constructor
|
11
|
+
#
|
12
|
+
# Parameters::
|
13
|
+
# * *mod_organizer* (ModOrganizer): The Mod Organizer instance this mod has been instantiated for
|
14
|
+
# * *file_name* (String): The file name for this download
|
15
|
+
def initialize(mod_organizer, file_name)
|
16
|
+
@mod_organizer = mod_organizer
|
17
|
+
@file_name = file_name
|
18
|
+
end
|
19
|
+
|
20
|
+
# Full downloaded file path
|
21
|
+
#
|
22
|
+
# Result::
|
23
|
+
# * String or nil: Full downloaded file path, or nil if does not exist
|
24
|
+
def downloaded_file_path
|
25
|
+
full_path = "#{@mod_organizer.downloads_dir}/#{@file_name}"
|
26
|
+
File.exist?(full_path) ? full_path : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Download date of the source
|
30
|
+
#
|
31
|
+
# Result::
|
32
|
+
# * Time or nil: Download date of this source, or nil if no file
|
33
|
+
def downloaded_date
|
34
|
+
file_path = downloaded_file_path
|
35
|
+
file_path ? File.mtime(file_path).utc : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# NexusMods file name
|
39
|
+
#
|
40
|
+
# Result::
|
41
|
+
# * String:: Original file name from NexusMods
|
42
|
+
def nexus_file_name
|
43
|
+
meta_ini['General']['name']
|
44
|
+
end
|
45
|
+
|
46
|
+
# NexusMods mod ID
|
47
|
+
#
|
48
|
+
# Result::
|
49
|
+
# * Integer:: Mod ID from NexusMods
|
50
|
+
def nexus_mod_id
|
51
|
+
meta_ini['General']['modID']
|
52
|
+
end
|
53
|
+
|
54
|
+
# NexusMods file ID
|
55
|
+
#
|
56
|
+
# Result::
|
57
|
+
# * Integer:: File ID from NexusMods
|
58
|
+
def nexus_file_id
|
59
|
+
meta_ini['General']['fileID']
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Return the mod's meta ini file.
|
65
|
+
# Cache it for performance.
|
66
|
+
#
|
67
|
+
# Result::
|
68
|
+
# * Hash: The mod's meta ini content
|
69
|
+
def meta_ini
|
70
|
+
IniFile.load("#{@mod_organizer.downloads_dir}/#{@file_name}.meta")
|
71
|
+
end
|
72
|
+
memoize :meta_ini
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
require 'memoist'
|
3
|
+
require 'mod_organizer/source'
|
4
|
+
require 'mod_organizer/utils'
|
5
|
+
|
6
|
+
class ModOrganizer
|
7
|
+
|
8
|
+
# Object storing information about a mod a giving a lazy API on it to save resources (API calls, IO reading, files parsing, esp/bsa exploration...)
|
9
|
+
# A mod is an entry in ModOrganizer list.
|
10
|
+
class Mod
|
11
|
+
|
12
|
+
extend Memoist
|
13
|
+
|
14
|
+
# String: Mod's name
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# Constructor
|
18
|
+
#
|
19
|
+
# Parameters::
|
20
|
+
# * *mod_organizer* (ModOrganizer): The Mod Organizer instance this mod has been instantiated for
|
21
|
+
# * *mod_path* (String): Directory containing the mod information
|
22
|
+
def initialize(mod_organizer, mod_path)
|
23
|
+
@mod_organizer = mod_organizer
|
24
|
+
@path = mod_path
|
25
|
+
@name = File.basename(@path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Is this mod enabled in Mod Organizer?
|
29
|
+
#
|
30
|
+
# Result::
|
31
|
+
# * Boolean: Is this mod enabled in Mod Organizer?
|
32
|
+
def enabled?
|
33
|
+
@mod_organizer.enabled_mods.include?(@name)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the list of ModOrganizer categories this mod belongs to
|
37
|
+
#
|
38
|
+
# Result::
|
39
|
+
# * Array<String>: List of MO categories
|
40
|
+
def categories
|
41
|
+
meta_ini['General']['category'].to_s.split(',').map do |cat_id|
|
42
|
+
cat_int = Integer(cat_id)
|
43
|
+
cat_int.positive? ? @mod_organizer.categories[cat_int] : nil
|
44
|
+
end.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the list of plugins this mod is containing.
|
48
|
+
# Cache it.
|
49
|
+
#
|
50
|
+
# Result::
|
51
|
+
# * Array<String>: List of plugins belonging to this mod
|
52
|
+
def plugins
|
53
|
+
(
|
54
|
+
files_glob("#{@path}/*.esm") +
|
55
|
+
files_glob("#{@path}/*.esp") +
|
56
|
+
files_glob("#{@path}/*.esl")
|
57
|
+
).map { |file_name| File.basename(file_name).downcase }
|
58
|
+
end
|
59
|
+
memoize :plugins
|
60
|
+
|
61
|
+
# Return the list of sources this mod belongs to
|
62
|
+
#
|
63
|
+
# Result::
|
64
|
+
# * Array<Source>: List of source information
|
65
|
+
def sources
|
66
|
+
nbr_sources = meta_ini['installedFiles']['size'] || 1
|
67
|
+
nbr_sources.times.map do |install_idx|
|
68
|
+
Source.new(
|
69
|
+
@mod_organizer,
|
70
|
+
nexus_mod_id: meta_ini['installedFiles']["#{install_idx + 1}\\modid"],
|
71
|
+
nexus_file_id: meta_ini['installedFiles']["#{install_idx + 1}\\fileid"],
|
72
|
+
file_name: install_idx == nbr_sources - 1 ? meta_ini['General']['installationFile'] : nil
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
memoize :sources
|
77
|
+
|
78
|
+
# The mod's URL
|
79
|
+
#
|
80
|
+
# Result::
|
81
|
+
# * String or nil: The mod's URL, or nil if none
|
82
|
+
def url
|
83
|
+
ini_url = meta_ini['General']['url']
|
84
|
+
ini_url.nil? || ini_url.empty? ? nil : ini_url
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
include Utils
|
90
|
+
|
91
|
+
# Return the mod's meta ini file.
|
92
|
+
# Cache it for performance.
|
93
|
+
#
|
94
|
+
# Result::
|
95
|
+
# * Hash: The mod's meta ini content (can be empty of no meta)
|
96
|
+
def meta_ini
|
97
|
+
ini_file = "#{@path}/meta.ini"
|
98
|
+
File.exist?(ini_file) ? IniFile.load(ini_file) : IniFile.new(content: '')
|
99
|
+
end
|
100
|
+
memoize :meta_ini
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class ModOrganizer
|
2
|
+
|
3
|
+
# Object storing information about the source of a mod and giving a lazy API on it to save resources
|
4
|
+
# A mod source is something (a file from NexusMods, a manual download...) that has provided content for the mod.
|
5
|
+
class Source
|
6
|
+
|
7
|
+
# Integer or nil: NexusMods mod ID, or nil if none
|
8
|
+
attr_reader :nexus_mod_id
|
9
|
+
|
10
|
+
# Integer or nil: NexusMods file ID, or nil if none
|
11
|
+
attr_reader :nexus_file_id
|
12
|
+
|
13
|
+
# String or nil: File name for this source, or nil if none
|
14
|
+
attr_reader :file_name
|
15
|
+
|
16
|
+
# Constructor
|
17
|
+
#
|
18
|
+
# Parameters::
|
19
|
+
# * *mod_organizer* (ModOrganizer): The Mod Organizer instance this mod has been instantiated for
|
20
|
+
# * *nexus_mod_id* (Integer): Corresponding Nexus mod id, or 0 or nil if none
|
21
|
+
# * *nexus_file_id* (Integer): Corresponding Nexus mod file id, or 0 or nil if none
|
22
|
+
# * *file_name* (String): File name that provided content to this mod, or nil if none
|
23
|
+
def initialize(
|
24
|
+
mod_organizer,
|
25
|
+
nexus_mod_id:,
|
26
|
+
nexus_file_id:,
|
27
|
+
file_name:
|
28
|
+
)
|
29
|
+
@mod_organizer = mod_organizer
|
30
|
+
@nexus_mod_id = nexus_mod_id.nil? || nexus_mod_id.zero? ? nil : nexus_mod_id
|
31
|
+
@nexus_file_id = nexus_file_id.nil? || nexus_file_id.zero? ? nil : nexus_file_id
|
32
|
+
@file_name = file_name
|
33
|
+
end
|
34
|
+
|
35
|
+
# The type of source
|
36
|
+
#
|
37
|
+
# Result::
|
38
|
+
# * Symbol: The source's type. Can be:
|
39
|
+
# * nexus_mods: Content downloaded from NexusMods
|
40
|
+
# * unknown: Unknown source
|
41
|
+
def type
|
42
|
+
@nexus_mod_id ? :nexus_mods : :unknown
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the download info corresponding to this source, or nil if none.
|
46
|
+
#
|
47
|
+
# Result::
|
48
|
+
# * Download or nil: Download info, or nil if none
|
49
|
+
def download
|
50
|
+
@file_name ? @mod_organizer.download(file_name: @file_name) : nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class ModOrganizer
|
2
|
+
|
3
|
+
# Module giving some helpers to various ModOrganizer classes
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# Return all files matching a glob.
|
7
|
+
# Handle special characters correctly.
|
8
|
+
# Don't return . and ..
|
9
|
+
#
|
10
|
+
# Parameters::
|
11
|
+
# * *glob* (String): The glob
|
12
|
+
# Result::
|
13
|
+
# * Array<String>: The list of files matching the glob
|
14
|
+
def files_glob(glob)
|
15
|
+
Dir.glob(glob.gsub('[', '\\[').gsub(']', '\\]'), File::FNM_DOTMATCH).select do |file|
|
16
|
+
basename = File.basename(file)
|
17
|
+
basename != '.' && basename != '..'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'json'
|
4
|
+
require 'logger'
|
5
|
+
require 'memoist'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'inifile'
|
8
|
+
require 'mod_organizer/download'
|
9
|
+
require 'mod_organizer/mod'
|
10
|
+
require 'mod_organizer/utils'
|
11
|
+
|
12
|
+
# Handle a ModOrganizer installation: mods, esps, load order.
|
13
|
+
# No concept of Merges.
|
14
|
+
# No concept of what is actually present in the game directory (except already installed masters/plugins and load order).
|
15
|
+
class ModOrganizer
|
16
|
+
|
17
|
+
extend Memoist
|
18
|
+
|
19
|
+
# String: The game path
|
20
|
+
attr_reader :game_path
|
21
|
+
|
22
|
+
# String: The downloads dir
|
23
|
+
attr_reader :downloads_dir
|
24
|
+
|
25
|
+
# Constructor
|
26
|
+
#
|
27
|
+
# Parameters::
|
28
|
+
# * *mo_dir* (String): Mod Organizer installation directory
|
29
|
+
# * *instance_name* (String or nil): Mod Organizer instance name, or nil in case of a portable installation. [default: nil]
|
30
|
+
# * *logger* (Logger): The logger to be used for log messages [default: Logger.new(STDOUT)]
|
31
|
+
def initialize(
|
32
|
+
mo_dir,
|
33
|
+
instance_name: nil,
|
34
|
+
logger: Logger.new($stdout)
|
35
|
+
)
|
36
|
+
@mo_dir = mo_dir.gsub('\\', '/')
|
37
|
+
@mo_instance_dir = instance_name.nil? ? @mo_dir : "#{ENV.fetch('LOCALAPPDATA')}/ModOrganizer/#{instance_name}"
|
38
|
+
@logger = logger
|
39
|
+
# Read MO ini file
|
40
|
+
mo_ini_file = "#{@mo_instance_dir}/ModOrganizer.ini"
|
41
|
+
raise "Missing ModOrganizer configuration file #{mo_ini_file}" unless File.exist?(mo_ini_file)
|
42
|
+
|
43
|
+
mo_ini = IniFile.load(mo_ini_file)
|
44
|
+
@selected_profile = mo_ini['General']['selected_profile']
|
45
|
+
@selected_profile = ::Regexp.last_match(1) if @selected_profile =~ /^@ByteArray\((.+)\)$/
|
46
|
+
@game_path = mo_ini['General']['gamePath'].gsub('\\', '/')
|
47
|
+
@game_path = ::Regexp.last_match(1) if @game_path =~ /^@ByteArray\((.+)\)$/
|
48
|
+
@profiles_dir = (mo_ini['Settings']['profiles_directory'] || "#{@mo_instance_dir}/profiles").gsub('\\', '/')
|
49
|
+
@mods_dir = (mo_ini['Settings']['mod_directory'] || "#{@mo_instance_dir}/mods").gsub('\\', '/')
|
50
|
+
@overwrite_dir = (mo_ini['Settings']['overwrite_directory'] || "#{@mo_instance_dir}/overwrite").gsub('\\', '/')
|
51
|
+
@downloads_dir = (mo_ini['Settings']['download_directory'] || "#{@mo_instance_dir}/downloads").gsub('\\', '/')
|
52
|
+
@logger.debug "Selected profile: #{@selected_profile}"
|
53
|
+
@logger.debug "Mods directory: #{@mods_dir}"
|
54
|
+
@logger.debug "Downloads directory: #{@downloads_dir}"
|
55
|
+
@logger.debug "Game path: #{@game_path}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Run an instance of ModOrganizer
|
59
|
+
def run
|
60
|
+
Dir.chdir(@mo_dir) do
|
61
|
+
system 'ModOrganizer.exe'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the list of mod names
|
66
|
+
#
|
67
|
+
# Result::
|
68
|
+
# * Array<String>: List of mods
|
69
|
+
def mod_names
|
70
|
+
files_glob("#{@mods_dir}/*").map { |mod_dir| File.directory?(mod_dir) ? File.basename(mod_dir) : nil }.compact
|
71
|
+
end
|
72
|
+
memoize :mod_names
|
73
|
+
|
74
|
+
# Retrieve a mod
|
75
|
+
#
|
76
|
+
# Parameters::
|
77
|
+
# * *name* (String): The mod name
|
78
|
+
# Result::
|
79
|
+
# * Mod or nil: The mod, or nil if the mod is unknown
|
80
|
+
def mod(name:)
|
81
|
+
mod_dir = "#{@mods_dir}/#{name}"
|
82
|
+
File.exist?(mod_dir) ? Mod.new(self, mod_dir) : nil
|
83
|
+
end
|
84
|
+
memoize :mod
|
85
|
+
|
86
|
+
# Get the ordered MO mods list, sorted from the first being loaded to the last (so opposite from the internal MO file)
|
87
|
+
#
|
88
|
+
# Result::
|
89
|
+
# * Array<String>: Sorted list of mod names
|
90
|
+
def mods_list
|
91
|
+
modlist.map { |mod_name, _enabled| mod_name }
|
92
|
+
end
|
93
|
+
memoize :mods_list
|
94
|
+
|
95
|
+
# Return the list of enabled mods
|
96
|
+
#
|
97
|
+
# Result::
|
98
|
+
# * Array<String>: Enabled mods
|
99
|
+
def enabled_mods
|
100
|
+
cached_enabled_mods = []
|
101
|
+
modlist.each do |(mod_name, mod_enabled)|
|
102
|
+
cached_enabled_mods << mod_name if mod_enabled
|
103
|
+
end
|
104
|
+
cached_enabled_mods
|
105
|
+
end
|
106
|
+
memoize :enabled_mods
|
107
|
+
|
108
|
+
# Get the categories
|
109
|
+
#
|
110
|
+
# Result::
|
111
|
+
# * Hash<Integer, String>: For each category ID, the corresponding category name
|
112
|
+
def categories
|
113
|
+
categories_file = "#{@mo_dir}/categories.dat"
|
114
|
+
categories_file = "#{__dir__}/default_categories.dat" unless File.exist?(categories_file)
|
115
|
+
CSV.read(categories_file, col_sep: '|').to_h { |cat_id, title, _nexus_ids, _parent_id| [cat_id.to_i, title] }
|
116
|
+
end
|
117
|
+
memoize :categories
|
118
|
+
|
119
|
+
# Return a downloaded info of file if it exists
|
120
|
+
#
|
121
|
+
# Parameters::
|
122
|
+
# * *file_name* (String): The base file name for which we want the download info
|
123
|
+
# Result::
|
124
|
+
# * Download: The downloaded information
|
125
|
+
def download(file_name:)
|
126
|
+
downloaded_file = "#{@downloads_dir}/#{file_name}"
|
127
|
+
File.exist?(downloaded_file) ? Download.new(self, file_name) : nil
|
128
|
+
end
|
129
|
+
memoize :download
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
include Utils
|
134
|
+
|
135
|
+
# Get the ordered MO mods list, sorted from the first being loaded to the last (so opposite from the internal MO file)
|
136
|
+
#
|
137
|
+
# Result::
|
138
|
+
# * Array<[String, Boolean]>: Sorted list of mod names with their enabled flag
|
139
|
+
def modlist
|
140
|
+
cached_mods_list = []
|
141
|
+
File.read("#{@profiles_dir}/#{@selected_profile}/modlist.txt").split("\n").each do |line|
|
142
|
+
cached_mods_list << [::Regexp.last_match(2), ::Regexp.last_match(1) == '+'] if line =~ /^([+-])(.+)$/
|
143
|
+
end
|
144
|
+
cached_mods_list.reverse
|
145
|
+
end
|
146
|
+
memoize :modlist
|
147
|
+
|
148
|
+
end
|