Compare commits
5 Commits
ad2544a56b
...
2031f589b7
| Author | SHA1 | Date | |
|---|---|---|---|
| 2031f589b7 | |||
| b909952790 | |||
| 6d651c7ad6 | |||
| 60909b0963 | |||
| 48617b26da |
16
lib/api.rb
@@ -1,19 +1,11 @@
|
|||||||
class W3DHub
|
class W3DHub
|
||||||
class Api
|
class Api
|
||||||
|
|
||||||
# Detect CA bundle path for Excon
|
|
||||||
def self.ca_bundle_path
|
|
||||||
redhat_path = '/etc/pki/tls/certs/ca-bundle.crt'
|
|
||||||
debian_path = '/etc/ssl/certs/ca-certificates.crt'
|
|
||||||
[redhat_path, debian_path].find { |path| File.exist?(path) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set Excon default CA file if found
|
# Set Excon default CA file if found
|
||||||
ca_file = ca_bundle_path
|
if (ca_file = W3DHub.ca_bundle_path)
|
||||||
if ca_file
|
|
||||||
Excon.defaults[:ssl_ca_file] = ca_file
|
Excon.defaults[:ssl_ca_file] = ca_file
|
||||||
end
|
end
|
||||||
|
|
||||||
LOG_TAG = "W3DHub::Api".freeze
|
LOG_TAG = "W3DHub::Api".freeze
|
||||||
|
|
||||||
API_TIMEOUT = 30 # seconds
|
API_TIMEOUT = 30 # seconds
|
||||||
@@ -295,7 +287,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
# If channel versions and access levels match then all's well
|
# If channel versions and access levels match then all's well
|
||||||
if channel.current_version == _channel.current_version &&
|
if channel.current_version == _channel.current_version &&
|
||||||
channel.user_level == _channel.user_level
|
channel.user_level == _channel.user_level
|
||||||
|
|
||||||
# All's Well!
|
# All's Well!
|
||||||
@@ -310,7 +302,7 @@ class W3DHub
|
|||||||
# Replaced, continue.
|
# Replaced, continue.
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
# If versions doen't match then pick whichever one is higher
|
# If versions doen't match then pick whichever one is higher
|
||||||
if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version)
|
if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version)
|
||||||
# Replace alternate's channel with primary's channel
|
# Replace alternate's channel with primary's channel
|
||||||
|
|||||||
@@ -10,15 +10,6 @@ class W3DHub
|
|||||||
TAG = "IRCClient"
|
TAG = "IRCClient"
|
||||||
|
|
||||||
class SSL
|
class SSL
|
||||||
# Detect system CA bundle path for SSL verification
|
|
||||||
def self.ca_bundle_path
|
|
||||||
[
|
|
||||||
'/etc/ssl/certs/ca-certificates.crt', # Debian/Ubuntu
|
|
||||||
'/etc/pki/tls/certs/ca-bundle.crt', # RHEL/Fedora/CentOS
|
|
||||||
'/etc/ssl/ca-bundle.pem' # Some other distros
|
|
||||||
].find { |path| File.exist?(path) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.default_context
|
def self.default_context
|
||||||
verify_peer_and_hostname
|
verify_peer_and_hostname
|
||||||
end
|
end
|
||||||
@@ -33,8 +24,7 @@ class W3DHub
|
|||||||
no_verify.tap do |context|
|
no_verify.tap do |context|
|
||||||
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
context.cert_store = OpenSSL::X509::Store.new
|
context.cert_store = OpenSSL::X509::Store.new
|
||||||
ca_file = ca_bundle_path
|
if (ca_file = W3DHub.ca_bundle_path)
|
||||||
if ca_file
|
|
||||||
context.cert_store.add_file(ca_file)
|
context.cert_store.add_file(ca_file)
|
||||||
else
|
else
|
||||||
context.cert_store.set_default_paths
|
context.cert_store.set_default_paths
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ class W3DHub
|
|||||||
linux? || mac?
|
linux? || mac?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Detect system CA bundle path for SSL verification
|
||||||
|
def self.ca_bundle_path
|
||||||
|
[
|
||||||
|
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu
|
||||||
|
"/etc/pki/tls/certs/ca-bundle.crt", # RHEL/Fedora/CentOS
|
||||||
|
"/etc/ssl/ca-bundle.pem" # Some other distros
|
||||||
|
].find { |path| File.exist?(path) }
|
||||||
|
end
|
||||||
|
|
||||||
def self.url(path)
|
def self.url(path)
|
||||||
raise "Hazardous input: #{path}" if path.include?("&&") || path.include?(";")
|
raise "Hazardous input: #{path}" if path.include?("&&") || path.include?(";")
|
||||||
|
|
||||||
@@ -193,7 +202,7 @@ class W3DHub
|
|||||||
|
|
||||||
path.strip
|
path.strip
|
||||||
else
|
else
|
||||||
result_ptr = LibUI.open_folder(window)
|
result_ptr = LibUI.open_folder(LIBUI_WINDOW)
|
||||||
result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/")
|
result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/")
|
||||||
|
|
||||||
result.strip
|
result.strip
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class W3DHub
|
|||||||
background app_color
|
background app_color
|
||||||
|
|
||||||
flow(width: 0.70, height: 1.0) do
|
flow(width: 0.70, height: 1.0) do
|
||||||
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
image_path = File.exist?("#{CACHE_PATH}/#{task.app_id}.png") ? "#{CACHE_PATH}/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
||||||
@application_image = image image_path, height: 1.0
|
@application_image = image image_path, height: 1.0
|
||||||
|
|
||||||
stack(margin_left: 8, width: 0.75) do
|
stack(margin_left: 8, width: 0.75) do
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Game Menu
|
# Game Menu
|
||||||
@game_page_container = stack(width: 1.0, fill: true, background_image: "#{GAME_ROOT_PATH}/media/textures/noiseb.png", background_image_mode: :tiled) do
|
@game_page_container = stack(width: 1.0, fill: true) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -85,7 +85,7 @@ class W3DHub
|
|||||||
padding_left: 4, padding_right: 4, tip: game.name) do
|
padding_left: 4, padding_right: 4, tip: game.name) do
|
||||||
background game.color if selected
|
background game.color if selected
|
||||||
|
|
||||||
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{game.id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
image_path = File.exist?("#{CACHE_PATH}/#{game.id}.png") ? "#{CACHE_PATH}/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
||||||
image_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff
|
image_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff
|
||||||
|
|
||||||
flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do
|
flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do
|
||||||
@@ -118,28 +118,25 @@ class W3DHub
|
|||||||
|
|
||||||
@game_page_container.clear do
|
@game_page_container.clear do
|
||||||
game_color = Gosu::Color.new(game.color)
|
game_color = Gosu::Color.new(game.color)
|
||||||
game_color.alpha = 0x88
|
game_color.alpha = 0xaa
|
||||||
|
|
||||||
background game_color
|
background_image_path = Cache.package_path(game.category, game.id, "background.png", "")
|
||||||
@game_page_container.style.background_image_color = game_color
|
if File.exist?(background_image_path)
|
||||||
@game_page_container.style.default[:background_image_color] = game_color
|
States::Interface.instance&.instance_variable_get(:"@interface_container")&.style&.background_image = get_image(background_image_path)
|
||||||
@game_page_container.update_background_image
|
States::Interface.instance&.instance_variable_get(:"@interface_container")&.style&.default[:background_image] = get_image(background_image_path)
|
||||||
|
end
|
||||||
|
|
||||||
# Game Stuff
|
# Game Stuff
|
||||||
flow(width: 1.0, fill: true) do
|
flow(width: 1.0, fill: true) do
|
||||||
# background 0xff_9999ff
|
|
||||||
|
|
||||||
# Game options
|
# Game options
|
||||||
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
|
stack(width: 360, height: 1.0, padding: 8, scroll: true, background: game_color, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
|
||||||
background 0x55_000000
|
# Game Logo
|
||||||
|
logo_image_path = Cache.package_path(game.category, game.id, "logo.png", "")
|
||||||
|
|
||||||
# Game Banner
|
if File.exist?(logo_image_path)
|
||||||
image_path = "#{GAME_ROOT_PATH}/media/banners/#{game.id}.png"
|
image logo_image_path, width: 1.0
|
||||||
|
|
||||||
if File.exist?(image_path)
|
|
||||||
image image_path, width: 1.0
|
|
||||||
else
|
else
|
||||||
banner game.name unless File.exist?(image_path)
|
banner game.name unless File.exist?(logo_image_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
|
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
|
||||||
@@ -331,11 +328,6 @@ class W3DHub
|
|||||||
|
|
||||||
def populate_all_games_view
|
def populate_all_games_view
|
||||||
@game_page_container.clear do
|
@game_page_container.clear do
|
||||||
background 0x88_353535
|
|
||||||
@game_page_container.style.background_image_color = 0x88_353535
|
|
||||||
@game_page_container.style.default[:background_image_color] = 0x88_353535
|
|
||||||
@game_page_container.update_background_image
|
|
||||||
|
|
||||||
@focused_game = nil
|
@focused_game = nil
|
||||||
@focused_channel = nil
|
@focused_channel = nil
|
||||||
|
|
||||||
@@ -438,6 +430,9 @@ class W3DHub
|
|||||||
return unless @focused_game == game
|
return unless @focused_game == game
|
||||||
|
|
||||||
if (feed = @game_news[game.id])
|
if (feed = @game_news[game.id])
|
||||||
|
game_color = Gosu::Color.new(game.color)
|
||||||
|
game_color.alpha = 0xaa
|
||||||
|
|
||||||
@game_news_container.clear do
|
@game_news_container.clear do
|
||||||
# Patch Notes
|
# Patch Notes
|
||||||
if false # Patch notes
|
if false # Patch notes
|
||||||
@@ -467,9 +462,7 @@ class W3DHub
|
|||||||
feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item|
|
feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item|
|
||||||
image_path = Cache.path(item.image)
|
image_path = Cache.path(item.image)
|
||||||
|
|
||||||
flow(width: 1.0, max_width: 869, height: 200, margin: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
|
flow(width: 1.0, max_width: 869, height: 200, margin: 8, background: game_color, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
|
||||||
background 0x44_000000
|
|
||||||
|
|
||||||
if File.file?(image_path)
|
if File.file?(image_path)
|
||||||
image image_path, height: 1.0
|
image image_path, height: 1.0
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class W3DHub
|
|||||||
app = Store.applications.games.find { |a| a.id == app_id.to_s }
|
app = Store.applications.games.find { |a| a.id == app_id.to_s }
|
||||||
next unless app
|
next unless app
|
||||||
|
|
||||||
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
image_path = File.exist?("#{CACHE_PATH}/#{app.id}.png") ? "#{CACHE_PATH}/#{app.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
||||||
|
|
||||||
image image_path, tip: "#{app.name}", height: 1.0,
|
image image_path, tip: "#{app.name}", height: 1.0,
|
||||||
border_thickness_bottom: 1, border_color_bottom: 0x00_000000,
|
border_thickness_bottom: 1, border_color_bottom: 0x00_000000,
|
||||||
@@ -534,7 +534,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def game_icon(server)
|
def game_icon(server)
|
||||||
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
image_path = File.exist?("#{CACHE_PATH}/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{CACHE_PATH}/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
||||||
|
|
||||||
if server.status.password
|
if server.status.password
|
||||||
@server_locked_icons[server.game] ||= Gosu.render(96, 96) do
|
@server_locked_icons[server.game] ||= Gosu.render(96, 96) do
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ class W3DHub
|
|||||||
"Español"
|
"Español"
|
||||||
else
|
else
|
||||||
logger.warn("W3DHub::Settings") { "Unknown language code: #{string.inspect}" }
|
logger.warn("W3DHub::Settings") { "Unknown language code: #{string.inspect}" }
|
||||||
|
|
||||||
|
"UNKNOWN"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class W3DHub
|
|||||||
refresh_user_token: { started: false, complete: false },
|
refresh_user_token: { started: false, complete: false },
|
||||||
service_status: { started: false, complete: false },
|
service_status: { started: false, complete: false },
|
||||||
applications: { started: false, complete: false },
|
applications: { started: false, complete: false },
|
||||||
app_icons: { started: false, complete: false }
|
app_icons: { started: false, complete: false },
|
||||||
|
app_logos_and_backgrounds: { started: false, complete: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
@offline_mode = false
|
@offline_mode = false
|
||||||
@@ -207,6 +208,8 @@ class W3DHub
|
|||||||
def app_icons
|
def app_icons
|
||||||
return unless Store.applications
|
return unless Store.applications
|
||||||
|
|
||||||
|
@status_label.value = "Retrieving application icons, this might take a moment..." # I18n.t(:"boot.checking_for_updates")
|
||||||
|
|
||||||
packages = []
|
packages = []
|
||||||
Store.applications.games.each do |app|
|
Store.applications.games.each do |app|
|
||||||
packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" }
|
packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" }
|
||||||
@@ -219,7 +222,7 @@ class W3DHub
|
|||||||
next if package.error?
|
next if package.error?
|
||||||
|
|
||||||
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png"
|
generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png"
|
||||||
|
|
||||||
regenerate = false
|
regenerate = false
|
||||||
|
|
||||||
@@ -241,6 +244,34 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def app_logos_and_backgrounds
|
||||||
|
return unless Store.applications
|
||||||
|
|
||||||
|
@status_label.value = "Retrieving application image assets, this might take a moment..." # I18n.t(:"boot.checking_for_updates")
|
||||||
|
|
||||||
|
packages = []
|
||||||
|
Store.applications.games.each do |app|
|
||||||
|
packages << { category: app.category, subcategory: app.id, name: "logo.png", version: "" }
|
||||||
|
packages << { category: app.category, subcategory: app.id, name: "background.png", version: "" }
|
||||||
|
end
|
||||||
|
|
||||||
|
Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details|
|
||||||
|
package_details ||= nil
|
||||||
|
|
||||||
|
package_details&.each do |package|
|
||||||
|
next if package.error?
|
||||||
|
|
||||||
|
package_cache_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
|
|
||||||
|
missing_or_broken_image = File.exist?(package_cache_path) ? Digest::SHA256.new.hexdigest(File.binread(package_cache_path)).upcase != package.checksum.upcase : true
|
||||||
|
|
||||||
|
Cache.fetch_package(package, proc {}) if missing_or_broken_image
|
||||||
|
end
|
||||||
|
|
||||||
|
@tasks[:app_logos_and_backgrounds][:complete] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def server_list
|
def server_list
|
||||||
@status_label.value = I18n.t(:"server_browser.fetching_server_list")
|
@status_label.value = I18n.t(:"server_browser.fetching_server_list")
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ class W3DHub
|
|||||||
APPLICATIONS_UPDATE_INTERVAL = 10 * 60 * 1000 # ten minutes
|
APPLICATIONS_UPDATE_INTERVAL = 10 * 60 * 1000 # ten minutes
|
||||||
SERVER_LIST_UPDATE_INTERVAL = 5 * 60 * 1000 # five minutes
|
SERVER_LIST_UPDATE_INTERVAL = 5 * 60 * 1000 # five minutes
|
||||||
|
|
||||||
|
DEFAULT_BACKGROUND_IMAGE = "#{GAME_ROOT_PATH}/media/banners/background.png".freeze
|
||||||
|
|
||||||
attr_accessor :interface_task_update_pending
|
attr_accessor :interface_task_update_pending
|
||||||
|
|
||||||
@@instance = nil
|
@@instance = nil
|
||||||
@@ -33,10 +35,12 @@ class W3DHub
|
|||||||
|
|
||||||
theme(W3DHub::THEME)
|
theme(W3DHub::THEME)
|
||||||
|
|
||||||
@interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR, background_image: "#{GAME_ROOT_PATH}/media/banners/background.png", background_image_color: 0xff_525252, background_image_mode: :fill) do
|
@interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR, background_image: DEFAULT_BACKGROUND_IMAGE, background_image_mode: :fill) do
|
||||||
background 0xff_252525
|
background 0xff_252525
|
||||||
|
|
||||||
@header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) do
|
@header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) do
|
||||||
|
background 0xaa_151515
|
||||||
|
|
||||||
flow(width: 148, height: 1.0) do
|
flow(width: 148, height: 1.0) do
|
||||||
flow(fill: true)
|
flow(fill: true)
|
||||||
image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 84
|
image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 84
|
||||||
@@ -54,18 +58,26 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
link I18n.t(:"interface.servers").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
link I18n.t(:"interface.servers").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
||||||
|
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
|
||||||
|
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
|
||||||
page(W3DHub::Pages::ServerBrowser)
|
page(W3DHub::Pages::ServerBrowser)
|
||||||
end
|
end
|
||||||
|
|
||||||
link I18n.t(:"interface.community").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
link I18n.t(:"interface.community").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
||||||
|
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
|
||||||
|
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
|
||||||
page(W3DHub::Pages::Community)
|
page(W3DHub::Pages::Community)
|
||||||
end
|
end
|
||||||
|
|
||||||
link I18n.t(:"interface.downloads").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
link I18n.t(:"interface.downloads").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
||||||
|
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
|
||||||
|
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
|
||||||
page(W3DHub::Pages::DownloadManager)
|
page(W3DHub::Pages::DownloadManager)
|
||||||
end
|
end
|
||||||
|
|
||||||
link I18n.t(:"interface.settings").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
link I18n.t(:"interface.settings").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
|
||||||
|
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
|
||||||
|
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
|
||||||
page(W3DHub::Pages::Settings)
|
page(W3DHub::Pages::Settings)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 771 KiB |
|
Before Width: | Height: | Size: 528 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 714 KiB |
|
Before Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 240 KiB |