mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-09-20 07:13:52 +00:00
Compare commits
2 Commits
9ad43f61b6
...
969a82a262
| Author | SHA1 | Date | |
|---|---|---|---|
| 969a82a262 | |||
| efc9c2f150 |
144
lib/ico.rb
Normal file
144
lib/ico.rb
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
require "stringio"
|
||||||
|
|
||||||
|
module W3DHubLauncher
|
||||||
|
class ICO
|
||||||
|
PNG_IDENT = "\211PNG\r\n\032\n".force_encoding("ASCII-8BIT").freeze
|
||||||
|
|
||||||
|
IconDirectory = Struct.new(:reserved, :type, :image_count)
|
||||||
|
|
||||||
|
IconDirectoryEntity = Struct.new(
|
||||||
|
:width,
|
||||||
|
:height,
|
||||||
|
:palette_size,
|
||||||
|
:reserved,
|
||||||
|
:color_planes,
|
||||||
|
:bit_depth,
|
||||||
|
:image_size,
|
||||||
|
:image_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(file:)
|
||||||
|
@file = StringIO.new(File.binread(file))
|
||||||
|
|
||||||
|
@images = []
|
||||||
|
|
||||||
|
parse
|
||||||
|
end
|
||||||
|
|
||||||
|
def directory
|
||||||
|
@icon_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def images
|
||||||
|
@images
|
||||||
|
end
|
||||||
|
|
||||||
|
# SEE: https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||||
|
def parse
|
||||||
|
# Parse IconDirectory
|
||||||
|
@icon_directory = IconDirectory.new(
|
||||||
|
read_u16, read_u16, read_u16
|
||||||
|
)
|
||||||
|
|
||||||
|
@icon_directory.image_count.times do
|
||||||
|
@images << IconDirectoryEntity.new(
|
||||||
|
read_u8,
|
||||||
|
read_u8,
|
||||||
|
read_u8,
|
||||||
|
read_u8,
|
||||||
|
read_u16,
|
||||||
|
read_u16,
|
||||||
|
read_u32,
|
||||||
|
read_u32
|
||||||
|
)
|
||||||
|
|
||||||
|
@images.last.width = 256 if @images.last.width == 0
|
||||||
|
@images.last.height = 256 if @images.last.height == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_u8
|
||||||
|
@file.read(1).unpack1("C")
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_u16
|
||||||
|
@file.read(2).unpack1("v")
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_u32
|
||||||
|
@file.read(4).unpack1("V")
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_pngs
|
||||||
|
@images.select do |image|
|
||||||
|
image_png?(image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_png?(image)
|
||||||
|
@file.pos = image.image_offset
|
||||||
|
buf = @file.read(8).unpack1("a*")
|
||||||
|
|
||||||
|
buf == PNG_IDENT
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_bmps
|
||||||
|
@images.select do |image|
|
||||||
|
image_bmp?(image) && image.palette_size.zero? && image.bit_depth == 32
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_bmp?(image)
|
||||||
|
!image_png?(image)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_rgba32_blob(image)
|
||||||
|
@file.pos = image.image_offset
|
||||||
|
buf = StringIO.new(@file.read(image.image_size))
|
||||||
|
|
||||||
|
raise NotImplementedError "Cannot parse png based icons!" unless image_bmp?(image)
|
||||||
|
|
||||||
|
raise NotImplementedError "Cannot parse #{image.bit_depth}" unless image.bit_depth == 32
|
||||||
|
|
||||||
|
blob = "".force_encoding("ASCII-8BIT")
|
||||||
|
|
||||||
|
width = image.width
|
||||||
|
height = image.height - 1
|
||||||
|
|
||||||
|
image.height.times do |y|
|
||||||
|
image.width.times do |x|
|
||||||
|
buf.pos = ((height - y) * width + x + 10) * 4
|
||||||
|
|
||||||
|
blue = buf.read(1)
|
||||||
|
green = buf.read(1)
|
||||||
|
red = buf.read(1)
|
||||||
|
alpha = buf.read(1)
|
||||||
|
|
||||||
|
blob << red
|
||||||
|
blob << green
|
||||||
|
blob << blue
|
||||||
|
blob << alpha
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Gosu::Image.from_blob(image.width, image.height, blob)
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_data(image)
|
||||||
|
@file.pos = image.image_offset
|
||||||
|
StringIO.new(@file.read(image.image_size)).string
|
||||||
|
end
|
||||||
|
|
||||||
|
def save(image, filename)
|
||||||
|
puts filename
|
||||||
|
if image_bmp?(image)
|
||||||
|
puts :gosu_image
|
||||||
|
to_rgba32_blob(image).save(filename)
|
||||||
|
else
|
||||||
|
puts :png
|
||||||
|
File.open(filename, "wb") { |f| f.write(image_data(image)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -71,9 +71,14 @@ module W3DHubLauncher
|
|||||||
def populate_games_list
|
def populate_games_list
|
||||||
@games_list_container.clear do
|
@games_list_container.clear do
|
||||||
@games.each_with_index do |game, i|
|
@games.each_with_index do |game, i|
|
||||||
button(safe_get_image("#{ROOT_PATH}/data/cache/#{game.id}.png"), tag: :"image_icon_#{game.id}", style_class: [:app_icon_button], enabled: @current_app.id != game.id, tip: game.name) do |btn|
|
btn = button(safe_get_image("#{CACHE_PATH}/icon_#{game.id}.png"), tag: :"image_icon_#{game.id}", style_class: [:app_icon_button], enabled: @current_app.id != game.id, tip: game.name) do |btn|
|
||||||
populate_game(game, game&.channels&.first)
|
populate_game(game, game&.channels&.first)
|
||||||
end
|
end
|
||||||
|
remote_image("#{CACHE_PATH}/icon_#{game.id}.ico", url: "https://s3.w3d.cyberarm.dev/games/#{game.id}/#{game.id}.ico", element: btn) do |e, path|
|
||||||
|
Worker::Api.ico_to_png(ico_path: path, png_path: path.sub(".ico", ".png")) do |result|
|
||||||
|
e&.value = safe_get_image(result.data["path"]) if result.okay?
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
puts
|
puts
|
||||||
@@ -87,8 +92,8 @@ module W3DHubLauncher
|
|||||||
|
|
||||||
@game_info_container.clear do
|
@game_info_container.clear do
|
||||||
# logo
|
# logo
|
||||||
img = image safe_get_image("#{ROOT_PATH}/data/cache/logo_#{@current_app.id}.png"), tag: :"image_logo_#{@current_app.id}", width: 1.0, max_height: 124
|
img = image safe_get_image("#{CACHE_PATH}/logo_#{@current_app.id}.png"), tag: :"image_logo_#{@current_app.id}", width: 1.0, max_height: 124
|
||||||
remote_image("#{ROOT_PATH}/data/cache/logo_#{@current_app.id}.png", url: "https://s3.w3d.cyberarm.dev/games/#{@current_app.id}/logo.png", element: img) do |e, path|
|
remote_image("#{CACHE_PATH}/logo_#{@current_app.id}.png", url: "https://s3.w3d.cyberarm.dev/games/#{@current_app.id}/logo.png", element: img) do |e, path|
|
||||||
e&.value = safe_get_image(path)
|
e&.value = safe_get_image(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module W3DHubLauncher
|
|||||||
MemCache[:applications].each do |app|
|
MemCache[:applications].each do |app|
|
||||||
next unless app.game?
|
next unless app.game?
|
||||||
|
|
||||||
button(safe_get_image("#{ROOT_PATH}/data/cache/#{app.id}.png"), style_class: [:app_icon_button], tip: app.name) do |btn|
|
button(safe_get_image("#{CACHE_PATH}/icon_#{app.id}.png"), style_class: [:app_icon_button], tip: app.name) do |btn|
|
||||||
if shift_down?
|
if shift_down?
|
||||||
@games_filter << app.id
|
@games_filter << app.id
|
||||||
@games_filter.uniq!
|
@games_filter.uniq!
|
||||||
@@ -56,7 +56,7 @@ module W3DHubLauncher
|
|||||||
|
|
||||||
widget(width: 1.0, height: 48, padding_top: HALF_PADDING, padding_bottom: HALF_PADDING, margin_bottom: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: server.channel == "release" ? ALPHA_GRAY : 0xaa_c64600, hover: { background_nine_slice_color: 0xff_5e5c64 } , active: { background_nine_slice_color: 0xaa_5e5c64 }) do
|
widget(width: 1.0, height: 48, padding_top: HALF_PADDING, padding_bottom: HALF_PADDING, margin_bottom: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: server.channel == "release" ? ALPHA_GRAY : 0xaa_c64600, hover: { background_nine_slice_color: 0xff_5e5c64 } , active: { background_nine_slice_color: 0xaa_5e5c64 }) do
|
||||||
# app icon container
|
# app icon container
|
||||||
image(safe_get_image("#{ROOT_PATH}/data/cache/#{app.id}.png"), tip: app.name, width: 48, height: 1.0, margin_left: HALF_PADDING)
|
image(safe_get_image("#{CACHE_PATH}/icon_#{app.id}.png"), tip: app.name, width: 48, height: 1.0, margin_left: HALF_PADDING)
|
||||||
|
|
||||||
# server name, region, and times container
|
# server name, region, and times container
|
||||||
stack(fill: true, height: 1.0, margin_left: HALF_PADDING) do
|
stack(fill: true, height: 1.0, margin_left: HALF_PADDING) do
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ module W3DHubLauncher
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
puts "This should never happen!?!?!"
|
||||||
|
|
||||||
rescue Errno::EWOULDBLOCK
|
rescue Errno::EWOULDBLOCK
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -157,6 +160,14 @@ module W3DHubLauncher
|
|||||||
response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_directory(path)
|
||||||
|
pathname = Pathname.new(path)
|
||||||
|
|
||||||
|
unless Dir.exist?(pathname.dirname)
|
||||||
|
FileUtils.mkdir_p(pathname.dirname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# ------------ query / request handlers ------------
|
# ------------ query / request handlers ------------
|
||||||
#
|
#
|
||||||
@@ -194,6 +205,8 @@ module W3DHubLauncher
|
|||||||
headers = query.data["headers"] || DEFAULT_HEADERS
|
headers = query.data["headers"] || DEFAULT_HEADERS
|
||||||
body = query.data["body"]
|
body = query.data["body"]
|
||||||
|
|
||||||
|
create_directory(path)
|
||||||
|
|
||||||
Sync do |task|
|
Sync do |task|
|
||||||
task.with_timeout(DEFAULT_NETWORK_TIMEOUT) do
|
task.with_timeout(DEFAULT_NETWORK_TIMEOUT) do
|
||||||
Async::HTTP::Internet.send(method, url, headers, body) do |response|
|
Async::HTTP::Internet.send(method, url, headers, body) do |response|
|
||||||
@@ -230,6 +243,20 @@ module W3DHubLauncher
|
|||||||
deliver_response(result, query)
|
deliver_response(result, query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ico_to_png(query)
|
||||||
|
result = CyberarmEngine::Result.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
ico = ICO.new(file: query.data["ico_path"])
|
||||||
|
ico.save(ico.images.max_by(&:width), query.data["png_path"])
|
||||||
|
result.data = { path: query.data["png_path"] }
|
||||||
|
rescue => e
|
||||||
|
result.error = e
|
||||||
|
end
|
||||||
|
|
||||||
|
deliver_response(result, query)
|
||||||
|
end
|
||||||
|
|
||||||
def dns_resolution(query)
|
def dns_resolution(query)
|
||||||
result = CyberarmEngine::Result.new
|
result = CyberarmEngine::Result.new
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ module W3DHubLauncher
|
|||||||
Worker::Request.new(:download_url, { url: url, path: path, method: method, headers: headers, body: body }, &block)
|
Worker::Request.new(:download_url, { url: url, path: path, method: method, headers: headers, body: body }, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.ico_to_png(ico_path:, png_path:, &block)
|
||||||
|
Worker::Request.new(:ico_to_png, { ico_path: ico_path, png_path: png_path }, &block)
|
||||||
|
end
|
||||||
|
|
||||||
def self.dns_resolution(&block)
|
def self.dns_resolution(&block)
|
||||||
Worker::Request.new(:dns_resolution, "", &block)
|
Worker::Request.new(:dns_resolution, "", &block)
|
||||||
end
|
end
|
||||||
|
|||||||
287
lib/ww_mix.rb
Normal file
287
lib/ww_mix.rb
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
require "digest"
|
||||||
|
require "stringio"
|
||||||
|
|
||||||
|
module W3DHubLauncher
|
||||||
|
# Reimplementating MIX1 reader/writer with years more
|
||||||
|
# experience working with these formats and having the
|
||||||
|
# advantage of being able to reference the renegade source
|
||||||
|
# code :)
|
||||||
|
class WWMix
|
||||||
|
MIX1_HEADER = 0x3158494D
|
||||||
|
MIX2_HEADER = 0x3258494D
|
||||||
|
|
||||||
|
MixHeader = Struct.new(
|
||||||
|
:mime_type, # int32
|
||||||
|
:file_data_offset, # int32
|
||||||
|
:file_names_offset, # int32
|
||||||
|
:_reserved # int32
|
||||||
|
)
|
||||||
|
|
||||||
|
EntryInfoHeader = Struct.new(
|
||||||
|
:crc32, # uint32
|
||||||
|
:content_offset, # uint32
|
||||||
|
:content_length # uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
class Entry
|
||||||
|
attr_accessor :path, :name, :info, :blob, :is_blob
|
||||||
|
|
||||||
|
def initialize(name:, path:, info:, blob: nil)
|
||||||
|
@name = name
|
||||||
|
@path = path
|
||||||
|
@info = info
|
||||||
|
@blob = blob
|
||||||
|
|
||||||
|
@info.content_length = blob.size if blob?
|
||||||
|
end
|
||||||
|
|
||||||
|
def blob?
|
||||||
|
@blob
|
||||||
|
end
|
||||||
|
|
||||||
|
def calculate_crc32
|
||||||
|
Digest::CRC32.hexdigest(@name.upcase).upcase.to_i(16)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write entry's data to stream.
|
||||||
|
# Caller is responsible for ensuring stream is valid for writing
|
||||||
|
def copy_to(stream)
|
||||||
|
if blob?
|
||||||
|
return false if @blob.size.zero?
|
||||||
|
|
||||||
|
stream.write(blob)
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if read
|
||||||
|
stream.write(@blob)
|
||||||
|
@blob = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def read
|
||||||
|
return false unless File.exist?(@path)
|
||||||
|
return false if File.directory?(@path)
|
||||||
|
return false if File.size(@path) < @info.content_offset + @info.content_length
|
||||||
|
|
||||||
|
@blob = File.binread(@path, @info.content_length, @info.content_offset)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :path, :encrypted, :entries, :error_reason
|
||||||
|
|
||||||
|
def initialize(path:, encrypted: false)
|
||||||
|
@path = path
|
||||||
|
@encrypted = encrypted
|
||||||
|
@entries = []
|
||||||
|
|
||||||
|
@error_reason = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Load entries from MIX file. Entry data is NOT loaded.
|
||||||
|
# @return true on success or false on failure. Check m_error_reason for why.
|
||||||
|
def load
|
||||||
|
unless File.exist?(@path)
|
||||||
|
@error_reason = format("Path does not exist: %s", @path)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.directory?(@path)
|
||||||
|
@error_reason = format("Path is a directory: %s", @path)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
File.open(@path, "rb") do |f|
|
||||||
|
header = MixHeader.new(0, 0, 0, 0)
|
||||||
|
header.mime_type = read_i32(f)
|
||||||
|
header.file_data_offset = read_i32(f)
|
||||||
|
header.file_names_offset = read_i32(f)
|
||||||
|
header._reserved = read_i32(f)
|
||||||
|
|
||||||
|
unless header.mime_type == MIX1_HEADER || header.mime_type == MIX2_HEADER
|
||||||
|
@error_reason = format("Invalid mime type: %d", header.mime_type)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
@encrypted = header.mime_type == MIX2_HEADER
|
||||||
|
|
||||||
|
# Read entry info
|
||||||
|
f.pos = header.file_data_offset
|
||||||
|
file_count = read_i32(f)
|
||||||
|
|
||||||
|
file_count.times do |i|
|
||||||
|
entry_info = EntryInfoHeader.new(0, 0, 0)
|
||||||
|
entry_info.crc32 = read_u32(f)
|
||||||
|
entry_info.content_offset = read_u32(f)
|
||||||
|
entry_info.content_length = read_u32(f)
|
||||||
|
|
||||||
|
@entries << Entry.new(name: "", path: @path, info: entry_info)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read entry names
|
||||||
|
f.pos = header.file_names_offset
|
||||||
|
file_count = read_i32(f)
|
||||||
|
|
||||||
|
file_count.times do |i|
|
||||||
|
@entries[i].name = read_string(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
unless @entries.size.positive?
|
||||||
|
@error_reason = "No entries to write."
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.directory?(@path)
|
||||||
|
@error_reason = format("Path is a directory: %s", @path)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
File.open(@path, "wb") do |f|
|
||||||
|
header = MixHeader.new(encrypted? ? MIX2_HEADER : MIX1_HEADER, 0, 0, 0)
|
||||||
|
|
||||||
|
# write mime type
|
||||||
|
write_i32(f, header.mime_type)
|
||||||
|
|
||||||
|
f.pos = 16
|
||||||
|
|
||||||
|
# sort entries by crc32 of their name
|
||||||
|
sort_entries
|
||||||
|
|
||||||
|
# write file blobs
|
||||||
|
@entries.each do |entry|
|
||||||
|
# store current io position
|
||||||
|
pos = f.pos
|
||||||
|
|
||||||
|
# copy entry to stream
|
||||||
|
entry.copy_to(f)
|
||||||
|
|
||||||
|
# update entry with new offset
|
||||||
|
entry.info.content_offset = pos
|
||||||
|
|
||||||
|
# add alignment padding
|
||||||
|
padding = (-f.pos & 7)
|
||||||
|
padding.times do |i|
|
||||||
|
write_u8(f, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Save file data offset
|
||||||
|
header.file_data_offset = f.pos
|
||||||
|
|
||||||
|
# write number of entries
|
||||||
|
write_i32(f, @entries.size)
|
||||||
|
|
||||||
|
# write entries file data
|
||||||
|
@entries.each do |entry|
|
||||||
|
write_u32(f, entry.info.crc32)
|
||||||
|
write_u32(f, entry.info.content_offset)
|
||||||
|
write_u32(f, entry.info.content_length)
|
||||||
|
end
|
||||||
|
|
||||||
|
# save file names offset
|
||||||
|
header.file_names_offset = f.pos
|
||||||
|
|
||||||
|
# write number of entries
|
||||||
|
write_i32(f, @entries.size)
|
||||||
|
|
||||||
|
# write entry names
|
||||||
|
@entries.each do |entry|
|
||||||
|
write_string(f, entry.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# jump to io_position 4
|
||||||
|
f.pos = 4
|
||||||
|
# write rest of header
|
||||||
|
|
||||||
|
write_i32(f, header.file_data_offset)
|
||||||
|
write_i32(f, header.file_names_offset)
|
||||||
|
write_i32(f, header._reserved)
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
# ALL entries MUST have unique case-insensitive names
|
||||||
|
@entries.each do |a|
|
||||||
|
@entries.each do |b|
|
||||||
|
next if a == b
|
||||||
|
|
||||||
|
return false if a.name.upcase == b.name.upcase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def encrypted?
|
||||||
|
@encrypted
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_file(path:, replace: false)
|
||||||
|
return false unless File.exist?(path)
|
||||||
|
return false if File.directory?(path)
|
||||||
|
|
||||||
|
entry = Entry.new(name: File.basename(path), path: path, info: EntryInfoHeader.new(0, 0, File.size(path)))
|
||||||
|
add_entry(entry: entry, replace: replace)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_blob(path:, blob:, replace: false)
|
||||||
|
info = EntryInfoHeader.new(0, 0, blob.size)
|
||||||
|
entry = Entry.new(name: File.basename(path), path: path, info: info, blob: blob)
|
||||||
|
into.crc32 = @entries.last.calculate_crc32
|
||||||
|
|
||||||
|
add_entry(entry: entry, replace: replace)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_entry(entry:, replace: false)
|
||||||
|
duplicate = @entries.find { |e| e.name.upcase == entry.name.upcase }
|
||||||
|
|
||||||
|
if duplicate
|
||||||
|
if replace
|
||||||
|
@entries.delete(duplicate)
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@entries << entry
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def sort_entries
|
||||||
|
return false if @entries.any? { |e| e.info.crc32 == 0 }
|
||||||
|
|
||||||
|
@entries.sort! { |a, b| a.info.crc32 <=> b.info.crc32 }
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_i32(f) = f.read(4).unpack1("l")
|
||||||
|
def read_u32(f) = f.read(4).unpack1("L")
|
||||||
|
def read_u8(f) = f.read(1).unpack1("C")
|
||||||
|
def read_string(f)
|
||||||
|
f.read(read_u8(f)).strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_i32(f, value) = f.write([value].pack("l"))
|
||||||
|
def write_u32(f, value) = f.write([value].pack("L"))
|
||||||
|
def write_u8(f, value) = f.write([value].pack("C"))
|
||||||
|
def write_string(f, string)
|
||||||
|
length = string.size + 1 # include null byte
|
||||||
|
write_u8(f, length)
|
||||||
|
f.write(string)
|
||||||
|
write_u8(f, 0) # null byte
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,6 +9,8 @@ require "rexml"
|
|||||||
require "base64"
|
require "base64"
|
||||||
require "logger"
|
require "logger"
|
||||||
require "digest"
|
require "digest"
|
||||||
|
require "pathname"
|
||||||
|
require "fileutils"
|
||||||
|
|
||||||
require "async"
|
require "async"
|
||||||
require "async/http/internet/instance"
|
require "async/http/internet/instance"
|
||||||
@@ -37,6 +39,9 @@ require_relative "lib/states/boot"
|
|||||||
require_relative "lib/states/interface"
|
require_relative "lib/states/interface"
|
||||||
require_relative "lib/window"
|
require_relative "lib/window"
|
||||||
|
|
||||||
|
require_relative "lib/ico"
|
||||||
|
require_relative "lib/ww_mix"
|
||||||
|
|
||||||
require_relative "lib/worker"
|
require_relative "lib/worker"
|
||||||
require_relative "lib/worker/api"
|
require_relative "lib/worker/api"
|
||||||
require_relative "lib/worker/api/application"
|
require_relative "lib/worker/api/application"
|
||||||
|
|||||||
Reference in New Issue
Block a user