neri 0.1.5 → 0.9.1

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.
data/lib/neri/build.rb CHANGED
@@ -1,718 +1,903 @@
1
- #!/usr/bin/env ruby
2
- # encoding: UTF-8
3
-
4
- require "neri"
5
-
6
- module Neri
7
- @data_files = []
8
- @system_files = []
9
-
10
- @options = {
11
- quiet: false,
12
- verbose: false,
13
-
14
- dlls: [],
15
- libs: [],
16
- gems: [],
17
- encoding: "*",
18
-
19
- enable_gems: false,
20
- enable_did_you_mean: false,
21
- chdir_first: false,
22
- pause_last: nil,
23
- pause_text: nil,
24
-
25
- output_dir: nil,
26
- system_dir: "system",
27
-
28
- data_file: nil,
29
- encryption_key: nil,
30
-
31
- no_exe: false,
32
- b2ec_path: "Bat_To_Exe_Converter.exe",
33
- b2ec: {
34
- icon: "#{File.expand_path(File.dirname(__FILE__) + '/../../share/default.ico')}",
35
- invisible: nil,
36
- x64: nil,
37
- uac_admin: nil,
38
- fileversion: nil,
39
- productversion: nil,
40
- productname: nil,
41
- originalfilename: nil,
42
- internalname: nil,
43
- description: nil,
44
- company: nil,
45
- trademarks: nil,
46
- copyright: nil,
47
- privatebuild: nil,
48
- specialbuild: nil,
49
- comments: nil
50
- },
51
-
52
- use_upx: false,
53
- upx_path: "upx.exe",
54
- upx_targets: ["bin/**/*.dll"],
55
- upx_options: "",
56
-
57
- zipfile: nil,
58
- sevenzip_path: "7z.exe",
59
-
60
- inno_script: nil,
61
- iscc_path: "iscc",
62
- }
63
- @rubyopt = ENV["RUBYOPT"].to_s
64
- @encryption_key = nil
65
-
66
- @use_dxruby = false
67
- @use_dxruby_tiled = false
68
- @use_ayame = false
69
-
70
- class << self
71
-
72
- attr_reader :options
73
-
74
- def relative_path(path, basedir=rubydir, prepath = "")
75
- basedir.concat(File::SEPARATOR) unless basedir.end_with?(File::SEPARATOR)
76
- return path.start_with?(basedir) ? path.sub(basedir, prepath) : path
77
- end
78
-
79
- def to_winpath(path)
80
- return File::ALT_SEPARATOR ? path.tr(File::SEPARATOR, File::ALT_SEPARATOR) : path
81
- end
82
-
83
- def bindir( ); RbConfig::CONFIG["bindir"] || File.join(rubydir, "bin"); end
84
- def rubydir( ); RbConfig::TOPDIR; end
85
- def rubyexe( ); RbConfig.ruby; end
86
- def batchfile(); File.join(options[:output_dir], "#{File.basename(@data_files.first, ".*")}.bat"); end
87
- def datafile( ); File.join(options[:output_dir], options[:system_dir], options[:data_file]); end
88
-
89
- # --help
90
- def output_help
91
- puts <<-EOF
92
- usage: neri [options] script.rb (other_files...) -- script_arguments
93
-
94
- options:
95
- --help or -h
96
- --version or -v
97
- --quiet
98
- --verbose
99
-
100
- --dll <dll1>,<dll2>,...
101
- --lib <lib1>,<lib2>,...
102
- --gem <gem1>,<gem2>,...
103
-
104
- --no-enc
105
- --encoding <enc1>,<enc2>,...
106
-
107
- --enable-gems
108
- --enable-did-you-mean
109
- --chdir-first
110
- --pause-last
111
- --no-pause-last
112
- --pause-text <text>
113
-
114
- --output-dir <dirname>
115
- --system-dir <dirname>
116
- --data-file <filename>
117
- --encryption-key <key>
118
-
119
- --no-exe
120
- --b2ec-path <bat_to_exe_converter_path>
121
- --icon <iconfile>
122
- --windows or --invisible
123
- --console or --visible
124
- --x64
125
- --uac-admin
126
- --fileversion <string> # ex) 1,2,3,4
127
- --productversion <string> # ex) 1,2,3,4
128
- --productname <string>
129
- --originalfilename <string>
130
- --internalname <string>
131
- --description <string>
132
- --company <string>
133
- --trademarks <string>
134
- --copyright <string>
135
- --privatebuild <string>
136
- --specialbuild <string>
137
- --comments <string>
138
-
139
- --use-upx
140
- --upx-path <upx path>
141
- --upx_targets '<glob>' # ex) 'bin/**/*.dll'
142
- --upx-options <options>
143
-
144
- --zipfile <filename>
145
- --7zip-path <7-zip path>
146
-
147
- --innosetup <inno_script>
148
- --iscc-path <iscc path>
149
-
150
- --create-recipe <recipefile>
151
- --recipe <recipefile>
152
- EOF
153
- end
154
-
155
- # --version
156
- def output_version
157
- puts "Neri #{Neri::VERSION}"
158
- end
159
-
160
- # --create-recipe
161
- def create_recipe(file, hash = options, pre = "Neri.options")
162
- hash.each_pair do |key, value|
163
- case value
164
- when Hash
165
- create_recipe(file, value, pre + "[:#{key}]")
166
- when Numeric, TrueClass, FalseClass
167
- file.puts "#{pre}[:#{key}] = #{value}"
168
- when NilClass
169
- file.puts "#{pre}[:#{key}] = nil"
170
- when String
171
- file.puts "#{pre}[:#{key}] = '#{value.gsub("\\", "\\\\").gsub("'", "\\'")}'"
172
- when Array
173
- file.puts "#{pre}[:#{key}] = " + JSON.generate(value)
174
- end
175
- end
176
- end
177
-
178
-
179
- def check_options
180
- nputs_v "Checking Neri options."
181
- while arg = ARGV.shift
182
- case arg
183
- when "--help", "-h"
184
- output_help
185
- exit
186
- when "--version", "-v"
187
- output_version
188
- exit
189
- when "--quiet", "-q"
190
- options[:quiet] = true
191
- when "--verbose", "-v"
192
- options[:verbose] = true
193
- when "--dll"
194
- options[:dlls] += ARGV.shift.split(/\s*,\s*/)
195
- when "--lib"
196
- options[:libs] += ARGV.shift.split(/\s*,\s*/)
197
- when "--gem"
198
- options[:gems] += ARGV.shift.split(/\s*,\s*/)
199
- when "--no-enc"
200
- options[:encoding] = nil
201
- when "--encoding"
202
- options[:encoding] = ARGV.shift
203
- when "--enable-gems"
204
- options[:enable_gems] = true
205
- when "--enable-did-you-mean"
206
- options[:enable_did_you_mean] = true
207
- when "--chdir-first"
208
- options[:chdir_first] = true
209
- when "--pause-last"
210
- options[:pause_last] = true
211
- when "--no-pause-last"
212
- options[:pause_last] = false
213
- when "--pause-text"
214
- options[:pause_text] = ARGV.shift
215
- options[:pause_last] = true
216
- when "--output-dir"
217
- options[:output_dir] = ARGV.shift
218
- when "--system-dir"
219
- options[:system_dir] = ARGV.shift
220
- when "--data-file"
221
- options[:data_file] = ARGV.shift
222
- when "--encryption-key"
223
- options[:encryption_key] = ARGV.shift
224
- when "--no-exe"
225
- options[:no_exe] = true
226
- when "--b2ec-path"
227
- options[:b2ec_path] = ARGV.shift
228
- when "--icon"
229
- options[:b2ec][:icon] = ARGV.shift
230
- when "--windows", "--invisible"
231
- options[:b2ec][:invisible] = true
232
- when "--console", "--visible"
233
- options[:b2ec][:invisible] = false
234
- when "--x64"
235
- options[:b2ec][:x64] = true
236
- when "--uac-admin"
237
- options[:b2ec][:uac_admin] = true
238
- when "--fileversion"
239
- options[:b2ec][:fileversion] = ARGV.shift
240
- when "--productversion"
241
- options[:b2ec][:productversion] = ARGV.shift
242
- when "--productname"
243
- options[:b2ec][:productname] = ARGV.shift
244
- when "--originalfilename"
245
- options[:b2ec][:originalfilename] = ARGV.shift
246
- when "--internalname"
247
- options[:b2ec][:internalname] = ARGV.shift
248
- when "--description"
249
- options[:b2ec][:description] = ARGV.shift
250
- when "--company"
251
- options[:b2ec][:company] = ARGV.shift
252
- when "--trademarks"
253
- options[:b2ec][:trademarks] = ARGV.shift
254
- when "--copyright"
255
- options[:b2ec][:copyright] = ARGV.shift
256
- when "--privatebuild"
257
- options[:b2ec][:privatebuild] = ARGV.shift
258
- when "--specialbuild"
259
- options[:b2ec][:specialbuild] = ARGV.shift
260
- when "--comments"
261
- options[:b2ec][:comments] = ARGV.shift
262
- when "--use-upx"
263
- options[:use_upx] = true
264
- when "--upx-path"
265
- options[:upx_path] = ARGV.shift
266
- when "--upx-targets"
267
- options[:upx_targets] += ARGV.shift.split(/\s*,\s*/)
268
- when "--upx-options"
269
- options[:upx_options] = ARGV.shift
270
- when "--zipfile"
271
- options[:zipfile] = ARGV.shift
272
- when "--7zip-path"
273
- options[:sevenzip_path] = ARGV.shift
274
- when "--innosetup"
275
- options[:inno_script] = ARGV.shift
276
- when "--iscc-path"
277
- options[:iscc_path] = ARGV.shift
278
- when "--create-recipe"
279
- require "json"
280
- filename = ARGV.shift
281
- nputs "Creating recipe_file '#{filename}'."
282
- open(filename, "w:utf-8"){|file| create_recipe(file)}
283
- exit
284
- when "--recipe"
285
- filename = ARGV.shift
286
- nputs_v "Loading recipe_file '#{filename}'."
287
- load filename
288
- when "--"
289
- break
290
- when /^(--.+)/
291
- puts "** Invalid Option '#{arg}'! **"
292
- output_help
293
- exit
294
- else
295
- @data_files.push(arg)
296
- end
297
- end
298
-
299
- if @data_files.empty?
300
- puts "** No Script File! **"
301
- output_help
302
- exit
303
- end
304
-
305
- if @data_files.size > 1 || options[:encryption_key]
306
- options[:data_file] ||= File.basename(@data_files.first, ".*") + ".dat"
307
- end
308
- end
309
-
310
-
311
- # check dependencies
312
- def rb_dependencies()
313
- return $LOADED_FEATURES.uniq
314
- end
315
-
316
- def dll_dependencies()
317
- require "Win32API"
318
-
319
- enumprocessmodules = Win32API.new("psapi" , "EnumProcessModules", ["L","P","L","P"], "L")
320
- getmodulefilename = Win32API.new("kernel32", "GetModuleFileNameW", ["L","P","L"], "L")
321
- getcurrentprocess = Win32API.new("kernel32", "GetCurrentProcess" , [], "L")
322
-
323
- bytes_needed = 4 * 32
324
- module_handle_buffer = nil
325
- process_handle = getcurrentprocess.call()
326
- loop do
327
- module_handle_buffer = "\x00" * bytes_needed
328
- bytes_needed_buffer = [0].pack("I")
329
- r = enumprocessmodules.call(process_handle, module_handle_buffer, module_handle_buffer.size, bytes_needed_buffer)
330
- bytes_needed = bytes_needed_buffer.unpack("I")[0]
331
- break if bytes_needed <= module_handle_buffer.size
332
- end
333
-
334
- handles = module_handle_buffer.unpack("I*")
335
- dependencies = handles.select { |handle| handle > 0 }.map do |handle|
336
- str = "\x00\x00" * 256
337
- modulefilename_length = getmodulefilename.call(handle, str, str.size)
338
- modulefilename = str[0, modulefilename_length * 2].force_encoding("UTF-16LE").encode("UTF-8")
339
- end
340
-
341
- dependencies.map!{|dep| dep.sub(/^\\\\\?\\/, "")}
342
- if File::ALT_SEPARATOR
343
- dependencies.map!{|dep| dep.tr(File::ALT_SEPARATOR, File::SEPARATOR)}
344
- end
345
- dependencies.delete(rubyexe)
346
-
347
- return dependencies.uniq
348
- end
349
-
350
- def ruby_dependencies()
351
- dependencies = Dir.glob(File.join(bindir, "**", "*.manifest"))
352
- dependencies.push(rubyexe)
353
- return dependencies.uniq
354
- end
355
-
356
- def additional_dlls_dependencies()
357
- dependencies = []
358
- options[:dlls].each do |dll|
359
- dependencies += Dir.glob(File.join(bindir, "**", dll))
360
- dependencies += Dir.glob(File.join(bindir, "**", dll + ".*"))
361
- end
362
- return dependencies.uniq
363
- end
364
-
365
- def additional_libs_dependencies()
366
- dependencies = []
367
- options[:libs].each do |lib|
368
- $LOAD_PATH.each do |path|
369
- dependencies += Dir.glob(File.join(path, lib))
370
- dependencies += Dir.glob(File.join(path, lib + ".*"))
371
- dependencies += Dir.glob(File.join(path, lib, "**", "*"))
372
- end
373
- end
374
- return dependencies.uniq
375
- end
376
-
377
- def additional_gems_dependencies()
378
- require "rubygems"
379
- dependencies = []
380
- rubygems_dir = File.join(Gem.dir, "gems")
381
- options[:gems].each do |gem|
382
- gem.sub!(/\:(.+)/, "")
383
- targets = $1.to_s.split("|")
384
- targets.push("lib/**/*")
385
- gem += "-*" unless gem.match("-")
386
- gemdir = Dir.glob(File.join(rubygems_dir, gem)).sort.last
387
- next unless gemdir
388
- targets.each do |target|
389
- dependencies += Dir.glob(File.join(gemdir, target))
390
- end
391
- end
392
- return dependencies.uniq
393
- end
394
-
395
- def encoding_dependencies()
396
- return [] unless options[:encoding]
397
- dependencies = []
398
- enc_dir = Dir.glob(File.join(RbConfig::CONFIG["archdir"] || RbConfig::TOPDIR, "**", "enc")).first
399
-
400
- options[:encoding].split(/\s*,\s*/).each do |enc|
401
- case enc
402
- when "ja"
403
- %w[windows_31j.so japanese_sjis.so encdb.so].each do |enc_name|
404
- dependencies += Dir.glob(File.join(enc_dir, "**", enc_name))
405
- end
406
- else
407
- dependencies += Dir.glob(File.join(enc_dir, "**", enc))
408
- dependencies += Dir.glob(File.join(enc_dir, "**", enc + ".*"))
409
- end
410
- end
411
-
412
- return dependencies.uniq
413
- end
414
-
415
- def check_dependencies()
416
- nputs "Running script '#{@data_files.first}' to check dependencies."
417
- begin
418
- load @data_files.first
419
- rescue SystemExit
420
- end
421
- nputs "Script '#{@data_files.first}' end."
422
-
423
- if defined? DXRuby
424
- require "neri/dxruby"
425
- @use_dxruby = true
426
- options[:b2ec][:invisible] = true if options[:b2ec][:invisible] == nil
427
- end
428
- if defined? DXRuby::Tiled
429
- require "neri/dxruby_tiled"
430
- @use_dxruby_tiled = true
431
- end
432
- if defined? Ayame
433
- require "neri/ayame"
434
- @use_ayame = true
435
- end
436
-
437
- if options[:b2ec][:invisible] == nil
438
- options[:b2ec][:invisible] = true if File.extname(@data_files.first) == ".rbw"
439
- end
440
- if options[:pause_last] == nil
441
- options[:pause_last] = true unless options[:b2ec][:invisible]
442
- end
443
-
444
- require "rbconfig"
445
- dependencies = []
446
- dependencies += rb_dependencies
447
- dependencies += dll_dependencies
448
- dependencies += ruby_dependencies
449
- dependencies += additional_dlls_dependencies
450
- dependencies += additional_libs_dependencies
451
- dependencies += additional_gems_dependencies
452
- dependencies += encoding_dependencies
453
- dependencies = select_dependencies(dependencies)
454
-
455
- size = dependencies.map{|d| File.size(d)}.inject(&:+)
456
- nputs "#{dependencies.size} files, #{size} bytes dependencies."
457
- if options[:verbose]
458
- dependencies.each do |dependency|
459
- nputs_v " - #{dependency}"
460
- end
461
- end
462
-
463
- return dependencies
464
- end
465
-
466
- def select_dependencies(dependencies)
467
- dependencies.select! do |dependency|
468
- dependency.start_with?(rubydir + File::SEPARATOR)
469
- end
470
-
471
- @data_files.each do |file|
472
- dependencies.delete(File.expand_path(file))
473
- end
474
-
475
- unless options[:enable_gems]
476
- dependencies.delete_if do |dependency|
477
- File.basename(dependency) == "rubygems.rb" ||
478
- dependency.split(File::SEPARATOR).index("rubygems")
479
- end
480
- end
481
- unless options[:enable_did_you_mean]
482
- dependencies.delete_if do |dependency|
483
- File.basename(dependency) == "did_you_mean.rb" ||
484
- dependency.split(File::SEPARATOR).index("did_you_mean")
485
- end
486
- end
487
-
488
- return dependencies.uniq
489
- end
490
-
491
-
492
- def copy_files(dependencies)
493
- nputs "Copying dependencies."
494
- require "fileutils"
495
- options[:output_dir] ||= File.basename(@data_files.first, ".*")
496
- src_dir = File.join(rubydir, "")
497
- desc_dir = File.join(options[:output_dir], options[:system_dir], "")
498
-
499
- @system_files = dependencies.map do |file|
500
- [file, file.sub(src_dir, desc_dir)]
501
- end
502
- unless options[:enable_gems]
503
- @system_files.each do |src, desc|
504
- desc.sub!(/\/gems(\/\d+\.\d+\.\d+\/)gems\/(.+?)\-[^\/]+\/lib\//, "/vendor_ruby\\1")
505
- end
506
- end
507
-
508
- @system_files.each do |src, desc|
509
- FileUtils.makedirs(File.dirname(desc))
510
- if File.file?(src)
511
- FileUtils.copy(src, desc)
512
- nputs_v " #{src}\n -> #{desc}"
513
- end
514
- end
515
- FileUtils.copy(@data_files.first, desc_dir) unless options[:data_file]
516
- end
517
-
518
-
519
- def create_batch()
520
- nputs "Creating batch_file '#{batchfile}'."
521
-
522
- enc = system(%(ruby --disable-gems -e "'#{@data_files.first}'" >NUL 2>&1)) ?
523
- '' : ' -e "# coding: utf-8"'
524
-
525
- unless options[:enable_gems]
526
- @rubyopt += " --disable-gems" unless @rubyopt.match("--disable-gems")
527
- end
528
-
529
- ruby_code = ""
530
- if options[:encryption_key]
531
- require "digest/sha2"
532
- @encryption_key = Digest::SHA2.hexdigest(options[:encryption_key])
533
- ruby_code = "Neri.key='#{@encryption_key}';"
534
- end
535
- if options[:data_file]
536
- data_file = "%~dp0#{options[:system_dir]}#{File::ALT_SEPARATOR || File::SEPARATOR}#{options[:data_file]}"
537
- ruby_code += "Neri.datafile='#{data_file}';"
538
- ruby_code += "load '#{File.basename(@data_files.first)}'"
539
- else
540
- ruby_code += "load '%~dp0#{options[:system_dir]}#{File::ALT_SEPARATOR}#{File.basename(@data_files.first)}'"
541
- end
542
-
543
- pause_code = ""
544
- if options[:pause_last]
545
- if options[:pause_text]
546
- pause_code = "echo.\necho #{options[:pause_text]}\npause > nul"
547
- else
548
- pause_code = "echo.\npause"
549
- end
550
- end
551
-
552
- r = " -rneri"
553
- r += " -rneri/dxruby" if @use_dxruby
554
- r += " -rneri/dxruby_tiled" if @use_dxruby_tiled
555
- r += " -rneri/ayame" if @use_ayame
556
-
557
- open(batchfile, "w:#{Encoding.default_external.name}") do |f|
558
- f.puts <<-EOF
559
- @echo off
560
- setlocal
561
- set PATH=%~dp0#{options[:system_dir]}\\#{relative_path(bindir)};%PATH%
562
- #{options[:chdir_first] ? 'cd /d "%~dp0"' : ''}
563
- if %~x0 == .exe ( shift )
564
- #{relative_path(rubyexe, bindir)}#{r} #{@rubyopt}#{enc} -e "#{ruby_code}"
565
- #{pause_code}
566
- endlocal
567
- EOF
568
- end
569
- end
570
-
571
-
572
- def create_datafile()
573
- nputs "Creating data_file '#{datafile}'."
574
- data_files = @data_files.select { |file| File.file? file }
575
- @data_files.select { |file| File.directory? file }.each do |dir|
576
- data_files += Dir.glob(dir + "/**/*").select { |file| File.file? file }
577
- end
578
- Neri.key = @encryption_key || "0" * 64
579
- open(datafile, "wb") do |f|
580
- pos = 0
581
- files_str = data_files.map{|file|
582
- filename = File.expand_path(file)
583
- filename = relative_path(filename, rubydir, "*neri*" + File::SEPARATOR)
584
- filename = relative_path(filename, Dir.pwd)
585
- filedata = [filename, File.size(file), pos]
586
- pos += File.size(file)
587
- pos += BLOCK_LENGTH - pos % BLOCK_LENGTH unless pos % BLOCK_LENGTH == 0
588
- nputs_v " - #{filename}:#{File.size(file)} bytes"
589
- filedata.join("\t")
590
- }.join("\n").encode(Encoding::UTF_8)
591
-
592
- f.write(sprintf("%#{BLOCK_LENGTH}d", files_str.bytesize))
593
- f.write(xor(files_str))
594
- data_files.each do |file|
595
- f.write(xor(File.binread(file)))
596
- end
597
- end
598
- end
599
-
600
-
601
- def bat_to_exe_converter()
602
- exefile = batchfile.sub(/\.bat$/, ".exe")
603
- nputs "Creating exe_file '#{exefile}'."
604
- File.delete(exefile) if File.exist?(exefile)
605
- args = "/bat #{batchfile} /exe #{exefile}"
606
- if options[:b2ec][:x64] == nil
607
- options[:b2ec][:x64] = true if RbConfig::CONFIG["target"].to_s.index("64")
608
- end
609
-
610
- args += options[:b2ec].map{|key, value|
611
- case value
612
- when String; " /#{key.to_s.tr('_', '-')} \"#{value}\""
613
- when true; " /#{key.to_s.tr('_', '-')}"
614
- else; ""
615
- end
616
- }.join("")
617
- begin
618
- exec = %(#{options[:b2ec_path]}#{args})
619
- nputs_v exec
620
- options[:quiet] ? `#{exec}` : system(exec)
621
- rescue SystemCallError
622
- end
623
- if File.exist?(exefile)
624
- File.delete(batchfile)
625
- else
626
- nputs "Failed to create exe_file."
627
- end
628
- end
629
-
630
-
631
- def upx()
632
- nputs "Compressing with UPX."
633
- options[:upx_targets].each do |target|
634
- Dir.glob(File.join(options[:output_dir], options[:system_dir], target)).each do |target_path|
635
- exec = %(#{options[:upx_path]} #{options[:upx_options]} "#{target_path}")
636
- nputs_v exec
637
- options[:quiet] ? `#{exec}` : system(exec)
638
- end
639
- end
640
- end
641
-
642
-
643
- def create_zipfile()
644
- nputs "Creating zip_file '#{options[:zipfile]}'."
645
- File.delete(options[:zipfile]) if File.exist?(options[:zipfile])
646
- exec = %(#{options[:sevenzip_path]} a #{options[:zipfile]} "#{options[:output_dir]}")
647
- nputs_v exec
648
- options[:quiet] ? `#{exec}` : system(exec)
649
- end
650
-
651
-
652
- def inno_setup()
653
- filename = options[:inno_script]
654
- script = "[Setup]\n"
655
- if File.exist?(filename)
656
- script = File.read(filename, encoding: Encoding::UTF_8)
657
- filename = File.basename(filename, ".*") + "_tmp" + File.extname(filename)
658
- end
659
-
660
- version = options[:b2ec][:productversion] || options[:b2ec][:fileversion]
661
- if !script.match(/^AppName=/) && options[:b2ec][:productname]
662
- script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppName=#{options[:b2ec][:productname]}#{$2}" }
663
- end
664
- if !script.match(/^AppVersion=/) && version
665
- script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppVersion=#{version}#{$2}" }
666
- end
667
- if !script.match(/^AppVerName=/) && options[:b2ec][:productname] && version
668
- script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppVerName=#{options[:b2ec][:productname]} #{version}#{$2}" }
669
- end
670
- if !script.match(/^AppPublisher=/) && options[:b2ec][:company]
671
- script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppPublisher=#{options[:b2ec][:company]}#{$2}" }
672
- end
673
- if !script.match(/^AppCopyright=/) && options[:b2ec][:copyright]
674
- script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppCopyright=#{options[:b2ec][:copyright]}#{$2}" }
675
- end
676
-
677
- script += "\n[Files]\n" unless script.match(/^\[Files\]/)
678
- dir = File.expand_path(options[:output_dir])
679
- files_str = ""
680
- Dir.glob(File.join(dir, "**", "*")).each do |file|
681
- next unless File.file? file
682
- dist_dir = to_winpath(File::SEPARATOR + File.dirname(relative_path(file, dir)))
683
- dist_dir = "" if dist_dir == "\\."
684
- files_str += "\nSource: \"#{to_winpath(file)}\"; DistDir: \"{app}#{dist_dir}\""
685
- files_str += "; Flags: isreadme" if File.basename(file).match(/^readme/i)
686
- end
687
- script.sub!(/^(\[Files\])(\s*)/i){ "#{$1}#{files_str}#{$2}" }
688
-
689
- File.write(filename, script)
690
- exec = %(#{options[:iscc_path]} "#{filename}")
691
- nputs_v exec
692
- options[:quiet] ? `#{exec}` : system(exec)
693
- end
694
-
695
- def run()
696
- check_options
697
- dependencies = check_dependencies
698
- copy_files(dependencies)
699
- create_batch
700
- create_datafile if options[:data_file]
701
- bat_to_exe_converter unless options[:no_exe]
702
- upx if options[:use_upx]
703
- create_zipfile if options[:zipfile]
704
- inno_setup if options[:inno_script]
705
- nputs "Neri Finished."
706
- end
707
-
708
- private
709
-
710
- def nputs(str)
711
- puts "=== #{str}" unless options[:quiet]
712
- end
713
-
714
- def nputs_v(str)
715
- puts str if options[:verbose]
716
- end
717
- end
718
- end
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require "neri"
5
+
6
+ module Neri
7
+ @data_files = []
8
+
9
+ @options = {
10
+ quiet: false,
11
+ verbose: false,
12
+
13
+ external_encoding: nil,
14
+
15
+ dlls: [],
16
+ libs: [],
17
+ gems: [],
18
+ encoding: "*",
19
+
20
+ enable_gems: false,
21
+ enable_did_you_mean: false,
22
+ chdir_first: false,
23
+ pause_last: nil,
24
+ pause_text: nil,
25
+
26
+ output_dir: "./",
27
+ system_dir: "system",
28
+
29
+ datafile: nil,
30
+ encryption_key: nil,
31
+
32
+ no_exe: false,
33
+ use_b2ec: false,
34
+ b2ec_path: "Bat_To_Exe_Converter",
35
+ b2ec: {
36
+ icon: "#{File.expand_path(File.dirname(__FILE__) + '/../../share/default.ico')}",
37
+ invisible: nil,
38
+ x64: nil,
39
+ uac_admin: nil,
40
+ fileversion: nil,
41
+ productversion: nil,
42
+ productname: nil,
43
+ originalfilename: nil,
44
+ internalname: nil,
45
+ description: nil,
46
+ company: nil,
47
+ trademarks: nil,
48
+ copyright: nil,
49
+ privatebuild: nil,
50
+ specialbuild: nil,
51
+ comments: nil
52
+ },
53
+
54
+ use_upx: false,
55
+ upx_path: "upx",
56
+ upx_targets: ["bin/**/*.dll"],
57
+ upx_options: "",
58
+
59
+ zipfile: nil,
60
+ sevenzip_path: "7z",
61
+
62
+ inno_script: nil,
63
+ iscc_path: "iscc",
64
+ }
65
+ @rubyopt = ENV["RUBYOPT"].to_s
66
+ @args = ""
67
+ @encryption_key = nil
68
+
69
+ @use_dxruby = false
70
+ @use_dxruby_tiled = false
71
+ @use_ayame = false
72
+
73
+ class << self
74
+
75
+ attr_reader :options
76
+
77
+ def relative_path(path, basedir=rubydir, prepath = "")
78
+ basedir.concat(File::SEPARATOR) unless basedir.end_with?(File::SEPARATOR)
79
+ return path.start_with?(basedir) ? path.sub(basedir, prepath) : path
80
+ end
81
+
82
+ def to_winpath(path)
83
+ return File::ALT_SEPARATOR ? path.tr(File::SEPARATOR, File::ALT_SEPARATOR) : path
84
+ end
85
+
86
+ def bindir( ); RbConfig::CONFIG["bindir"] || File.join(rubydir, "bin"); end
87
+ def rubydir( ); File.join(RbConfig::TOPDIR, ""); end
88
+ def rubyexe( ); RbConfig.ruby; end
89
+ def scriptfile(); @data_files.first; end
90
+ def basename( ); File.basename(scriptfile, ".*"); end
91
+ def basepath( ); File.join(options[:output_dir], basename); end
92
+ def datafile( ); File.join(options[:output_dir], options[:system_dir], options[:datafile]); end
93
+
94
+ # --help
95
+ def output_help
96
+ puts <<-EOF
97
+ usage: neri [options] script.rb (other_files...) -- script_arguments
98
+
99
+ options:
100
+ --help or -h
101
+ --version or -v
102
+ --quiet
103
+ --verbose
104
+
105
+ --external-encoding <encoding>
106
+
107
+ --dll <dll1>,<dll2>,...
108
+ --lib <lib1>,<lib2>,...
109
+ --gem <gem1>,<gem2>,...
110
+
111
+ --no-enc
112
+ --encoding <enc1>,<enc2>,...
113
+
114
+ --enable-gems
115
+ --enable-did-you-mean
116
+ --chdir-first
117
+ --pause-last
118
+ --no-pause-last
119
+ --pause-text <text>
120
+
121
+ --output-dir <dirname>
122
+ --system-dir <dirname>
123
+ --datafile <filename>
124
+ --encryption-key <key>
125
+
126
+ --no-exe
127
+ --use-b2ec
128
+ --b2ec-path <bat_to_exe_converter_path>
129
+ --icon <iconfile>
130
+ --windows or --invisible
131
+ --console or --visible
132
+ --x64
133
+ --uac-admin
134
+ --fileversion <string> # ex) 1,2,3,4
135
+ --productversion <string> # ex) 1,2,3,4
136
+ --productname <string>
137
+ --originalfilename <string>
138
+ --internalname <string>
139
+ --description <string>
140
+ --company <string>
141
+ --trademarks <string>
142
+ --copyright <string>
143
+ --privatebuild <string>
144
+ --specialbuild <string>
145
+ --comments <string>
146
+
147
+ --use-upx
148
+ --upx-path <upx path>
149
+ --upx_targets '<glob>' # ex) 'bin/**/*.dll'
150
+ --upx-options <options>
151
+
152
+ --zipfile <filename>
153
+ --7zip-path <7-zip path>
154
+
155
+ --innosetup <inno_script>
156
+ --iscc-path <iscc path>
157
+
158
+ --create-recipe <recipefile>
159
+ --recipe <recipefile>
160
+ EOF
161
+ end
162
+
163
+ # --version
164
+ def output_version
165
+ puts "Neri #{Neri::VERSION}"
166
+ end
167
+
168
+ # --create-recipe
169
+ def create_recipe(file, hash = options, pre = "Neri.options")
170
+ hash.each_pair do |key, value|
171
+ case value
172
+ when Hash
173
+ create_recipe(file, value, pre + "[:#{key}]")
174
+ when Numeric, TrueClass, FalseClass
175
+ file.puts "#{pre}[:#{key}] = #{value}"
176
+ when NilClass
177
+ file.puts "#{pre}[:#{key}] = nil"
178
+ when String, Array
179
+ file.puts "#{pre}[:#{key}] = " + JSON.generate(value)
180
+ end
181
+ end
182
+ end
183
+
184
+
185
+ def check_options
186
+ nputs_v "Checking Neri options."
187
+ while arg = ARGV.shift
188
+ case arg
189
+ when "--help", "-h"
190
+ output_help
191
+ exit
192
+ when "--version", "-v"
193
+ output_version
194
+ exit
195
+ when "--quiet", "-q"
196
+ options[:quiet] = true
197
+ when "--verbose", "-v"
198
+ options[:verbose] = true
199
+ when "--external_encoding"
200
+ options[:external_encoding] = ARGV.shift
201
+ when "--dll"
202
+ options[:dlls] += ARGV.shift.split(",").map(&:strip)
203
+ when "--lib"
204
+ options[:libs] += ARGV.shift.split(",").map(&:strip)
205
+ when "--gem"
206
+ options[:gems] += ARGV.shift.split(",").map(&:strip)
207
+ when "--no-enc"
208
+ options[:encoding] = nil
209
+ when "--encoding"
210
+ options[:encoding] = ARGV.shift
211
+ when "--enable-gems"
212
+ options[:enable_gems] = true
213
+ when "--enable-did-you-mean"
214
+ options[:enable_did_you_mean] = true
215
+ when "--chdir-first"
216
+ options[:chdir_first] = true
217
+ when "--pause-last"
218
+ options[:pause_last] = true
219
+ when "--no-pause-last"
220
+ options[:pause_last] = false
221
+ when "--pause-text"
222
+ options[:pause_text] = ARGV.shift.encode("utf-8")
223
+ options[:pause_last] = true
224
+ when "--output-dir"
225
+ options[:output_dir] = ARGV.shift.encode("utf-8")
226
+ when "--system-dir"
227
+ options[:system_dir] = ARGV.shift.encode("utf-8")
228
+ when "--datafile"
229
+ options[:datafile] = ARGV.shift.encode("utf-8")
230
+ when "--encryption-key"
231
+ options[:encryption_key] = ARGV.shift.encode("utf-8")
232
+ when "--no-exe"
233
+ options[:no_exe] = true
234
+ when "--use-b2ec"
235
+ options[:use_b2ec] = true
236
+ when "--b2ec-path"
237
+ options[:b2ec_path] = ARGV.shift.encode("utf-8")
238
+ when "--icon"
239
+ options[:b2ec][:icon] = ARGV.shift.encode("utf-8")
240
+ when "--windows", "--invisible"
241
+ options[:b2ec][:invisible] = true
242
+ when "--console", "--visible"
243
+ options[:b2ec][:invisible] = false
244
+ when "--x64"
245
+ options[:b2ec][:x64] = true
246
+ when "--uac-admin"
247
+ options[:b2ec][:uac_admin] = true
248
+ when "--fileversion"
249
+ options[:b2ec][:fileversion] = ARGV.shift
250
+ when "--productversion"
251
+ options[:b2ec][:productversion] = ARGV.shift
252
+ when "--productname"
253
+ options[:b2ec][:productname] = ARGV.shift.encode("utf-8")
254
+ when "--originalfilename"
255
+ options[:b2ec][:originalfilename] = ARGV.shift.encode("utf-8")
256
+ when "--internalname"
257
+ options[:b2ec][:internalname] = ARGV.shift.encode("utf-8")
258
+ when "--description"
259
+ options[:b2ec][:description] = ARGV.shift.encode("utf-8")
260
+ when "--company"
261
+ options[:b2ec][:company] = ARGV.shift.encode("utf-8")
262
+ when "--trademarks"
263
+ options[:b2ec][:trademarks] = ARGV.shift.encode("utf-8")
264
+ when "--copyright"
265
+ options[:b2ec][:copyright] = ARGV.shift.encode("utf-8")
266
+ when "--privatebuild"
267
+ options[:b2ec][:privatebuild] = ARGV.shift.encode("utf-8")
268
+ when "--specialbuild"
269
+ options[:b2ec][:specialbuild] = ARGV.shift.encode("utf-8")
270
+ when "--comments"
271
+ options[:b2ec][:comments] = ARGV.shift.encode("utf-8")
272
+ when "--use-upx"
273
+ options[:use_upx] = true
274
+ when "--upx-path"
275
+ options[:upx_path] = ARGV.shift.encode("utf-8")
276
+ when "--upx-targets"
277
+ options[:upx_targets] += ARGV.shift.split(",").map(&:strip)
278
+ when "--upx-options"
279
+ options[:upx_options] = ARGV.shift
280
+ when "--zipfile"
281
+ options[:zipfile] = ARGV.shift.encode("utf-8").sub(/\.zip$/, "") + ".zip"
282
+ when "--7zip-path"
283
+ options[:sevenzip_path] = ARGV.shift.encode("utf-8")
284
+ when "--innosetup"
285
+ options[:inno_script] = ARGV.shift.encode("utf-8")
286
+ when "--iscc-path"
287
+ options[:iscc_path] = ARGV.shift.encode("utf-8")
288
+ when "--create-recipe"
289
+ require "json"
290
+ filename = ARGV.shift.encode("utf-8")
291
+ nputs "Creating recipe_file '#{filename}'."
292
+ open(filename, "w:utf-8"){ |file| create_recipe(file) }
293
+ exit
294
+ when "--recipe"
295
+ filename = ARGV.shift.encode("utf-8")
296
+ nputs_v "Loading recipe_file '#{filename}'."
297
+ load File.expand_path(filename)
298
+ when "--"
299
+ break
300
+ when /^(--.+)/
301
+ error "Invalid Option '#{arg}'!"
302
+ output_help
303
+ exit
304
+ else
305
+ @data_files.push(arg.encode("utf-8"))
306
+ end
307
+ end
308
+
309
+ if @data_files.empty?
310
+ error "No Script File!"
311
+ output_help
312
+ exit
313
+ end
314
+
315
+ @args = ARGV.map{ |arg| %[ "#{arg}"] }.join("")
316
+ @options[:external_encoding] ||= Encoding::default_external.name
317
+ unless options[:enable_gems]
318
+ @rubyopt += " --disable-gems" unless @rubyopt.index("--disable-gems")
319
+ end
320
+ unless options[:enable_did_you_mean]
321
+ @rubyopt += " --disable-did_you_mean" unless @rubyopt.index("--disable-did_you_mean")
322
+ end
323
+ if @data_files.size > 1 || options[:encryption_key]
324
+ options[:datafile] ||= basename + ".dat"
325
+ end
326
+ end
327
+
328
+
329
+ # check dependencies
330
+ def rb_dependencies()
331
+ return $LOADED_FEATURES.uniq
332
+ end
333
+
334
+ def dll_dependencies()
335
+ require "Win32API"
336
+
337
+ enumprocessmodules = Win32API.new("psapi" , "EnumProcessModules", ["L","P","L","P"], "L")
338
+ getmodulefilename = Win32API.new("kernel32", "GetModuleFileNameW", ["L","P","L"], "L")
339
+ getcurrentprocess = Win32API.new("kernel32", "GetCurrentProcess" , [], "L")
340
+
341
+ bytes_needed = 4 * 32
342
+ module_handle_buffer = nil
343
+ process_handle = getcurrentprocess.call()
344
+ loop do
345
+ module_handle_buffer = "\x00" * bytes_needed
346
+ bytes_needed_buffer = [0].pack("I")
347
+ r = enumprocessmodules.call(process_handle, module_handle_buffer, module_handle_buffer.size, bytes_needed_buffer)
348
+ bytes_needed = bytes_needed_buffer.unpack("I")[0]
349
+ break if bytes_needed <= module_handle_buffer.size
350
+ end
351
+
352
+ handles = module_handle_buffer.unpack("I*")
353
+ dependencies = handles.select { |handle| handle > 0 }.map do |handle|
354
+ str = "\x00\x00" * 256
355
+ modulefilename_length = getmodulefilename.call(handle, str, str.size)
356
+ modulefilename = str[0, modulefilename_length * 2].force_encoding("UTF-16LE").encode("UTF-8")
357
+ end
358
+
359
+ dependencies.map!{|dep| dep.sub(/^\\\\\?\\/, "")}
360
+ if File::ALT_SEPARATOR
361
+ dependencies.map!{|dep| dep.tr(File::ALT_SEPARATOR, File::SEPARATOR)}
362
+ end
363
+ dependencies.delete(rubyexe)
364
+
365
+ return dependencies.uniq
366
+ end
367
+
368
+ def ruby_dependencies()
369
+ dependencies = Dir.glob(File.join(bindir, "**", "*.manifest"))
370
+ dependencies.push(rubyexe)
371
+ return dependencies.uniq
372
+ end
373
+
374
+ def additional_dlls_dependencies()
375
+ dependencies = []
376
+ options[:dlls].each do |dll|
377
+ dependencies += Dir.glob(File.join(bindir, "**", dll))
378
+ dependencies += Dir.glob(File.join(bindir, "**", dll + ".*"))
379
+ end
380
+ return dependencies.uniq
381
+ end
382
+
383
+ def additional_libs_dependencies()
384
+ dependencies = []
385
+ options[:libs].each do |lib|
386
+ $LOAD_PATH.each do |path|
387
+ dependencies += Dir.glob(File.join(path, lib))
388
+ dependencies += Dir.glob(File.join(path, lib + ".*"))
389
+ dependencies += Dir.glob(File.join(path, lib, "**", "*"))
390
+ end
391
+ end
392
+ return dependencies.uniq
393
+ end
394
+
395
+ def additional_gems_dependencies()
396
+ require "rubygems"
397
+ dependencies = []
398
+ rubygems_dir = File.join(Gem.dir, "gems")
399
+ options[:gems].each do |gem|
400
+ gem.sub!(/\:(.+)/, "")
401
+ targets = $1.to_s.split("|")
402
+ targets.push("lib/**/*")
403
+ gem += "-*" unless gem.match("-")
404
+ gemdir = Dir.glob(File.join(rubygems_dir, gem)).sort.last
405
+ next unless gemdir
406
+ targets.each do |target|
407
+ dependencies += Dir.glob(File.join(gemdir, target))
408
+ end
409
+ end
410
+ return dependencies.uniq
411
+ end
412
+
413
+ def encoding_dependencies()
414
+ return [] unless options[:encoding]
415
+ dependencies = []
416
+ enc_dir = Dir.glob(File.join(RbConfig::CONFIG["archdir"] || RbConfig::TOPDIR, "**", "enc")).first
417
+
418
+ options[:encoding].split(",").map(&:strip).each do |enc|
419
+ case enc
420
+ when "ja"
421
+ %w[windows_31j.so japanese_sjis.so encdb.so].each do |enc_name|
422
+ dependencies += Dir.glob(File.join(enc_dir, "**", enc_name))
423
+ end
424
+ else
425
+ dependencies += Dir.glob(File.join(enc_dir, "**", enc))
426
+ dependencies += Dir.glob(File.join(enc_dir, "**", enc + ".*"))
427
+ end
428
+ end
429
+
430
+ return dependencies.uniq
431
+ end
432
+
433
+ def check_dependencies()
434
+ nputs "Running script '#{scriptfile}' to check dependencies."
435
+ begin
436
+ load File.expand_path(scriptfile)
437
+ rescue SystemExit, Interrupt
438
+ end
439
+ nputs "Script '#{scriptfile}' end."
440
+
441
+ if defined? DXRuby
442
+ require "neri/dxruby"
443
+ @use_dxruby = true
444
+ end
445
+ if defined? DXRuby::Tiled
446
+ require "neri/dxruby_tiled"
447
+ @use_dxruby_tiled = true
448
+ end
449
+ if defined? Ayame
450
+ require "neri/ayame"
451
+ @use_ayame = true
452
+ end
453
+
454
+ if options[:b2ec][:invisible] == nil &&
455
+ (File.extname(scriptfile) == ".rbw" || @use_dxruby)
456
+ options[:b2ec][:invisible] = true
457
+ end
458
+ if options[:pause_last] == nil
459
+ options[:pause_last] = true unless options[:b2ec][:invisible]
460
+ end
461
+
462
+ require "rbconfig"
463
+ dependencies = []
464
+ dependencies += rb_dependencies
465
+ dependencies += dll_dependencies
466
+ dependencies += ruby_dependencies
467
+ dependencies += additional_dlls_dependencies
468
+ dependencies += additional_libs_dependencies
469
+ dependencies += additional_gems_dependencies
470
+ dependencies += encoding_dependencies
471
+ dependencies = select_dependencies(dependencies)
472
+
473
+ size = dependencies.map{|d| File.size(d)}.inject(&:+)
474
+ nputs "#{dependencies.size} files, #{size} bytes dependencies."
475
+ if options[:verbose]
476
+ dependencies.each do |dependency|
477
+ nputs_v " - #{dependency}"
478
+ end
479
+ end
480
+
481
+ return dependencies
482
+ end
483
+
484
+ def select_dependencies(dependencies)
485
+ dependencies.select! do |dependency|
486
+ dependency.start_with?(rubydir)
487
+ end
488
+
489
+ @data_files.each do |file|
490
+ dependencies.delete(File.expand_path(file))
491
+ end
492
+
493
+ unless options[:enable_gems]
494
+ dependencies.delete_if do |dependency|
495
+ File.basename(dependency) == "rubygems.rb" ||
496
+ dependency.split(File::SEPARATOR).index("rubygems")
497
+ end
498
+ end
499
+ unless options[:enable_did_you_mean]
500
+ dependencies.delete_if do |dependency|
501
+ File.basename(dependency) == "did_you_mean.rb" ||
502
+ dependency.split(File::SEPARATOR).index("did_you_mean")
503
+ end
504
+ end
505
+
506
+ return dependencies.uniq
507
+ end
508
+
509
+
510
+ def copy_files(dependencies)
511
+ nputs "Copying dependencies."
512
+ require "fileutils"
513
+ src_dir = rubydir
514
+ desc_dir = File.join(options[:output_dir], options[:system_dir], "")
515
+
516
+ system_files = dependencies.map do |file|
517
+ [file, file.sub(src_dir, desc_dir)]
518
+ end
519
+ unless options[:enable_gems]
520
+ system_files.each do |src, desc|
521
+ desc.sub!(/\/gems(\/\d+\.\d+\.\d+\/)gems\/(.+?)\-[^\/]+\/lib\//, "/vendor_ruby\\1")
522
+ end
523
+ end
524
+
525
+ system_files.each do |src, desc|
526
+ FileUtils.makedirs(File.dirname(desc))
527
+ if File.file?(src)
528
+ FileUtils.copy(src, desc)
529
+ nputs_v " #{src}\n -> #{desc}"
530
+ end
531
+ end
532
+ FileUtils.copy(scriptfile, desc_dir) unless options[:datafile]
533
+ end
534
+
535
+
536
+ def create_datafile()
537
+ if @data_files.size > 1 || options[:encryption_key]
538
+ options[:datafile] ||= basename + ".dat"
539
+ end
540
+ return unless options[:datafile]
541
+
542
+ nputs "Creating datafile '#{datafile}'."
543
+ data_files = @data_files.select { |file| File.file? file }
544
+ @data_files.select { |file| File.directory? file }.each do |dir|
545
+ data_files += Dir.glob(dir + "/**/*").select { |file| File.file? file }
546
+ end
547
+ if options[:encryption_key]
548
+ require "digest/sha2"
549
+ @encryption_key = Digest::SHA2.hexdigest(options[:encryption_key])
550
+ end
551
+ Neri.key = @encryption_key || "0" * 64
552
+ open(datafile, "wb") do |f|
553
+ pos = 0
554
+ files_str = data_files.map{|file|
555
+ filename = File.expand_path(file)
556
+ filename = relative_path(filename, rubydir, "*neri*" + File::SEPARATOR)
557
+ filename = relative_path(filename, Dir.pwd)
558
+ filedata = [filename, File.size(file), pos]
559
+ pos += File.size(file)
560
+ pos += BLOCK_LENGTH - pos % BLOCK_LENGTH unless pos % BLOCK_LENGTH == 0
561
+ nputs_v " - #{filename}:#{File.size(file)} bytes"
562
+ filedata.join("\t")
563
+ }.join("\n").encode(Encoding::UTF_8)
564
+
565
+ f.write(sprintf("%#{BLOCK_LENGTH}d", files_str.bytesize))
566
+ f.write(xor(files_str))
567
+ data_files.each do |file|
568
+ f.write(xor(File.binread(file)))
569
+ end
570
+ end
571
+ end
572
+
573
+
574
+ def create_batfile()
575
+ nputs "Creating batch_file '#{basepath}.bat'."
576
+
577
+ pause_command = ""
578
+ if options[:pause_last]
579
+ pause_command += "echo.\n"
580
+ if options[:pause_text]
581
+ pause_command += "echo #{options[:pause_text]}\n" +
582
+ "pause > nul"
583
+ else
584
+ pause_command += "pause"
585
+ end
586
+ end
587
+ chdir = options[:chdir_first] ? 'cd /d "%~dp0"' : ""
588
+
589
+ open(basepath + ".bat", "w:#{options[:external_encoding]}") do |f|
590
+ f.puts <<-EOF
591
+ @echo off
592
+ setlocal
593
+ set PATH=%~dp0#{options[:system_dir]}\\#{relative_path(bindir)};%PATH%
594
+ set NERI_EXECUTABLE=%~0
595
+ #{chdir}
596
+ if %~x0 == .exe ( shift )
597
+ #{ruby_command(options[:chdir_first] ? "" : "%~dp0")} %1 %2 %3 %4 %5 %6 %7 %8 %9
598
+ #{pause_command}
599
+ endlocal
600
+ EOF
601
+ end
602
+ end
603
+
604
+ def create_exefile()
605
+ unless system("gcc --version >nul 2>&1 && windres --version >nul 2>&1")
606
+ error "gcc or windres not found !"
607
+ create_batfile
608
+ return
609
+ end
610
+
611
+ exe_file = to_winpath(basepath + ".exe" )
612
+ c_file = to_winpath(basepath + "_tmp.c" )
613
+ o_file = to_winpath(basepath + "_tmp.o" )
614
+ rc_file = to_winpath(basepath + "_tmp.rc")
615
+ system_dir = escape_cstr(to_winpath(File.join(options[:system_dir], "")))
616
+ nputs "Creating exe_file '#{exe_file}'."
617
+ open(c_file, "w:#{options[:external_encoding]}") do |f|
618
+ f.puts <<-EOF
619
+ #include <stdio.h>
620
+ #include <stdlib.h>
621
+ #include <windows.h>
622
+ #include <unistd.h>
623
+
624
+ int main(int argc, char *argv[])
625
+ {
626
+ char exepath[_MAX_PATH * 2 + 1],
627
+ drive [_MAX_DRIVE + 1],
628
+ dir [_MAX_DIR * 2 + 1],
629
+ fname [_MAX_FNAME * 2 + 1],
630
+ ext [_MAX_EXT * 2 + 1],
631
+ paths [_MAX_PATH * 32 + 1],
632
+ runruby[_MAX_PATH * 32 + 1];
633
+ PROCESS_INFORMATION pi;
634
+ STARTUPINFO si;
635
+ ZeroMemory(&si, sizeof(STARTUPINFO));
636
+
637
+ if(GetModuleFileName(NULL, exepath, MAX_PATH * 2) != 0){
638
+ _splitpath_s(exepath, drive, _MAX_DRIVE, dir, _MAX_DIR * 2, fname, _MAX_FNAME * 2, ext, _MAX_EXT * 2);
639
+ } else {
640
+ exit(EXIT_FAILURE);
641
+ }
642
+ snprintf(paths, sizeof(paths), "NERI_EXECUTABLE=%s", exepath);
643
+ putenv(paths);
644
+ snprintf(paths, sizeof(paths), "PATH=%s%s#{system_dir}bin;%s", drive, dir, getenv("PATH"));
645
+ putenv(paths);
646
+ #{options[:chdir_first] ? 'snprintf(paths, sizeof(paths), "%s%s", drive, dir);chdir(paths);' : ''}
647
+ snprintf(runruby, sizeof(runruby), "#{escape_cstr(ruby_command(options[:chdir_first] ? "" : "%s%s"))} %s %s %s %s %s %s %s %s %s",
648
+ #{options[:chdir_first] ? "" : "drive, dir,"}
649
+ argc > 1 ? argv[1] : "",
650
+ argc > 2 ? argv[2] : "",
651
+ argc > 3 ? argv[3] : "",
652
+ argc > 4 ? argv[4] : "",
653
+ argc > 5 ? argv[5] : "",
654
+ argc > 6 ? argv[6] : "",
655
+ argc > 7 ? argv[7] : "",
656
+ argc > 8 ? argv[8] : "",
657
+ argc > 9 ? argv[9] : ""
658
+ );
659
+ EOF
660
+ if options[:b2ec][:invisible]
661
+ f.puts %[ CreateProcess(NULL, runruby, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi);]
662
+ else
663
+ f.puts %[ system(runruby);]
664
+ end
665
+ if options[:pause_last]
666
+ f.puts %[ system("echo.");]
667
+ if options[:pause_text]
668
+ f.puts %[ system("echo #{escape_cstr(options[:pause_text])}");]
669
+ f.puts %[ system("pause >nul");]
670
+ else
671
+ f.puts %[ system("pause");]
672
+ end
673
+ end
674
+ f.puts " return 0;\n}"
675
+ end
676
+
677
+ open(rc_file, "w:#{options[:external_encoding]}") do |f|
678
+ f.puts <<-EOF
679
+ #include <winver.h>
680
+
681
+ 1 VERSIONINFO
682
+ #{options[:b2ec][:fileversion ] ? "FILEVERSION " + escape_cstr(options[:b2ec][:fileversion ]) : ""}
683
+ #{options[:b2ec][:productversion] ? "PRODUCTVERSION " + escape_cstr(options[:b2ec][:productversion]) : ""}
684
+ FILETYPE VFT_APP
685
+ BEGIN
686
+ BLOCK "StringFileInfo"
687
+ BEGIN
688
+ BLOCK "000004b0"
689
+ BEGIN
690
+ #{options[:b2ec][:fileversion ] ? 'VALUE "FileVersion", "' + escape_cstr(options[:b2ec][:fileversion ]) + '\0"' : ''}
691
+ #{options[:b2ec][:productversion ] ? 'VALUE "ProductVersion", "' + escape_cstr(options[:b2ec][:productversion ]) + '\0"' : ''}
692
+ #{options[:b2ec][:productname ] ? 'VALUE "ProductName", "' + escape_cstr(options[:b2ec][:productname ]) + '\0"' : ''}
693
+ #{options[:b2ec][:originalfilename] ? 'VALUE "OriginalFileName", "' + escape_cstr(options[:b2ec][:originalfilename]) + '\0"' : ''}
694
+ #{options[:b2ec][:internalname ] ? 'VALUE "InternalName", "' + escape_cstr(options[:b2ec][:internalname ]) + '\0"' : ''}
695
+ #{options[:b2ec][:description ] ? 'VALUE "FileDescription", "' + escape_cstr(options[:b2ec][:description ]) + '\0"' : ''}
696
+ #{options[:b2ec][:company ] ? 'VALUE "CompanyName", "' + escape_cstr(options[:b2ec][:company ]) + '\0"' : ''}
697
+ #{options[:b2ec][:trademarks ] ? 'VALUE "LegalTrademarks", "' + escape_cstr(options[:b2ec][:trademarks ]) + '\0"' : ''}
698
+ #{options[:b2ec][:copyright ] ? 'VALUE "LegalCopyright", "' + escape_cstr(options[:b2ec][:copyright ]) + '\0"' : ''}
699
+ #{options[:b2ec][:privatebuild ] ? 'VALUE "PrivateBuild", "' + escape_cstr(options[:b2ec][:privatebuild ]) + '\0"' : ''}
700
+ #{options[:b2ec][:specialbuild ] ? 'VALUE "SpecialBuild", "' + escape_cstr(options[:b2ec][:specialbuild ]) + '\0"' : ''}
701
+ #{options[:b2ec][:comments ] ? 'VALUE "Comments", "' + escape_cstr(options[:b2ec][:comments ]) + '\0"' : ''}
702
+ END
703
+ END
704
+
705
+ BLOCK "VarFileInfo"
706
+ BEGIN
707
+ VALUE "Translation", 0x0, 0x4b0
708
+ END
709
+ END
710
+
711
+ 2 ICON "#{escape_cstr(options[:b2ec][:icon])}"
712
+ EOF
713
+ end
714
+ nsystem(%[windres -o "#{o_file}" "#{rc_file}"])
715
+ nsystem(%[gcc#{options[:b2ec][:invisible] ? " -mwindows" : ""} -o "#{exe_file}" "#{c_file}" "#{o_file}"])
716
+ nsystem(%[strip "#{exe_file}"])
717
+ File.delete(c_file, rc_file, o_file)
718
+ end
719
+
720
+ def ruby_command(path)
721
+ system_dir = "#{path}#{File.join(options[:system_dir], "")}"
722
+ ruby_code = ""
723
+ ruby_code = "Neri.key='#{@encryption_key}';" if @encryption_key
724
+ if options[:datafile]
725
+ ruby_code += "Neri.datafile='#{system_dir}' + #{unpack_filename(options[:datafile])};"
726
+ ruby_code += "load #{unpack_filename(File.basename(scriptfile))}"
727
+ else
728
+ ruby_code += "load File.expand_path('#{system_dir}' + #{unpack_filename(scriptfile)})"
729
+ end
730
+
731
+ r = " -rneri"
732
+ r += " -rneri/dxruby" if @use_dxruby
733
+ r += " -rneri/dxruby_tiled" if @use_dxruby_tiled
734
+ r += " -rneri/ayame" if @use_ayame
735
+
736
+ ruby = to_winpath(relative_path(rubyexe, bindir))
737
+ return %[#{ruby}#{r} #{@rubyopt} -e "# coding:utf-8" -e "#{ruby_code}" #{@args}]
738
+ end
739
+
740
+
741
+ def bat_to_exe_converter()
742
+ create_batfile
743
+ begin
744
+ `#{options[:b2ec_path]} /help`
745
+ rescue
746
+ error "Bat To Exe Converter not found !"
747
+ return
748
+ end
749
+
750
+ batch_file = basepath + ".bat"
751
+ exe_file = basepath + ".exe"
752
+ nputs "Creating exe_file '#{exe_file}' with Bat To Exe Converter."
753
+ File.delete(exe_file) if File.exist?(exe_file)
754
+ if options[:b2ec][:x64] == nil
755
+ options[:b2ec][:x64] = true if RbConfig::CONFIG["target"].to_s.index("64")
756
+ end
757
+ args = %[ /bat "#{batch_file}" /exe "#{exe_file}"]
758
+ args += options[:b2ec].map{|key, value|
759
+ case value
760
+ when String; %[ /#{key.to_s.tr("_", "-")} "#{value}"]
761
+ when true; %[ /#{key.to_s.tr("_", "-")}]
762
+ else; %[]
763
+ end
764
+ }.join("")
765
+
766
+
767
+ unless nsystem "#{options[:b2ec_path]}#{args}"
768
+ error "Failed to create exe_file !"
769
+ end
770
+ end
771
+
772
+
773
+ def upx()
774
+ unless system("#{options[:upx_path]} --version >nul 2>&1")
775
+ error "UPX not found !"
776
+ return
777
+ end
778
+
779
+ nputs "Compressing with UPX."
780
+ options[:upx_targets].each do |target|
781
+ Dir.glob(File.join(options[:output_dir], options[:system_dir], target)).each do |target_path|
782
+ command = %("#{options[:upx_path]}" #{options[:upx_options]} "#{target_path}")
783
+ nsystem command
784
+ end
785
+ end
786
+ end
787
+
788
+
789
+ def create_zipfile()
790
+ unless system("#{options[:sevenzip_path]} >nul 2>&1")
791
+ error "7-Zip not found !"
792
+ return
793
+ end
794
+
795
+ nputs "Creating zip_file '#{options[:zipfile]}'."
796
+ File.delete(options[:zipfile]) if File.exist?(options[:zipfile])
797
+ files = []
798
+ if options[:output_dir] == "./"
799
+ files.push(options[:system_dir])
800
+ files.push(File.exist?(basepath + ".exe") ? basepath + ".exe" : basepath + ".bat")
801
+ else
802
+ files.push(options[:output_dir])
803
+ end
804
+ command = %("#{options[:sevenzip_path]}" a "#{options[:zipfile]}" "#{files.join('" "')}")
805
+ nsystem command
806
+ end
807
+
808
+
809
+ def inno_setup()
810
+ unless system("#{options[:iscc_path]} /? >nul 2>&1")
811
+ error("Inno Setup not found !")
812
+ return
813
+ end
814
+
815
+ filename = options[:inno_script]
816
+ nputs "Creating Installer '#{filename}'."
817
+ script = "[Setup]\n"
818
+ if File.exist?(filename)
819
+ script = File.read(filename, encoding: Encoding::UTF_8)
820
+ filename = File.basename(filename, ".*") + "_tmp" + File.extname(filename)
821
+ end
822
+
823
+ version = options[:b2ec][:productversion] || options[:b2ec][:fileversion]
824
+ if !script.match(/^AppName=/) && options[:b2ec][:productname]
825
+ script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppName=#{options[:b2ec][:productname]}#{$2}" }
826
+ end
827
+ if !script.match(/^AppVersion=/) && version
828
+ script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppVersion=#{version}#{$2}" }
829
+ end
830
+ if !script.match(/^AppVerName=/) && options[:b2ec][:productname] && version
831
+ script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppVerName=#{options[:b2ec][:productname]} #{version}#{$2}" }
832
+ end
833
+ if !script.match(/^AppPublisher=/) && options[:b2ec][:company]
834
+ script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppPublisher=#{options[:b2ec][:company]}#{$2}" }
835
+ end
836
+ if !script.match(/^AppCopyright=/) && options[:b2ec][:copyright]
837
+ script.sub!(/^(\[Setup\])(\s+)/i){ "#{$1}\nAppCopyright=#{options[:b2ec][:copyright]}#{$2}" }
838
+ end
839
+
840
+ script += "\n[Files]\n" unless script.match(/^\[Files\]/)
841
+ dir = File.expand_path(options[:output_dir])
842
+ files_str = ""
843
+ Dir.glob(File.join(dir, "**", "*")).each do |file|
844
+ next unless File.file? file
845
+ dist_dir = to_winpath(File::SEPARATOR + File.dirname(relative_path(file, dir)))
846
+ dist_dir = "" if dist_dir == "\\."
847
+ files_str += "\nSource: \"#{to_winpath(file)}\"; DistDir: \"{app}#{dist_dir}"
848
+ files_str += "; Flags: isreadme" if File.basename(file).match(/^readme/i)
849
+ end
850
+ script.sub!(/^(\[Files\])(\s*)/i){ "#{$1}#{files_str}#{$2}" }
851
+
852
+ File.write(filename, script)
853
+ command = %(#{options[:iscc_path]} "#{filename}")
854
+ nsystem command
855
+ end
856
+
857
+
858
+ def run()
859
+ check_options
860
+ dependencies = check_dependencies
861
+ copy_files(dependencies)
862
+ create_datafile
863
+ if options[:no_exe]
864
+ create_batfile
865
+ else
866
+ options[:use_b2ec] ? bat_to_exe_converter : create_exefile
867
+ end
868
+ upx if options[:use_upx]
869
+ create_zipfile if options[:zipfile]
870
+ inno_setup if options[:inno_script]
871
+ nputs "Neri Finished."
872
+ end
873
+
874
+
875
+ private
876
+
877
+ def nputs(str)
878
+ puts "=== #{str}" unless options[:quiet]
879
+ end
880
+
881
+ def nputs_v(str)
882
+ puts str if options[:verbose]
883
+ end
884
+
885
+ def error(str)
886
+ puts "\e[31m#{str}\e[0m"
887
+ end
888
+
889
+ def unpack_filename(filename)
890
+ "[" + filename.unpack("U*").map { |u| u.to_s }.join(",") + "].pack('U*')"
891
+ end
892
+
893
+ def escape_cstr(str)
894
+ str.gsub("\\"){ "\\\\" }.gsub('"'){ '\\"' }.gsub("'"){ "\\'" }
895
+ end
896
+
897
+ def nsystem(str)
898
+ nputs_v(str)
899
+ command = str.encode(options[:external_encoding])
900
+ return system(command + (options[:quiet] ? " >nul 2>&1" : ""))
901
+ end
902
+ end
903
+ end