neri 0.1.5 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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