feh-bin 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +21 -0
- data/Rakefile +10 -0
- data/bin/feh_bin_lz +36 -0
- data/feh-bin.gemspec +28 -0
- data/lib/feh/bin.rb +76 -0
- data/lib/feh/bin/array_istream.rb +68 -0
- data/lib/feh/bin/array_ostream.rb +62 -0
- data/lib/feh/bin/lz11.rb +229 -0
- data/lib/feh/bin/version.rb +6 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7b1192b0d08d2e5e744017670cdf9150336f200cad3f737296eb8d4b64a2d519
|
|
4
|
+
data.tar.gz: 39922afca016513a3eb2abf02516e4f48e8ac5253757330ca262a5e86611cb22
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4a2ef24ab21d7b3ae3ddd0463ef633f71980e9eca9cdc1b7b8f66f0d0a35ff8f0f07278fe77727a736cca2ce07bf0730e42cd42d5b317964eb7b00fc9ca4dd5f
|
|
7
|
+
data.tar.gz: 1085fcd820135ebcfc7ea0b3a750fcdc3ff564d19f2f8b892d003d211b6cb21a1d607f65b11fcf5c764436a2e27cf1ff3595e48bdffed66a369472b485c4744c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
feh-bin (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
minitest (5.11.3)
|
|
10
|
+
rake (10.5.0)
|
|
11
|
+
|
|
12
|
+
PLATFORMS
|
|
13
|
+
ruby
|
|
14
|
+
|
|
15
|
+
DEPENDENCIES
|
|
16
|
+
bundler (~> 1.16)
|
|
17
|
+
feh-bin!
|
|
18
|
+
minitest (~> 5.0)
|
|
19
|
+
rake (~> 10.0)
|
|
20
|
+
|
|
21
|
+
BUNDLED WITH
|
|
22
|
+
1.16.3
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Quinton Miller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# feh-bin
|
|
2
|
+
|
|
3
|
+
Conversion routines for Fire Emblem Heroes asset files.
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'feh-bin'
|
|
9
|
+
|
|
10
|
+
Dir.glob('assets/Common/SRPGMap/*.bin.lz').each do |fname|
|
|
11
|
+
IO.binwrite(fname.sub(/.lz$/, ''), Feh::Bin.decompress(IO.binread(fname)).pack('c*'))
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Testing
|
|
16
|
+
|
|
17
|
+
A fuzz test is available at `fuzz/fuzz_feh_bin.rb` that tests the compression-decompression round trip using [FuzzBert](https://rubygems.org/gems/fuzzbert).
|
|
18
|
+
|
|
19
|
+
## License
|
|
20
|
+
|
|
21
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/feh_bin_lz
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'feh/bin'
|
|
4
|
+
|
|
5
|
+
DESC = "Fire Emblem Heroes assets converter
|
|
6
|
+
Usage: #{File.basename(__FILE__)} <files>...
|
|
7
|
+
|
|
8
|
+
Files ending in .bin.lz are converted to .bin; all other files are converted
|
|
9
|
+
into .bin.lz."
|
|
10
|
+
|
|
11
|
+
abort DESC if ARGV.empty? || ARGV.include?('-?') || ARGV.include?('--help')
|
|
12
|
+
|
|
13
|
+
ARGV.each do |fname|
|
|
14
|
+
begin
|
|
15
|
+
buf = IO.binread(fname)
|
|
16
|
+
if fname.end_with?('.bin.lz')
|
|
17
|
+
outname = File.expand_path(
|
|
18
|
+
File.basename(fname).sub(/\.lz$/, ''), File.dirname(fname))
|
|
19
|
+
puts "Decompressing #{fname} -> #{outname}..."
|
|
20
|
+
res = Feh::Bin.decompress(buf)
|
|
21
|
+
raise RuntimeError, res.to_s if res.is_a?(Symbol)
|
|
22
|
+
IO.binwrite(outname, res.pack('c*'))
|
|
23
|
+
else
|
|
24
|
+
outname = File.expand_path(
|
|
25
|
+
File.basename(fname, '.*') + '.bin.lz', File.dirname(fname))
|
|
26
|
+
puts "Compressing #{fname} -> #{outname}..."
|
|
27
|
+
res = Feh::Bin.compress(buf)
|
|
28
|
+
raise RuntimeError, res.to_s if res.is_a?(Symbol)
|
|
29
|
+
IO.binwrite(outname, res.pack('c*'))
|
|
30
|
+
end
|
|
31
|
+
rescue RuntimeError => e
|
|
32
|
+
STDERR.puts "Error! (#{e})"
|
|
33
|
+
rescue Errno::ENOENT
|
|
34
|
+
STDERR.puts "#{fname} not found!"
|
|
35
|
+
end
|
|
36
|
+
end
|
data/feh-bin.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "feh/bin/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "feh-bin"
|
|
8
|
+
spec.version = Feh::Bin::VERSION
|
|
9
|
+
spec.authors = ["Quinton Miller"]
|
|
10
|
+
spec.email = ["nicetas.c@gmail.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = "Conversion routines for Fire Emblem Heroes asset files"
|
|
13
|
+
spec.description = "Functions to compress and decompress binary asset files from Fire Emblem Heroes"
|
|
14
|
+
spec.homepage = "https://github.com/HertzDevil/feh-bin"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
+
end
|
|
20
|
+
spec.bindir = "bin"
|
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
spec.extra_rdoc_files = ['README.md']
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
28
|
+
end
|
data/lib/feh/bin.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'feh/bin/version'
|
|
2
|
+
|
|
3
|
+
require 'feh/bin/lz11'
|
|
4
|
+
|
|
5
|
+
module Feh
|
|
6
|
+
module Bin
|
|
7
|
+
# Decompresses a .bin.lz file.
|
|
8
|
+
# @param buf [Array<Integer>, String] content of the .bin.lz file
|
|
9
|
+
# @return [Array<Integer>] content of the decompressed asset data
|
|
10
|
+
# @return [Symbol] error code if the input is not a valid .bin.lz file
|
|
11
|
+
def self.decompress(buf)
|
|
12
|
+
buf = buf.bytes if buf.is_a?(String)
|
|
13
|
+
read_bin_lz(buf)
|
|
14
|
+
# buf = read_bin_lz(buf)
|
|
15
|
+
# return buf if buf.is_a?(Symbol)
|
|
16
|
+
# LZ11.new(buf).decompress
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Compresses data into a .bin.lz file.
|
|
20
|
+
# @param buf [Array<Integer>, String] content of the data to compress
|
|
21
|
+
# @return [Array<Integer>] content of the .bin.lz file
|
|
22
|
+
# @return [Symbol] error code if the input is not a valid data buffer
|
|
23
|
+
def self.compress(buf)
|
|
24
|
+
buf = buf.bytes if buf.is_a?(String)
|
|
25
|
+
buf = LZ11.new(buf).compress
|
|
26
|
+
return buf if buf.is_a?(Symbol)
|
|
27
|
+
write_bin_lz(buf)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Unpacks a Fire Emblem Heroes .bin.lz file.
|
|
31
|
+
# @param buf [Array<Integer>, String] content of the .bin.lz file
|
|
32
|
+
# @return [Array<Integer>] content of the unpacked LZ11 archive
|
|
33
|
+
# @return [Symbol] error code if the input is not a valid .bin.lz file
|
|
34
|
+
def self.read_bin_lz(buf)
|
|
35
|
+
buf = buf.bytes if buf.is_a?(String)
|
|
36
|
+
header = buf.shift(4)
|
|
37
|
+
xorseed = header[1] | (header[2] << 8) | (header[3] << 16)
|
|
38
|
+
if (header.first & 0xFF) == 0x17 && (buf.first & 0xFF) == 0x11
|
|
39
|
+
xorkey = [0x8083 * xorseed].pack('<I').bytes
|
|
40
|
+
(4...buf.size).step(4).each do |i|
|
|
41
|
+
4.times {|j| buf[i + j] ^= xorkey[j]}
|
|
42
|
+
4.times {|j| xorkey[j] ^= buf[i + j]}
|
|
43
|
+
end
|
|
44
|
+
LZ11.new(buf).decompress
|
|
45
|
+
elsif header.first == 0x04 && xorseed == buf.size
|
|
46
|
+
xorkey = [0x8083 * xorseed].pack('<I').bytes
|
|
47
|
+
(0...buf.size).step(4).each do |i|
|
|
48
|
+
4.times {|j| buf[i + j] ^= xorkey[j]}
|
|
49
|
+
4.times {|j| xorkey[j] ^= buf[i + j]}
|
|
50
|
+
end
|
|
51
|
+
buf
|
|
52
|
+
else
|
|
53
|
+
:invalid_archive
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Packs a Fire Emblem Heroes .bin.lz file.
|
|
58
|
+
# @param buf [Array<Integer>, String] content of an LZ11 archive
|
|
59
|
+
# @return [Array<Integer>] content of the packed .bin.lz file
|
|
60
|
+
# @return [Symbol] error code if the input is not a valid LZ11 archive
|
|
61
|
+
def self.write_bin_lz(buf)
|
|
62
|
+
buf = buf.bytes if buf.is_a?(String)
|
|
63
|
+
bytes = LZ11.new(buf).compress
|
|
64
|
+
return bytes if bytes.is_a?(Symbol)
|
|
65
|
+
bytes += [0] * ((-bytes.size) % 4)
|
|
66
|
+
xorseed = buf.size
|
|
67
|
+
header = [xorseed * 0x100 + 0x17].pack('<I').bytes
|
|
68
|
+
xorkey = [0x8083 * xorseed].pack('<I').bytes
|
|
69
|
+
4.times {|j| bytes[4 + j] ^= xorkey[j]}
|
|
70
|
+
(8...bytes.size).step(4).each do |i|
|
|
71
|
+
4.times {|j| bytes[i + j] ^= bytes[i - 4 + j]}
|
|
72
|
+
end
|
|
73
|
+
header + bytes
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module Feh module Bin
|
|
6
|
+
# Single-pass input array stream that reads little-endian integers.
|
|
7
|
+
class ArrayIStream
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
# @!attribute [r] size
|
|
11
|
+
# @return [Integer] the size of the underlying array stream
|
|
12
|
+
def_delegators :@buf, :[], :size
|
|
13
|
+
|
|
14
|
+
# @return [Integer] the number of bytes read so far
|
|
15
|
+
attr_reader :bytes_read
|
|
16
|
+
|
|
17
|
+
# Initializes the stream.
|
|
18
|
+
# @param buffer [Array<Integer>] an array of byte values between 0 and 255
|
|
19
|
+
# @raise [ArgumentError] if *arr* is not a byte array
|
|
20
|
+
def initialize(buffer)
|
|
21
|
+
raise ArgumentError, 'Input is not a byte array' unless
|
|
22
|
+
buffer.is_a?(Array) &&
|
|
23
|
+
buffer.all? {|x| x.is_a?(Integer) && x.between?(0, 255)}
|
|
24
|
+
@buf = buffer
|
|
25
|
+
@bytes_read = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Attempts to read an unsigned 8-bit integer.
|
|
29
|
+
# @return [Integer] the integer read
|
|
30
|
+
# @return [nil] if not enough bytes remaining are present to form an integer
|
|
31
|
+
def u8
|
|
32
|
+
return nil if @bytes_read > @buf.size - 1
|
|
33
|
+
x = @buf[@bytes_read]
|
|
34
|
+
@bytes_read += 1
|
|
35
|
+
x
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Attempts to read an unsigned 16-bit integer.
|
|
39
|
+
# @return [Integer] the integer read
|
|
40
|
+
# @return [nil] if not enough bytes remaining are present to form an integer
|
|
41
|
+
def u16
|
|
42
|
+
return nil if @bytes_read > @buf.size - 2
|
|
43
|
+
x = @buf[@bytes_read]
|
|
44
|
+
x |= @buf[@bytes_read + 1] << 8
|
|
45
|
+
@bytes_read += 2
|
|
46
|
+
x
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Attempts to read an unsigned 32-bit integer.
|
|
50
|
+
# @return [Integer] the integer read
|
|
51
|
+
# @return [nil] if not enough bytes remaining are present to form an integer
|
|
52
|
+
def u32
|
|
53
|
+
return nil if @bytes_read > @buf.size - 4
|
|
54
|
+
x = @buf[@bytes_read]
|
|
55
|
+
x |= @buf[@bytes_read + 1] << 8
|
|
56
|
+
x |= @buf[@bytes_read + 2] << 16
|
|
57
|
+
x |= @buf[@bytes_read + 3] << 24
|
|
58
|
+
@bytes_read += 4
|
|
59
|
+
x
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns the unread bytes of the stream.
|
|
63
|
+
# @return [Array<Integer>] An array of unread bytes.
|
|
64
|
+
def remaining
|
|
65
|
+
@buf[@bytes_read..-1]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module Feh module Bin
|
|
6
|
+
# Single-pass output array stream that writes little-endian integers.
|
|
7
|
+
class ArrayOStream
|
|
8
|
+
|
|
9
|
+
# @return [Array<Integer>] the stream content
|
|
10
|
+
def buf
|
|
11
|
+
@buf.dup
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Integer] the number of bytes written so far
|
|
15
|
+
def bytes_written
|
|
16
|
+
@buf.size
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Initializes the stream.
|
|
20
|
+
def initialize
|
|
21
|
+
@buf = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Writes an unsigned 8-bit integer.
|
|
25
|
+
# @param x [Integer] integer value to write
|
|
26
|
+
# @return [ArrayOStream] self
|
|
27
|
+
def u8(x)
|
|
28
|
+
write [x & 0xFF]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Writes an unsigned 16-bit integer.
|
|
32
|
+
# @param x [Integer] integer value to write
|
|
33
|
+
# @return [ArrayOStream] self
|
|
34
|
+
def u16(x)
|
|
35
|
+
write [x & 0xFF, (x >> 8) & 0xFF]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Writes an unsigned 32-bit integer.
|
|
39
|
+
# @param x [Integer] integer value to write
|
|
40
|
+
# @return [ArrayOStream] self
|
|
41
|
+
def u32(x)
|
|
42
|
+
write [x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF, (x >> 24) & 0xFF]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Writes an array of bytes.
|
|
46
|
+
# @param arr [Array<Integer>] an array of byte values
|
|
47
|
+
# @return [ArrayOStream] self
|
|
48
|
+
# @raise [ArgumentError] if *arr* is not a byte array
|
|
49
|
+
def write(arr)
|
|
50
|
+
raise ArgumentError, 'Input is not a byte array' unless
|
|
51
|
+
arr.is_a?(Array) &&
|
|
52
|
+
arr.all? {|x| x.is_a?(Integer) && x.between?(0, 255)}
|
|
53
|
+
write2(arr)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
def write2(arr)
|
|
58
|
+
@buf += arr
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end end
|
data/lib/feh/bin/lz11.rb
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'feh/bin/array_istream'
|
|
5
|
+
require 'feh/bin/array_ostream'
|
|
6
|
+
|
|
7
|
+
module Feh module Bin
|
|
8
|
+
# Converter for the LZ11 archive format used in Fire Emblem Heroes.
|
|
9
|
+
# Ported from DSDecmp.
|
|
10
|
+
class LZ11
|
|
11
|
+
# @return [ArrayIStream] the input buffer of the converter.
|
|
12
|
+
attr_reader :buf
|
|
13
|
+
|
|
14
|
+
# Initializes the LZ11 converter.
|
|
15
|
+
# @param buffer [Array<Integer>] byte array containing the data to convert
|
|
16
|
+
def initialize(buffer)
|
|
17
|
+
@buf = ArrayIStream.new(buffer)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Decompresses an LZ11 archive.
|
|
21
|
+
# @return [Array<Integer>] byte array representing the decompressed content
|
|
22
|
+
# of the input archive
|
|
23
|
+
# @return [Symbol] error code if the input is not a valid LZ11 archive
|
|
24
|
+
def decompress
|
|
25
|
+
header = buf.u32
|
|
26
|
+
return :invalid_data if (header & 0xFF) != 0x11
|
|
27
|
+
decompressedSize = header >> 8
|
|
28
|
+
decompressedSize = buf.u32 if decompressedSize == 0
|
|
29
|
+
|
|
30
|
+
bufferLength = 0x1000
|
|
31
|
+
buffer = Array.new(bufferLength)
|
|
32
|
+
bufferOffset = 0
|
|
33
|
+
|
|
34
|
+
flags = 0
|
|
35
|
+
mask = 1
|
|
36
|
+
|
|
37
|
+
outbuf = []
|
|
38
|
+
until outbuf.size >= decompressedSize
|
|
39
|
+
if mask == 1
|
|
40
|
+
flags = buf.u8
|
|
41
|
+
return :stream_too_short if flags.nil?
|
|
42
|
+
mask = 0x80
|
|
43
|
+
else
|
|
44
|
+
mask >>= 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if (flags & mask) > 0
|
|
48
|
+
byte1 = buf.u8
|
|
49
|
+
return :stream_too_short if byte1.nil?
|
|
50
|
+
|
|
51
|
+
length = byte1 >> 4
|
|
52
|
+
disp = -1
|
|
53
|
+
case length
|
|
54
|
+
when 0
|
|
55
|
+
byte2 = buf.u8
|
|
56
|
+
byte3 = buf.u8
|
|
57
|
+
return :stream_too_short if byte3.nil?
|
|
58
|
+
length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11
|
|
59
|
+
disp = (((byte2 & 0x0F) << 8) | byte3) + 0x1
|
|
60
|
+
when 1
|
|
61
|
+
byte2 = buf.u8
|
|
62
|
+
byte3 = buf.u8
|
|
63
|
+
byte4 = buf.u8
|
|
64
|
+
return :stream_too_short if byte4.nil?
|
|
65
|
+
length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111
|
|
66
|
+
disp = (((byte3 & 0x0F) << 8) | byte4) + 0x1
|
|
67
|
+
else
|
|
68
|
+
byte2 = buf.u8
|
|
69
|
+
return :stream_too_short if byte2.nil?
|
|
70
|
+
length = ((byte1 & 0xF0) >> 4) + 0x1
|
|
71
|
+
disp = (((byte1 & 0x0F) << 8) | byte2) + 0x1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return :invalid_data if disp > outbuf.size
|
|
75
|
+
|
|
76
|
+
bufIdx = bufferOffset + bufferLength - disp
|
|
77
|
+
length.times do
|
|
78
|
+
next_byte = buffer[bufIdx % bufferLength]
|
|
79
|
+
bufIdx += 1
|
|
80
|
+
outbuf << next_byte
|
|
81
|
+
buffer[bufferOffset] = next_byte
|
|
82
|
+
bufferOffset = (bufferOffset + 1) % bufferLength
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
next_byte = buf.u8
|
|
86
|
+
return :stream_too_short if next_byte.nil?
|
|
87
|
+
outbuf << next_byte
|
|
88
|
+
buffer[bufferOffset] = next_byte
|
|
89
|
+
bufferOffset = (bufferOffset + 1) % bufferLength
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
outbuf
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Compresses a byte buffer.
|
|
97
|
+
# This function is not required to produce exactly the same results as
|
|
98
|
+
# existing archives in Fire Emblem Heroes when given the same inputs.
|
|
99
|
+
# @return [Array<Integer>] byte array representing the compressed LZ11
|
|
100
|
+
# archive
|
|
101
|
+
# @return [Symbol] error code if the input is too large or empty
|
|
102
|
+
def compress
|
|
103
|
+
return :input_too_short if buf.size < 2
|
|
104
|
+
return :input_too_large if buf.size > 0xFFFFFF
|
|
105
|
+
|
|
106
|
+
outstream = ArrayOStream.new
|
|
107
|
+
.u8(0x11).u16(buf.size).u8(buf.size >> 16)
|
|
108
|
+
|
|
109
|
+
outbuffer = [8 * 4 + 1] * 33
|
|
110
|
+
outbuffer[0] = 0
|
|
111
|
+
bufferlength = 1
|
|
112
|
+
bufferedBlocks = 0
|
|
113
|
+
readBytes = 0
|
|
114
|
+
while readBytes < buf.size
|
|
115
|
+
if bufferedBlocks == 8
|
|
116
|
+
outstream.write(outbuffer[0, bufferlength])
|
|
117
|
+
outbuffer[0] = 0
|
|
118
|
+
bufferlength = 1
|
|
119
|
+
bufferedBlocks = 0
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
oldLength = [readBytes, 0x1000].min
|
|
123
|
+
disp, length = occurrence_length(readBytes,
|
|
124
|
+
[buf.size - readBytes, 0x10110].min, readBytes - oldLength, oldLength)
|
|
125
|
+
if length < 3
|
|
126
|
+
outbuffer[bufferlength] = buf[readBytes]
|
|
127
|
+
readBytes += 1
|
|
128
|
+
bufferlength += 1
|
|
129
|
+
else
|
|
130
|
+
readBytes += length
|
|
131
|
+
outbuffer[0] |= (1 << (7 - bufferedBlocks)) & 0xFF
|
|
132
|
+
case
|
|
133
|
+
when length > 0x110
|
|
134
|
+
outbuffer[bufferlength] = 0x10
|
|
135
|
+
outbuffer[bufferlength] |= ((length - 0x111) >> 12) & 0x0F
|
|
136
|
+
bufferlength += 1
|
|
137
|
+
outbuffer[bufferlength] = ((length - 0x111) >> 4) & 0xFF
|
|
138
|
+
bufferlength += 1
|
|
139
|
+
outbuffer[bufferlength] = ((length - 0x111) << 4) & 0xF0
|
|
140
|
+
when length > 0x10
|
|
141
|
+
outbuffer[bufferlength] = 0x00
|
|
142
|
+
outbuffer[bufferlength] |= ((length - 0x111) >> 4) & 0x0F
|
|
143
|
+
bufferlength += 1
|
|
144
|
+
outbuffer[bufferlength] = ((length - 0x111) << 4) & 0xF0
|
|
145
|
+
else
|
|
146
|
+
outbuffer[bufferlength] = ((length - 1) << 4) & 0xF0
|
|
147
|
+
end
|
|
148
|
+
outbuffer[bufferlength] |= ((disp - 1) >> 8) & 0x0F
|
|
149
|
+
bufferlength += 1
|
|
150
|
+
outbuffer[bufferlength] = (disp - 1) & 0xFF
|
|
151
|
+
bufferlength += 1
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
bufferedBlocks += 1
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if bufferedBlocks > 0
|
|
158
|
+
outstream.write(outbuffer[0, bufferlength])
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
outstream.buf
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
def occurrence_length(newPtr, newLength, oldPtr, oldLength, minDisp = 1)
|
|
166
|
+
disp = 0
|
|
167
|
+
return [disp, 0] if newLength == 0
|
|
168
|
+
oldRange = buf[oldPtr, newLength + oldLength - minDisp - 1]
|
|
169
|
+
newArray = buf[newPtr, newLength]
|
|
170
|
+
|
|
171
|
+
j = 0
|
|
172
|
+
k = 0
|
|
173
|
+
t = [-1]
|
|
174
|
+
pos = 1
|
|
175
|
+
cnd = 0
|
|
176
|
+
while pos < newArray.size
|
|
177
|
+
if newArray[pos] == newArray[cnd]
|
|
178
|
+
t[pos] = t[cnd]
|
|
179
|
+
pos += 1
|
|
180
|
+
cnd += 1
|
|
181
|
+
else
|
|
182
|
+
t[pos] = cnd
|
|
183
|
+
cnd = t[cnd]
|
|
184
|
+
while cnd >= 0 && newArray[pos] != newArray[cnd]
|
|
185
|
+
cnd = t[cnd]
|
|
186
|
+
end
|
|
187
|
+
pos += 1
|
|
188
|
+
cnd += 1
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
t[pos] = cnd
|
|
192
|
+
|
|
193
|
+
maxLength = 0
|
|
194
|
+
while j < oldRange.size && j - k < oldLength - minDisp
|
|
195
|
+
if newArray[k] == oldRange[j]
|
|
196
|
+
j += 1
|
|
197
|
+
k += 1
|
|
198
|
+
if k > maxLength
|
|
199
|
+
maxLength = k
|
|
200
|
+
disp = oldLength - (j - k)
|
|
201
|
+
break if maxLength == newLength
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
k = t[k]
|
|
205
|
+
if k < 0
|
|
206
|
+
j += 1
|
|
207
|
+
k += 1
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# currentLengths = Array.new(oldLength - minDisp) do |i|
|
|
213
|
+
# oldArray = oldRange[i, newLength]
|
|
214
|
+
# p [oldArray, newArray]
|
|
215
|
+
# oldArray.zip(newArray).take_while {|x, y| x == y}.size
|
|
216
|
+
# end
|
|
217
|
+
# maxLength = 0
|
|
218
|
+
# currentLengths.each_with_index do |currentLength, i|
|
|
219
|
+
# if currentLength > maxLength
|
|
220
|
+
# maxLength = currentLength
|
|
221
|
+
# disp = oldLength - i
|
|
222
|
+
# break if maxLength == newLength
|
|
223
|
+
# end
|
|
224
|
+
# end
|
|
225
|
+
|
|
226
|
+
[disp, maxLength]
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end end
|
metadata
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: feh-bin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Quinton Miller
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-09-29 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.0'
|
|
55
|
+
description: Functions to compress and decompress binary asset files from Fire Emblem
|
|
56
|
+
Heroes
|
|
57
|
+
email:
|
|
58
|
+
- nicetas.c@gmail.com
|
|
59
|
+
executables:
|
|
60
|
+
- feh_bin_lz
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files:
|
|
63
|
+
- README.md
|
|
64
|
+
files:
|
|
65
|
+
- ".gitignore"
|
|
66
|
+
- Gemfile
|
|
67
|
+
- Gemfile.lock
|
|
68
|
+
- LICENSE.txt
|
|
69
|
+
- README.md
|
|
70
|
+
- Rakefile
|
|
71
|
+
- bin/feh_bin_lz
|
|
72
|
+
- feh-bin.gemspec
|
|
73
|
+
- lib/feh/bin.rb
|
|
74
|
+
- lib/feh/bin/array_istream.rb
|
|
75
|
+
- lib/feh/bin/array_ostream.rb
|
|
76
|
+
- lib/feh/bin/lz11.rb
|
|
77
|
+
- lib/feh/bin/version.rb
|
|
78
|
+
homepage: https://github.com/HertzDevil/feh-bin
|
|
79
|
+
licenses:
|
|
80
|
+
- MIT
|
|
81
|
+
metadata: {}
|
|
82
|
+
post_install_message:
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubyforge_project:
|
|
98
|
+
rubygems_version: 2.7.6
|
|
99
|
+
signing_key:
|
|
100
|
+
specification_version: 4
|
|
101
|
+
summary: Conversion routines for Fire Emblem Heroes asset files
|
|
102
|
+
test_files: []
|