snes_utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d649dce97b8d4385763f9fc18ee9917cff690b938beac9f281b1f13666b7dbd1
4
+ data.tar.gz: 206fc1e24eb2acfda9380ce3295beec7c402f9076eaebd2bcafcde6df19c0283
5
+ SHA512:
6
+ metadata.gz: 598b0c6c67ca081027148860638653eed841e3492f748ccc0173cf63fbe2f4a2a0fe4f3c4b00f31f46c7f357adaa112ca13cdd7f303f750cc2c60472c055ae65
7
+ data.tar.gz: ba437c0b86b96a60eb141edfc9a8a80f59ca39d352013d31fd1181b251d571da0234827d68bc415af44d2ba67e0dc47d649ad9bfe9aa759c08e425d36567860e
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'snes_utils'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.on('-f', '--file FILENAME', 'ROM file') { |o| options[:filename] = o }
9
+ end.parse!
10
+
11
+ SnesUtils::MiniAssembler.new(options[:filename]).run
data/bin/png2snes ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'snes_utils'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.on('-f', '--file FILENAME', 'PNG source file') { |o| options[:filename] = o }
9
+ end.parse!
10
+
11
+ raise OptionParser::MissingArgument, 'Must specify PNG source file' if options[:filename].nil?
12
+
13
+ c = SnesUtils::Png2Snes.new options[:filename]
14
+ c.write_palette
15
+ c.write_image
data/bin/tmx2snes ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'snes_utils'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.on('-f', '--file FILENAME', 'TMX source file') { |o| options[:filename] = o }
9
+ opts.on('-s', '--tile-size TILESIZE', '8 or 16') { |o| options[:tile_size] = o.to_i }
10
+ end.parse!
11
+
12
+ raise OptionParser::MissingArgument, 'Must specify TMX source file' if options[:filename].nil?
13
+ raise 'Wrong size : must either be 8 or 16' unless [8, 16].include? options[:tile_size]
14
+
15
+ t = SnesUtils::Tmx2Snes.new options[:filename], big_char: options[:tile_size] == 16
16
+
17
+ t.write
@@ -0,0 +1,347 @@
1
+ module SnesUtils
2
+ class Definitions
3
+ HEX_DIGIT = '[0-9a-f]'
4
+ HEX8 = "\\$?(#{HEX_DIGIT}{1,2})"
5
+ HEX16 = "\\$?(#{HEX_DIGIT}{3,4})"
6
+ HEX24 = "\\$?(#{HEX_DIGIT}{5,6})"
7
+
8
+ MODES_REGEXES = {
9
+ acc: /^$/,
10
+ imp: /^$/,
11
+ imm: /^#{HEX8}$/i,
12
+ iml: /^#{HEX16}$/i,
13
+ imm8: /^##{HEX8}$/i,
14
+ imm16: /^##{HEX16}$/i,
15
+ sr: /^#{HEX8},S$/i,
16
+ dp: /^#{HEX8}$/i,
17
+ dpx: /^#{HEX8},X$/i,
18
+ dpy: /^#{HEX8},Y$/i,
19
+ idp: /^\(#{HEX8}\)$/i,
20
+ idx: /^\(#{HEX8},X\)$/i,
21
+ idy: /^\(#{HEX8}\),Y$/i,
22
+ idl: /^\[#{HEX8}\]$/i,
23
+ idly: /^\[#{HEX8}\],Y$/i,
24
+ isy: /^\(#{HEX8},S\),Y$/i,
25
+ abs: /^#{HEX16}$/i,
26
+ abx: /^#{HEX16},X$/i,
27
+ aby: /^#{HEX16},Y$/i,
28
+ abl: /^#{HEX24}$/i,
29
+ alx: /^#{HEX24},X$/i,
30
+ ind: /^\(#{HEX16}\)$/i,
31
+ iax: /^\(#{HEX16},X\)$/i,
32
+ ial: /^\[#{HEX16}\]$/i,
33
+ rel: /^#{HEX16}$/i,
34
+ rell: /^#{HEX16}$/i,
35
+ bm: /^#{HEX8},#{HEX8}$/i
36
+ }
37
+
38
+ BYTE_LOC_REGEX = /^#{HEX_DIGIT}{1,4}$/i
39
+ BYTE_RANGE_REGEX = /^(#{HEX_DIGIT}{1,4})\.+(#{HEX_DIGIT}{1,4})$/i
40
+ BYTE_SEQUENCE_REGEX = /^(#{HEX_DIGIT}{1,4}):\s*([0-9a-f ]+)$/i
41
+ DISASSEMBLE_REGEX = /^(#{HEX_DIGIT}{,4})l/i
42
+ SWITCH_BANK_REGEX = /^(#{HEX_DIGIT}{1,2})\/$/i
43
+ FLIP_MX_REG_REGEX = /^([01])=([xm])$/i
44
+ WRITE_REGEX = /^\.write\s*(.*)$/i
45
+ INCBIN_REGEX = /^(#{HEX_DIGIT}{1,4}):\s*\.incbin\s+(.*)$/i
46
+ READ_REGEX = /^\.read\s+(.*)$/i
47
+
48
+ MODES_FORMATS = {
49
+ acc: "%s",
50
+ imp: "%s",
51
+ imm: "%s #%02X",
52
+ iml: "%s %02X",
53
+ imm8: "%s #%02X",
54
+ imm16: "%s #%04X",
55
+ sr: "%s #%02X,S",
56
+ dp: "%s %02X",
57
+ dpx: "%s %02X,X",
58
+ dpy: "%s %02X,Y",
59
+ idp: "%s (%02X)",
60
+ idx: "%s (%02X,X)",
61
+ idy: "%s (%02X),Y",
62
+ idl: "%s [%02X]",
63
+ idly: "%s [%02X],Y",
64
+ isy: "%s (%02X,S),Y",
65
+ abs: "%s %04X",
66
+ abx: "%s %04X,X",
67
+ aby: "%s %04X,Y",
68
+ abl: "%s %06x",
69
+ alx: "%s %06x,X",
70
+ ind: "%s (%04X)",
71
+ iax: "%s (%04X,X)",
72
+ ial: "%s [%04X]",
73
+ rel: "%s %04X {%s}",
74
+ rell: "%s %04X {%s}",
75
+ bm: "%s %02X,%02X"
76
+ }
77
+
78
+ OPCODES_DATA = [{ opcode: 0x61, mnemonic: 'ADC', mode: :idx, length: 2 },
79
+ { opcode: 0x63, mnemonic: 'ADC', mode: :sr, length: 2 },
80
+ { opcode: 0x65, mnemonic: 'ADC', mode: :dp, length: 2 },
81
+ { opcode: 0x67, mnemonic: 'ADC', mode: :idl, length: 2 },
82
+ { opcode: 0x69, mnemonic: 'ADC', mode: :imm8, length: 2, m: 1 },
83
+ { opcode: 0x69, mnemonic: 'ADC', mode: :imm16, length: 3, m: 0 },
84
+ { opcode: 0x6d, mnemonic: 'ADC', mode: :abs, length: 3 },
85
+ { opcode: 0x6f, mnemonic: 'ADC', mode: :abl, length: 4 },
86
+ { opcode: 0x71, mnemonic: 'ADC', mode: :idy, length: 2 },
87
+ { opcode: 0x72, mnemonic: 'ADC', mode: :idp, length: 2 },
88
+ { opcode: 0x73, mnemonic: 'ADC', mode: :isy, length: 2 },
89
+ { opcode: 0x75, mnemonic: 'ADC', mode: :dpx, length: 2 },
90
+ { opcode: 0x77, mnemonic: 'ADC', mode: :idly, length: 2 },
91
+ { opcode: 0x79, mnemonic: 'ADC', mode: :aby, length: 3 },
92
+ { opcode: 0x7d, mnemonic: 'ADC', mode: :abx, length: 3 },
93
+ { opcode: 0x7f, mnemonic: 'ADC', mode: :alx, length: 4 },
94
+ { opcode: 0xe1, mnemonic: 'SBC', mode: :idx, length: 2 },
95
+ { opcode: 0xe3, mnemonic: 'SBC', mode: :sr, length: 2 },
96
+ { opcode: 0xe5, mnemonic: 'SBC', mode: :dp, length: 2 },
97
+ { opcode: 0xe7, mnemonic: 'SBC', mode: :idl, length: 2 },
98
+ { opcode: 0xe9, mnemonic: 'SBC', mode: :imm8, length: 2, m: 1 },
99
+ { opcode: 0xe9, mnemonic: 'SBC', mode: :imm16, length: 3, m: 0 },
100
+ { opcode: 0xed, mnemonic: 'SBC', mode: :abs, length: 3 },
101
+ { opcode: 0xef, mnemonic: 'SBC', mode: :abl, length: 4 },
102
+ { opcode: 0xf1, mnemonic: 'SBC', mode: :idy, length: 2 },
103
+ { opcode: 0xf2, mnemonic: 'SBC', mode: :idp, length: 2 },
104
+ { opcode: 0xf3, mnemonic: 'SBC', mode: :isy, length: 2 },
105
+ { opcode: 0xf5, mnemonic: 'SBC', mode: :dpx, length: 2 },
106
+ { opcode: 0xf7, mnemonic: 'SBC', mode: :idly, length: 2 },
107
+ { opcode: 0xf9, mnemonic: 'SBC', mode: :aby, length: 3 },
108
+ { opcode: 0xfd, mnemonic: 'SBC', mode: :abx, length: 3 },
109
+ { opcode: 0xff, mnemonic: 'SBC', mode: :alx, length: 4 },
110
+ { opcode: 0xc1, mnemonic: 'CMP', mode: :idx, length: 2 },
111
+ { opcode: 0xc3, mnemonic: 'CMP', mode: :sr, length: 2 },
112
+ { opcode: 0xc5, mnemonic: 'CMP', mode: :dp, length: 2 },
113
+ { opcode: 0xc7, mnemonic: 'CMP', mode: :idl, length: 2 },
114
+ { opcode: 0xc9, mnemonic: 'CMP', mode: :imm8, length: 2, m: 1 },
115
+ { opcode: 0xc9, mnemonic: 'CMP', mode: :imm16, length: 3, m: 0 },
116
+ { opcode: 0xcd, mnemonic: 'CMP', mode: :abs, length: 3 },
117
+ { opcode: 0xcf, mnemonic: 'CMP', mode: :abl, length: 4 },
118
+ { opcode: 0xd1, mnemonic: 'CMP', mode: :idy, length: 2 },
119
+ { opcode: 0xd2, mnemonic: 'CMP', mode: :idp, length: 2 },
120
+ { opcode: 0xd3, mnemonic: 'CMP', mode: :isy, length: 2 },
121
+ { opcode: 0xd5, mnemonic: 'CMP', mode: :dpx, length: 2 },
122
+ { opcode: 0xd7, mnemonic: 'CMP', mode: :idly, length: 2 },
123
+ { opcode: 0xd9, mnemonic: 'CMP', mode: :aby, length: 3 },
124
+ { opcode: 0xdd, mnemonic: 'CMP', mode: :abx, length: 3 },
125
+ { opcode: 0xdf, mnemonic: 'CMP', mode: :alx, length: 4 },
126
+ { opcode: 0xe0, mnemonic: 'CPX', mode: :imm8, length: 2, x: 1 },
127
+ { opcode: 0xe0, mnemonic: 'CPX', mode: :imm16, length: 3, x: 0 },
128
+ { opcode: 0xe4, mnemonic: 'CPX', mode: :dp, length: 2 },
129
+ { opcode: 0xec, mnemonic: 'CPX', mode: :abs, length: 3 },
130
+ { opcode: 0xc0, mnemonic: 'CPY', mode: :imm8, length: 2, x: 1 },
131
+ { opcode: 0xc0, mnemonic: 'CPY', mode: :imm16, length: 3, x: 0 },
132
+ { opcode: 0xc4, mnemonic: 'CPY', mode: :dp, length: 2 },
133
+ { opcode: 0xcc, mnemonic: 'CPY', mode: :abs, length: 3 },
134
+ { opcode: 0x3a, mnemonic: 'DEC', mode: :acc, length: 1 },
135
+ { opcode: 0xc6, mnemonic: 'DEC', mode: :dp, length: 2 },
136
+ { opcode: 0xce, mnemonic: 'DEC', mode: :abs, length: 3 },
137
+ { opcode: 0xd6, mnemonic: 'DEC', mode: :dpx, length: 2 },
138
+ { opcode: 0xde, mnemonic: 'DEC', mode: :abx, length: 3 },
139
+ { opcode: 0xca, mnemonic: 'DEX', mode: :imp, length: 1 },
140
+ { opcode: 0x88, mnemonic: 'DEY', mode: :imp, length: 1 },
141
+ { opcode: 0x1a, mnemonic: 'INC', mode: :acc, length: 1 },
142
+ { opcode: 0xe6, mnemonic: 'INC', mode: :dp, length: 2 },
143
+ { opcode: 0xee, mnemonic: 'INC', mode: :abs, length: 3 },
144
+ { opcode: 0xf6, mnemonic: 'INC', mode: :dpx, length: 2 },
145
+ { opcode: 0xfe, mnemonic: 'INC', mode: :abx, length: 3 },
146
+ { opcode: 0xe8, mnemonic: 'INX', mode: :imp, length: 1 },
147
+ { opcode: 0xc8, mnemonic: 'INY', mode: :imp, length: 1 },
148
+ { opcode: 0x21, mnemonic: 'AND', mode: :idx, length: 2 },
149
+ { opcode: 0x23, mnemonic: 'AND', mode: :sr, length: 2 },
150
+ { opcode: 0x25, mnemonic: 'AND', mode: :dp, length: 2 },
151
+ { opcode: 0x27, mnemonic: 'AND', mode: :idl, length: 2 },
152
+ { opcode: 0x29, mnemonic: 'AND', mode: :imm8, length: 2, m: 1 },
153
+ { opcode: 0x29, mnemonic: 'AND', mode: :imm16, length: 3, m: 0 },
154
+ { opcode: 0x2d, mnemonic: 'AND', mode: :abs, length: 3 },
155
+ { opcode: 0x2f, mnemonic: 'AND', mode: :abl, length: 4 },
156
+ { opcode: 0x31, mnemonic: 'AND', mode: :idy, length: 2 },
157
+ { opcode: 0x32, mnemonic: 'AND', mode: :idp, length: 2 },
158
+ { opcode: 0x33, mnemonic: 'AND', mode: :isy, length: 2 },
159
+ { opcode: 0x35, mnemonic: 'AND', mode: :dpx, length: 2 },
160
+ { opcode: 0x37, mnemonic: 'AND', mode: :idly, length: 2 },
161
+ { opcode: 0x39, mnemonic: 'AND', mode: :aby, length: 3 },
162
+ { opcode: 0x3d, mnemonic: 'AND', mode: :abx, length: 3 },
163
+ { opcode: 0x3f, mnemonic: 'AND', mode: :alx, length: 4 },
164
+ { opcode: 0x41, mnemonic: 'EOR', mode: :idx, length: 2 },
165
+ { opcode: 0x43, mnemonic: 'EOR', mode: :sr, length: 2 },
166
+ { opcode: 0x45, mnemonic: 'EOR', mode: :dp, length: 2 },
167
+ { opcode: 0x47, mnemonic: 'EOR', mode: :idl, length: 2 },
168
+ { opcode: 0x49, mnemonic: 'EOR', mode: :imm8, length: 2, m: 1 },
169
+ { opcode: 0x49, mnemonic: 'EOR', mode: :imm16, length: 3, m: 0 },
170
+ { opcode: 0x4d, mnemonic: 'EOR', mode: :abs, length: 3 },
171
+ { opcode: 0x4f, mnemonic: 'EOR', mode: :abl, length: 4 },
172
+ { opcode: 0x51, mnemonic: 'EOR', mode: :idy, length: 2 },
173
+ { opcode: 0x52, mnemonic: 'EOR', mode: :idp, length: 2 },
174
+ { opcode: 0x53, mnemonic: 'EOR', mode: :isy, length: 2 },
175
+ { opcode: 0x55, mnemonic: 'EOR', mode: :dpx, length: 2 },
176
+ { opcode: 0x57, mnemonic: 'EOR', mode: :idly, length: 2 },
177
+ { opcode: 0x59, mnemonic: 'EOR', mode: :aby, length: 3 },
178
+ { opcode: 0x5d, mnemonic: 'EOR', mode: :abx, length: 3 },
179
+ { opcode: 0x5f, mnemonic: 'EOR', mode: :alx, length: 4 },
180
+ { opcode: 0x01, mnemonic: 'ORA', mode: :idx, length: 2 },
181
+ { opcode: 0x03, mnemonic: 'ORA', mode: :sr, length: 2 },
182
+ { opcode: 0x05, mnemonic: 'ORA', mode: :dp, length: 2 },
183
+ { opcode: 0x07, mnemonic: 'ORA', mode: :idl, length: 2 },
184
+ { opcode: 0x09, mnemonic: 'ORA', mode: :imm8, length: 2, m: 1 },
185
+ { opcode: 0x09, mnemonic: 'ORA', mode: :imm16, length: 3, m: 0 },
186
+ { opcode: 0x0d, mnemonic: 'ORA', mode: :abs, length: 3 },
187
+ { opcode: 0x0f, mnemonic: 'ORA', mode: :abl, length: 4 },
188
+ { opcode: 0x11, mnemonic: 'ORA', mode: :idy, length: 2 },
189
+ { opcode: 0x12, mnemonic: 'ORA', mode: :idp, length: 2 },
190
+ { opcode: 0x13, mnemonic: 'ORA', mode: :isy, length: 2 },
191
+ { opcode: 0x15, mnemonic: 'ORA', mode: :dpx, length: 2 },
192
+ { opcode: 0x17, mnemonic: 'ORA', mode: :idly, length: 2 },
193
+ { opcode: 0x19, mnemonic: 'ORA', mode: :aby, length: 3 },
194
+ { opcode: 0x1d, mnemonic: 'ORA', mode: :abx, length: 3 },
195
+ { opcode: 0x1f, mnemonic: 'ORA', mode: :alx, length: 4 },
196
+ { opcode: 0x24, mnemonic: 'BIT', mode: :dp, length: 2 },
197
+ { opcode: 0x2c, mnemonic: 'BIT', mode: :abs, length: 3 },
198
+ { opcode: 0x34, mnemonic: 'BIT', mode: :dpx, length: 2 },
199
+ { opcode: 0x3c, mnemonic: 'BIT', mode: :abx, length: 3 },
200
+ { opcode: 0x89, mnemonic: 'BIT', mode: :imm8, length: 2, m: 1 },
201
+ { opcode: 0x89, mnemonic: 'BIT', mode: :imm16, length: 3, m: 0 },
202
+ { opcode: 0x14, mnemonic: 'TRB', mode: :dp, length: 2 },
203
+ { opcode: 0x1c, mnemonic: 'TRB', mode: :abs, length: 3 },
204
+ { opcode: 0x04, mnemonic: 'TSB', mode: :dp, length: 2 },
205
+ { opcode: 0x0c, mnemonic: 'TSB', mode: :abs, length: 3 },
206
+ { opcode: 0x06, mnemonic: 'ASL', mode: :dp, length: 2 },
207
+ { opcode: 0x0a, mnemonic: 'ASL', mode: :acc, length: 1 },
208
+ { opcode: 0x0e, mnemonic: 'ASL', mode: :abs, length: 3 },
209
+ { opcode: 0x16, mnemonic: 'ASL', mode: :dpx, length: 2 },
210
+ { opcode: 0x1e, mnemonic: 'ASL', mode: :abx, length: 3 },
211
+ { opcode: 0x46, mnemonic: 'LSR', mode: :dp, length: 2 },
212
+ { opcode: 0x4a, mnemonic: 'LSR', mode: :acc, length: 1 },
213
+ { opcode: 0x4e, mnemonic: 'LSR', mode: :abs, length: 3 },
214
+ { opcode: 0x56, mnemonic: 'LSR', mode: :dpx, length: 2 },
215
+ { opcode: 0x5e, mnemonic: 'LSR', mode: :abx, length: 3 },
216
+ { opcode: 0x26, mnemonic: 'ROL', mode: :dp, length: 2 },
217
+ { opcode: 0x2a, mnemonic: 'ROL', mode: :acc, length: 1 },
218
+ { opcode: 0x2e, mnemonic: 'ROL', mode: :abs, length: 3 },
219
+ { opcode: 0x36, mnemonic: 'ROL', mode: :dpx, length: 2 },
220
+ { opcode: 0x3e, mnemonic: 'ROL', mode: :abx, length: 3 },
221
+ { opcode: 0x66, mnemonic: 'ROR', mode: :dp, length: 2 },
222
+ { opcode: 0x6a, mnemonic: 'ROR', mode: :acc, length: 1 },
223
+ { opcode: 0x6e, mnemonic: 'ROR', mode: :abs, length: 3 },
224
+ { opcode: 0x76, mnemonic: 'ROR', mode: :dpx, length: 2 },
225
+ { opcode: 0x7e, mnemonic: 'ROR', mode: :abx, length: 3 },
226
+ { opcode: 0x90, mnemonic: 'BCC', mode: :rel, length: 2 },
227
+ { opcode: 0xb0, mnemonic: 'BCS', mode: :rel, length: 2 },
228
+ { opcode: 0xf0, mnemonic: 'BEQ', mode: :rel, length: 2 },
229
+ { opcode: 0x30, mnemonic: 'BMI', mode: :rel, length: 2 },
230
+ { opcode: 0xd0, mnemonic: 'BNE', mode: :rel, length: 2 },
231
+ { opcode: 0x10, mnemonic: 'BPL', mode: :rel, length: 2 },
232
+ { opcode: 0x80, mnemonic: 'BRA', mode: :rel, length: 2 },
233
+ { opcode: 0x50, mnemonic: 'BVC', mode: :rel, length: 2 },
234
+ { opcode: 0x70, mnemonic: 'BVS', mode: :rel, length: 2 },
235
+ { opcode: 0x82, mnemonic: 'BRL', mode: :rell, length: 3 },
236
+ { opcode: 0x4c, mnemonic: 'JMP', mode: :abs, length: 3 },
237
+ { opcode: 0x5c, mnemonic: 'JMP', mode: :abl, length: 4 },
238
+ { opcode: 0x6c, mnemonic: 'JMP', mode: :ind, length: 3 },
239
+ { opcode: 0x7c, mnemonic: 'JMP', mode: :iax, length: 3 },
240
+ { opcode: 0xdc, mnemonic: 'JMP', mode: :ial, length: 3 },
241
+ { opcode: 0x22, mnemonic: 'JSL', mode: :abl, length: 4 },
242
+ { opcode: 0x20, mnemonic: 'JSR', mode: :abs, length: 3 },
243
+ { opcode: 0xfc, mnemonic: 'JSR', mode: :iax, length: 3 },
244
+ { opcode: 0x6b, mnemonic: 'RTL', mode: :imp, length: 1 },
245
+ { opcode: 0x60, mnemonic: 'RTS', mode: :imp, length: 1 },
246
+ { opcode: 0x00, mnemonic: 'BRK', mode: :imm, length: 2 },
247
+ { opcode: 0x02, mnemonic: 'COP', mode: :imm, length: 2 },
248
+ { opcode: 0x40, mnemonic: 'RTI', mode: :imp, length: 1 },
249
+ { opcode: 0x18, mnemonic: 'CLC', mode: :imp, length: 1 },
250
+ { opcode: 0xd8, mnemonic: 'CLD', mode: :imp, length: 1 },
251
+ { opcode: 0x58, mnemonic: 'CLI', mode: :imp, length: 1 },
252
+ { opcode: 0xb8, mnemonic: 'CLV', mode: :imp, length: 1 },
253
+ { opcode: 0x38, mnemonic: 'SEC', mode: :imp, length: 1 },
254
+ { opcode: 0xf8, mnemonic: 'SED', mode: :imp, length: 1 },
255
+ { opcode: 0x78, mnemonic: 'SEI', mode: :imp, length: 1 },
256
+ { opcode: 0xc2, mnemonic: 'REP', mode: :imm8, length: 2 },
257
+ { opcode: 0xe2, mnemonic: 'SEP', mode: :imm8, length: 2 },
258
+ { opcode: 0xa1, mnemonic: 'LDA', mode: :idx, length: 2 },
259
+ { opcode: 0xa3, mnemonic: 'LDA', mode: :sr, length: 2 },
260
+ { opcode: 0xa5, mnemonic: 'LDA', mode: :dp, length: 2 },
261
+ { opcode: 0xa7, mnemonic: 'LDA', mode: :idl, length: 2 },
262
+ { opcode: 0xa9, mnemonic: 'LDA', mode: :imm8, length: 2, m: 1 },
263
+ { opcode: 0xa9, mnemonic: 'LDA', mode: :imm16, length: 3, m: 0 },
264
+ { opcode: 0xad, mnemonic: 'LDA', mode: :abs, length: 3 },
265
+ { opcode: 0xaf, mnemonic: 'LDA', mode: :abl, length: 4 },
266
+ { opcode: 0xb1, mnemonic: 'LDA', mode: :idy, length: 2 },
267
+ { opcode: 0xb2, mnemonic: 'LDA', mode: :idp, length: 2 },
268
+ { opcode: 0xb3, mnemonic: 'LDA', mode: :isy, length: 2 },
269
+ { opcode: 0xb5, mnemonic: 'LDA', mode: :dpx, length: 2 },
270
+ { opcode: 0xb7, mnemonic: 'LDA', mode: :idly, length: 2 },
271
+ { opcode: 0xb9, mnemonic: 'LDA', mode: :aby, length: 3 },
272
+ { opcode: 0xbd, mnemonic: 'LDA', mode: :abx, length: 3 },
273
+ { opcode: 0xbf, mnemonic: 'LDA', mode: :alx, length: 4 },
274
+ { opcode: 0xa2, mnemonic: 'LDX', mode: :imm8, length: 2, x: 1 },
275
+ { opcode: 0xa2, mnemonic: 'LDX', mode: :imm16, length: 3, x: 0 },
276
+ { opcode: 0xa6, mnemonic: 'LDX', mode: :dp, length: 2 },
277
+ { opcode: 0xae, mnemonic: 'LDX', mode: :abs, length: 3 },
278
+ { opcode: 0xb6, mnemonic: 'LDX', mode: :dpy, length: 2 },
279
+ { opcode: 0xbe, mnemonic: 'LDX', mode: :aby, length: 3 },
280
+ { opcode: 0xa0, mnemonic: 'LDY', mode: :imm8, length: 2, x: 1 },
281
+ { opcode: 0xa0, mnemonic: 'LDY', mode: :imm16, length: 3, x: 0 },
282
+ { opcode: 0xa4, mnemonic: 'LDY', mode: :dp, length: 2 },
283
+ { opcode: 0xac, mnemonic: 'LDY', mode: :abs, length: 3 },
284
+ { opcode: 0xb4, mnemonic: 'LDY', mode: :dpx, length: 2 },
285
+ { opcode: 0xbc, mnemonic: 'LDY', mode: :abx, length: 3 },
286
+ { opcode: 0x81, mnemonic: 'STA', mode: :idx, length: 2 },
287
+ { opcode: 0x83, mnemonic: 'STA', mode: :sr, length: 2 },
288
+ { opcode: 0x85, mnemonic: 'STA', mode: :dp, length: 2 },
289
+ { opcode: 0x87, mnemonic: 'STA', mode: :idl, length: 2 },
290
+ { opcode: 0x8d, mnemonic: 'STA', mode: :abs, length: 3 },
291
+ { opcode: 0x8f, mnemonic: 'STA', mode: :abl, length: 4 },
292
+ { opcode: 0x91, mnemonic: 'STA', mode: :idy, length: 2 },
293
+ { opcode: 0x92, mnemonic: 'STA', mode: :idp, length: 2 },
294
+ { opcode: 0x93, mnemonic: 'STA', mode: :isy, length: 2 },
295
+ { opcode: 0x95, mnemonic: 'STA', mode: :dpx, length: 2 },
296
+ { opcode: 0x97, mnemonic: 'STA', mode: :idly, length: 2 },
297
+ { opcode: 0x99, mnemonic: 'STA', mode: :aby, length: 3 },
298
+ { opcode: 0x9d, mnemonic: 'STA', mode: :abx, length: 3 },
299
+ { opcode: 0x9f, mnemonic: 'STA', mode: :alx, length: 4 },
300
+ { opcode: 0x86, mnemonic: 'STX', mode: :dp, length: 2 },
301
+ { opcode: 0x8e, mnemonic: 'STX', mode: :abs, length: 3 },
302
+ { opcode: 0x96, mnemonic: 'STX', mode: :dpy, length: 2 },
303
+ { opcode: 0x84, mnemonic: 'STY', mode: :dp, length: 2 },
304
+ { opcode: 0x8c, mnemonic: 'STY', mode: :abs, length: 3 },
305
+ { opcode: 0x94, mnemonic: 'STY', mode: :dpx, length: 2 },
306
+ { opcode: 0x64, mnemonic: 'STZ', mode: :dp, length: 2 },
307
+ { opcode: 0x74, mnemonic: 'STZ', mode: :dpx, length: 2 },
308
+ { opcode: 0x9c, mnemonic: 'STZ', mode: :abs, length: 3 },
309
+ { opcode: 0x9e, mnemonic: 'STZ', mode: :abx, length: 3 },
310
+ { opcode: 0x54, mnemonic: 'MVN', mode: :bm, length: 3 },
311
+ { opcode: 0x44, mnemonic: 'MVP', mode: :bm, length: 3 },
312
+ { opcode: 0xea, mnemonic: 'NOP', mode: :imp, length: 1 },
313
+ { opcode: 0x42, mnemonic: 'WDM', mode: :imm, length: 2 },
314
+ { opcode: 0xf4, mnemonic: 'PEA', mode: :iml, length: 3 },
315
+ { opcode: 0xd4, mnemonic: 'PEI', mode: :dp, length: 2 },
316
+ { opcode: 0x62, mnemonic: 'PER', mode: :rell, length: 3 },
317
+ { opcode: 0x48, mnemonic: 'PHA', mode: :imp, length: 1 },
318
+ { opcode: 0xda, mnemonic: 'PHX', mode: :imp, length: 1 },
319
+ { opcode: 0x5a, mnemonic: 'PHY', mode: :imp, length: 1 },
320
+ { opcode: 0x68, mnemonic: 'PLA', mode: :imp, length: 1 },
321
+ { opcode: 0xfa, mnemonic: 'PLX', mode: :imp, length: 1 },
322
+ { opcode: 0x7a, mnemonic: 'PLY', mode: :imp, length: 1 },
323
+ { opcode: 0x8b, mnemonic: 'PHB', mode: :imp, length: 1 },
324
+ { opcode: 0x0b, mnemonic: 'PHD', mode: :imp, length: 1 },
325
+ { opcode: 0x4b, mnemonic: 'PHK', mode: :imp, length: 1 },
326
+ { opcode: 0x08, mnemonic: 'PHP', mode: :imp, length: 1 },
327
+ { opcode: 0xab, mnemonic: 'PLB', mode: :imp, length: 1 },
328
+ { opcode: 0x2b, mnemonic: 'PLD', mode: :imp, length: 1 },
329
+ { opcode: 0x28, mnemonic: 'PLP', mode: :imp, length: 1 },
330
+ { opcode: 0xdb, mnemonic: 'STP', mode: :imp, length: 1 },
331
+ { opcode: 0xcb, mnemonic: 'WAI', mode: :imp, length: 1 },
332
+ { opcode: 0xaa, mnemonic: 'TAX', mode: :imp, length: 1 },
333
+ { opcode: 0xa8, mnemonic: 'TAY', mode: :imp, length: 1 },
334
+ { opcode: 0xba, mnemonic: 'TSX', mode: :imp, length: 1 },
335
+ { opcode: 0x8a, mnemonic: 'TXA', mode: :imp, length: 1 },
336
+ { opcode: 0x9a, mnemonic: 'TXS', mode: :imp, length: 1 },
337
+ { opcode: 0x9b, mnemonic: 'TXY', mode: :imp, length: 1 },
338
+ { opcode: 0x98, mnemonic: 'TYA', mode: :imp, length: 1 },
339
+ { opcode: 0xbb, mnemonic: 'TYX', mode: :imp, length: 1 },
340
+ { opcode: 0x5b, mnemonic: 'TCD', mode: :imp, length: 1 },
341
+ { opcode: 0x1b, mnemonic: 'TCS', mode: :imp, length: 1 },
342
+ { opcode: 0x7b, mnemonic: 'TDC', mode: :imp, length: 1 },
343
+ { opcode: 0x3b, mnemonic: 'TSC', mode: :imp, length: 1 },
344
+ { opcode: 0xeb, mnemonic: 'XBA', mode: :imp, length: 1 },
345
+ { opcode: 0xfb, mnemonic: 'XCE', mode: :imp, length: 1 }].freeze
346
+ end
347
+ end
@@ -0,0 +1,326 @@
1
+ module SnesUtils
2
+ Readline.completion_proc = proc do |input|
3
+ Definitions::OPCODES_DATA.map { |row| row[:mnemonic] }
4
+ .select { |mnemonic| mnemonic.upcase.start_with?(input.upcase) }
5
+ end
6
+
7
+ class MiniAssembler
8
+ def initialize(filename = nil)
9
+ if filename && File.file?(filename)
10
+ file = File.open(filename)
11
+ @memory = file.each_byte.map { |b| hex(b) }
12
+ else
13
+ @memory = []
14
+ end
15
+
16
+ @normal_mode = true
17
+
18
+ @current_addr = 0
19
+ @current_bank_no = 0
20
+ @accumulator_flag = 1
21
+ @index_flag = 1
22
+
23
+ @next_addr_to_list = 0
24
+ end
25
+
26
+ def run
27
+ while line = getline
28
+ result = parse_line(line.strip.chomp)
29
+ puts result if result
30
+ end
31
+
32
+ puts
33
+ end
34
+
35
+ def hex(num, rjust_len = 2)
36
+ num.to_s(16).rjust(rjust_len, '0').upcase
37
+ end
38
+
39
+ def write(filename = 'out.smc')
40
+ filename = filename.empty? ? 'out.smc' : filename
41
+
42
+ File.open(filename, 'w+b') do |file|
43
+ file.write([@memory.map { |i| i ? i : '00' }.join].pack('H*'))
44
+ end
45
+
46
+ filename
47
+ end
48
+
49
+ def incbin(filename, addr)
50
+ return 0 unless File.file?(filename)
51
+
52
+ file = File.open(filename)
53
+ bytes = file.each_byte.map { |b| hex(b) }
54
+
55
+ replace_memory_range(addr, addr + bytes.size - 1, bytes)
56
+
57
+ bytes.size
58
+ end
59
+
60
+ def read(filename)
61
+ return 0 unless File.file?(filename)
62
+
63
+ file = File.open(filename)
64
+
65
+ instructions = []
66
+
67
+ file.each_with_index do |raw_line, line_no|
68
+ line = raw_line.split(';').first.strip.chomp
69
+ next if line.empty?
70
+
71
+ instruction, length, address = parse_instruction(line)
72
+ return "Error at line #{line_no + 1}" unless instruction
73
+
74
+ instructions << [instruction, length, address]
75
+ @current_addr = address + length
76
+ end
77
+
78
+ disassembled_instructions = []
79
+ instructions.map do |instruction_arr|
80
+ instruction, length, address = instruction_arr
81
+ replace_memory_range(address, address+length-1, instruction)
82
+ disassembled_instructions << disassemble_range(address, 1, length > 2).join
83
+ end
84
+
85
+ return disassembled_instructions
86
+ end
87
+
88
+ def detect_opcode_data_from_mnemonic(mnemonic, operand)
89
+ Definitions::OPCODES_DATA.detect do |row|
90
+ mode = row[:mode]
91
+ regex = Definitions::MODES_REGEXES[mode]
92
+ row[:mnemonic] == mnemonic && regex =~ operand
93
+ end
94
+ end
95
+
96
+ def detect_opcode_data_from_opcode(opcode, force_length)
97
+ Definitions::OPCODES_DATA.detect do |row|
98
+ if row[:m]
99
+ accumulator_flag = force_length ? 0 : @accumulator_flag
100
+ row[:opcode] == opcode && row[:m] == accumulator_flag
101
+ elsif row[:x]
102
+ index_flag = force_length ? 0 : @index_flag
103
+ row[:opcode] == opcode && row[:x] == index_flag
104
+ else
105
+ row[:opcode] == opcode
106
+ end
107
+ end
108
+ end
109
+
110
+ def full_address(address)
111
+ (@current_bank_no << 16) | address
112
+ end
113
+
114
+ def address_human(addr=nil)
115
+ address = full_address(addr || @current_addr)
116
+ bank = address >> 16
117
+ addr = (((address>>8)&0xFF) << 8) | (address&0xFF)
118
+ "#{hex(bank)}/#{hex(addr, 4)}"
119
+ end
120
+
121
+ def memory_loc(address)
122
+ @memory[full_address(address)]
123
+ end
124
+
125
+ def memory_range(start_addr, end_addr)
126
+ start_full_addr = full_address(start_addr)
127
+ end_full_addr = full_address(end_addr)
128
+
129
+ return [] if start_full_addr > end_full_addr || start_full_addr >= @memory.length
130
+
131
+ @memory[start_full_addr..end_full_addr]
132
+ end
133
+
134
+ def replace_memory_range(start_addr, end_addr, bytes)
135
+ start_full_addr = full_address(start_addr)
136
+ end_full_addr = full_address(end_addr)
137
+
138
+ @memory[start_full_addr..end_full_addr] = bytes
139
+
140
+ true
141
+ end
142
+
143
+ def getline
144
+ prompt = @normal_mode ? "(#{@accumulator_flag}=m #{@index_flag}=x)*" : "(#{address_human})!"
145
+ Readline.readline(prompt, true)
146
+ end
147
+
148
+ def parse_line(line)
149
+ if @normal_mode
150
+ if line == '!'
151
+ @normal_mode = false
152
+ return
153
+ elsif matches = Definitions::WRITE_REGEX.match(line)
154
+ filename = matches[1].strip.chomp
155
+ out_filename = write(filename)
156
+ return "Written #{@memory.size} bytes to file #{out_filename}"
157
+ elsif matches = Definitions::READ_REGEX.match(line)
158
+ filename = matches[1].strip.chomp
159
+ return read(filename)
160
+ elsif matches = Definitions::INCBIN_REGEX.match(line)
161
+ start_addr = matches[1].to_i(16)
162
+ filename = matches[2].strip.chomp
163
+ nb_bytes = incbin(filename, start_addr)
164
+
165
+ return "Inserted #{nb_bytes} bytes at #{address_human(start_addr)}"
166
+ elsif Definitions::BYTE_LOC_REGEX =~ line
167
+ return memory_loc(line.to_i(16))
168
+ elsif matches = Definitions::BYTE_RANGE_REGEX.match(line)
169
+ start_addr = matches[1].to_i(16)
170
+ end_addr = matches[2].to_i(16)
171
+
172
+ padding_count = start_addr % 8
173
+ padding = (1..padding_count).map { |b| ' ' }
174
+ arr = memory_range(start_addr, end_addr)
175
+ return if arr.empty?
176
+
177
+ padded_arr = arr.insert(8-padding_count, *padding).each_slice(8).to_a
178
+ return padded_arr.each_with_index.map do |row, idx|
179
+ if idx == 0
180
+ line_addr = start_addr
181
+ else
182
+ line_addr = start_addr - padding_count + idx * 8
183
+ end
184
+ ["#{address_human(line_addr)}-", *row].join(' ')
185
+ end.join("\n")
186
+ elsif matches = Definitions::BYTE_SEQUENCE_REGEX.match(line)
187
+ addr = matches[1].to_i(16)
188
+ bytes = matches[2].delete(' ').scan(/.{1,2}/).map { |b| hex(b.to_i(16)) }
189
+ replace_memory_range(addr, addr + bytes.length - 1, bytes)
190
+ return
191
+ elsif matches = Definitions::DISASSEMBLE_REGEX.match(line)
192
+ start = matches[1].empty? ? @next_addr_to_list : matches[1].to_i(16)
193
+ return disassemble_range(start, 20).join("\n")
194
+ elsif matches = Definitions::SWITCH_BANK_REGEX.match(line)
195
+ target_bank_no = matches[1].to_i(16)
196
+ @current_bank_no = target_bank_no
197
+ @current_addr = @current_bank_no << 16
198
+ @next_addr_to_list = 0
199
+ return
200
+ elsif matches = Definitions::FLIP_MX_REG_REGEX.match(line)
201
+ val = matches[1]
202
+ reg = matches[2]
203
+
204
+ if reg.downcase == 'm'
205
+ @accumulator_flag = val.to_i
206
+ elsif reg.downcase == 'x'
207
+ @index_flag = val.to_i
208
+ end
209
+
210
+ return
211
+ end
212
+ else
213
+ if line == ''
214
+ @normal_mode = true
215
+ return
216
+ else
217
+ instruction, length, address = parse_instruction(line)
218
+ return 'error' unless instruction
219
+
220
+ replace_memory_range(address, address+length-1, instruction)
221
+ @current_addr = address + length
222
+ return disassemble_range(address, 1, length > 2).join
223
+ end
224
+ end
225
+ end
226
+
227
+ def parse_address(line)
228
+ return @current_addr if line.index(':').nil?
229
+ line.split(':').first.to_i(16)
230
+ end
231
+
232
+ def parse_instruction(line)
233
+ current_address = parse_address(line)
234
+ instruction = line.split(':').last.split(' ')
235
+ mnemonic = instruction[0].upcase
236
+ raw_operand = instruction[1].to_s
237
+
238
+ opcode_data = detect_opcode_data_from_mnemonic(mnemonic, raw_operand)
239
+
240
+ return unless opcode_data
241
+
242
+ opcode = hex(opcode_data[:opcode])
243
+ mode = opcode_data[:mode]
244
+ length = opcode_data[:length]
245
+
246
+ operand_matches = Definitions::MODES_REGEXES[mode].match(raw_operand)
247
+ if mode == :bm
248
+ operand = "#{operand_matches[1]}#{operand_matches[2]}".to_i(16)
249
+ else
250
+ operand = operand_matches[1]&.to_i(16)
251
+ end
252
+
253
+ if operand
254
+ if %i[rel rell].include? mode
255
+ relative_addr = operand - current_address - length
256
+ return if mode == :rel && (relative_addr < -128 || relative_addr > 127)
257
+ return if mode == :rell && (relative_addr < -32768 || relative_addr > 32767)
258
+
259
+ relative_addr = (2**(8*(length-1))) + relative_addr if relative_addr < 0
260
+
261
+ param_bytes = hex(relative_addr, 2*(length-1)).scan(/.{2}/).reverse.join
262
+ else
263
+ param_bytes = hex(operand, 2*(length-1)).scan(/.{2}/).reverse.join
264
+ end
265
+ end
266
+
267
+ encoded_result = "#{opcode}#{param_bytes}"
268
+
269
+ return [encoded_result.scan(/.{2}/), length, current_address]
270
+ end
271
+
272
+ def auto_update_flags(opcode, operand)
273
+ if 0xc2 == opcode
274
+ @index_flag = 0 if (operand & 0x10) == 0x10
275
+ @accumulator_flag = 0 if (operand & 0x20) == 0x20
276
+ elsif 0xe2 == opcode
277
+ @index_flag = 1 if (operand & 0x10) == 0x10
278
+ @accumulator_flag = 1 if (operand & 0x20) == 0x20
279
+ end
280
+ end
281
+
282
+ def disassemble_range(start, count, force_length = false)
283
+ next_idx = start
284
+ instructions = []
285
+ count.times do
286
+ byte = memory_loc(next_idx)
287
+ break unless byte
288
+ opcode = byte.to_i(16)
289
+
290
+ opcode_data = detect_opcode_data_from_opcode(opcode, force_length)
291
+
292
+ mnemonic = opcode_data[:mnemonic]
293
+ mode = opcode_data[:mode]
294
+ length = opcode_data[:length]
295
+
296
+ format = Definitions::MODES_FORMATS[mode]
297
+
298
+ operand = memory_range(next_idx+1, next_idx+length-1).reverse.join.to_i(16)
299
+
300
+ hex_encoded_instruction = memory_range(next_idx, next_idx+length-1)
301
+ prefix = ["#{address_human(next_idx)}:", *hex_encoded_instruction].join(' ')
302
+
303
+ auto_update_flags(opcode, operand)
304
+
305
+ if mode == :bm
306
+ operand = operand.to_s(16).scan(/.{2}/).map { |op| op.to_i(16) }
307
+ elsif %i[rel rell].include? mode
308
+ limit = mode == :rel ? 0x7f : 0x7fff
309
+ offset = mode == :rel ? 0x100 : 0x10000
310
+ rjust_len = mode == :rel ? 2 : 4
311
+ relative_addr = operand > limit ? operand - offset : operand
312
+ relative_addr_s = "#{relative_addr.positive? ? '+' : '-'}#{hex(relative_addr.abs, rjust_len)}"
313
+ absolute_addr = next_idx + length + relative_addr
314
+ absolute_addr += 0x10000 if absolute_addr.negative?
315
+ operand = [absolute_addr, relative_addr_s]
316
+ end
317
+
318
+ instructions << "#{prefix.ljust(30)} #{format % [mnemonic, *operand]}"
319
+ next_idx += length
320
+ end
321
+
322
+ @next_addr_to_list = next_idx
323
+ return instructions
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,114 @@
1
+ module SnesUtils
2
+ class Png2Snes
3
+ def initialize(file_path, bpp:4, alpha:nil)
4
+ @file_path = file_path
5
+ @file_dir = File.dirname(@file_path)
6
+ @file_name = File.basename(@file_path, File.extname(@file_path))
7
+ @image = ChunkyPNG::Image.from_file(@file_path)
8
+ @pixels = pixels_to_bgr5
9
+
10
+ @palette = @pixels.uniq
11
+ @char_size = 8
12
+
13
+ raise ArgumentError, 'BPP must be 2, 4, or 8' unless [2, 4, 8].include? bpp
14
+ @bpp = bpp
15
+
16
+ raise ArgumentError, 'Image width and height must be a multiple of sprite size' if (@image.width % @char_size != 0) or (@image.height % @char_size != 0)
17
+
18
+ alpha_first if alpha
19
+ fill_palette
20
+ end
21
+
22
+ def pixels_to_bgr5
23
+ @image.pixels.map do |c|
24
+ r = ((c >> 24) & 0xff) >> 3
25
+ g = ((c >> 16) & 0xff) >> 3
26
+ b = ((c >> 8) & 0xff) >> 3
27
+
28
+ r | (g << 5) | (b << 10)
29
+ end
30
+ end
31
+
32
+ def alpha_first
33
+ end
34
+
35
+ def fill_palette
36
+ target_size = 2**@bpp
37
+ missing_colors = target_size - @palette.count
38
+ raise ArgumentError, "Palette size too large for target BPP (#{@palette.count})" if missing_colors < 0
39
+
40
+ @palette += [0] * missing_colors
41
+ end
42
+
43
+ def write hex, file_path
44
+ File.open(file_path, 'w+b') do |file|
45
+ file.write([hex.join].pack('H*'))
46
+ end
47
+ end
48
+
49
+ def write_palette
50
+ palette_hex = @palette.map { |c| ('%04x' % c).scan(/.{2}/).reverse.join }
51
+ write palette_hex, File.expand_path("#{@file_name}-pal.bin", @file_dir)
52
+ end
53
+
54
+ def pixel_indices
55
+ pix_idx = @pixels.map { |p| @palette.index(p) }
56
+ pix_idx_bin = pix_idx.map { |i| "%0#{@bpp}b" % i }
57
+ pix_idx_bin.map { |i| i.reverse }
58
+ end
59
+
60
+ def extract_sprites
61
+ pixel_idx = pixel_indices
62
+
63
+ sprite_per_row = @image.width / @char_size
64
+ sprite_per_col = @image.height / @char_size
65
+ sprite_per_sheet = sprite_per_row * sprite_per_col
66
+
67
+ sprites = []
68
+ (0..sprite_per_sheet-1).each do |s|
69
+ sprite = []
70
+ (0..@char_size-1).each do |r|
71
+ offset = (s/sprite_per_row)*sprite_per_row * @char_size**2 + s % sprite_per_row * @char_size
72
+ sprite += pixel_idx[offset + r*sprite_per_row*@char_size, @char_size]
73
+ end
74
+ sprites.push(sprite)
75
+ end
76
+
77
+ sprites
78
+ end
79
+
80
+ def extract_bitplanes sprite
81
+ bitplanes = []
82
+ (0..@bpp-1).each do |plane|
83
+ bitplanes.push sprite.map { |p| p[plane] }
84
+ end
85
+
86
+ bitplanes
87
+ end
88
+
89
+ def write_image
90
+ sprite_per_row = @image.width / @char_size
91
+ sprites = extract_sprites
92
+ sprites_bitplanes = sprites.map { |s| extract_bitplanes s }
93
+
94
+ image_bits = ""
95
+ sprites_bitplanes.each do |sprite_bitplanes|
96
+ sprite_bitplane_pairs = sprite_bitplanes.each_slice(2).to_a
97
+
98
+ bitplane_bits = ""
99
+ sprite_bitplane_pairs.each do |bitplane|
100
+ (0..@char_size-1).each do |r|
101
+ offset = r*@char_size
102
+ bitplane_bits += bitplane[0][offset, @char_size].join + bitplane[1][offset, @char_size].join
103
+ end
104
+ end
105
+ image_bits += bitplane_bits
106
+
107
+ end
108
+
109
+ image_hex = image_bits.scan(/.{8}/).map { |b| "%02x" % b.to_i(2) }
110
+ write image_hex, File.expand_path("#{@file_name}.bin", @file_dir)
111
+ end
112
+
113
+ end
114
+ end
data/lib/snes_utils.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'readline'
2
+ require 'chunky_png'
3
+ require 'matrix'
4
+ require 'nokogiri'
5
+
6
+ require 'byebug'
7
+
8
+ require 'mini_assembler/mini_assembler'
9
+ require 'mini_assembler/definitions'
10
+ require 'png2snes/png2snes'
11
+ require 'tmx2snes/tmx2snes'
12
+
13
+ module SnesUtils
14
+ end
@@ -0,0 +1,57 @@
1
+ module SnesUtils
2
+ class Tmx2Snes
3
+ def initialize(file_path, big_char:false, palette:0, v:false, h:false, p:false)
4
+ raise unless File.file? file_path
5
+ @file_path = file_path
6
+ @file_dir = File.dirname(@file_path)
7
+ @file_name = File.basename(@file_path, File.extname(@file_path))
8
+ @tilemap = []
9
+ raise if palette < 0 || palette > 7
10
+ @palette = palette
11
+ @v_flip = v ? "1" : "0"
12
+ @h_flip = h ? "1" : "0"
13
+ @prio_bg3 = p ? "1" : "0"
14
+ tnm = big_char ? 2 : 1 # big_char : 16x16 tiles. otherwise, 8x8 tiles
15
+ row_offset = 16 * (tnm - 1) # Skip a row in case of 16x16 tiles ( tile #9 starts at index 32)
16
+
17
+ doc = Nokogiri::XML(File.open(@file_path))
18
+ csv_node = doc.xpath('//data').children.first.to_s
19
+
20
+ csv = csv_node.split("\n").compact.reject { |e| e.empty? }.map { |row| row.split(',') }
21
+
22
+ csv.each do |row|
23
+ raise if row.length != 32
24
+ @tilemap += row.map { |r| (r.to_i - 1)*tnm + row_offset * ((r.to_i - 1)/8).to_i }
25
+ end
26
+
27
+ raise if @tilemap.length != 32*32
28
+ dummy = 1023 # TODO: check max tile per tileset
29
+ raise if @tilemap.map { |t| t < 0 || t > dummy }.include? true
30
+ end
31
+
32
+ def tile_to_data tile
33
+ tile_name = "%010b" % tile
34
+ palette_name = "%03b" % @palette
35
+
36
+ tile_hl = @v_flip + @h_flip + @prio_bg3 + palette_name + tile_name
37
+ tile_data = tile_hl.scan(/.{8}/)
38
+ tile_data.reverse.map { |b| "%02x" % b.to_i(2) }
39
+
40
+ end
41
+
42
+ def tilemap_to_data
43
+ bg_sc_data = []
44
+ @tilemap.each do |tile|
45
+ bg_sc_data.push(tile_to_data(tile))
46
+ end
47
+ bg_sc_data
48
+ end
49
+
50
+ def write
51
+ out = File.expand_path("#{@file_name}.map", @file_dir)
52
+ File.open(out, 'w+b') do |file|
53
+ file.write([tilemap_to_data.join].pack('H*'))
54
+ end
55
+ end
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snes_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vivien Bihl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: byebug
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '11.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '11.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: chunky_png
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rb-readline
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ description: A collection of tools to create or edit SNES games
84
+ email: vivienbihl@gmail.com
85
+ executables:
86
+ - mini_assembler
87
+ - png2snes
88
+ - tmx2snes
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - bin/mini_assembler
93
+ - bin/png2snes
94
+ - bin/tmx2snes
95
+ - lib/mini_assembler/definitions.rb
96
+ - lib/mini_assembler/mini_assembler.rb
97
+ - lib/png2snes/png2snes.rb
98
+ - lib/snes_utils.rb
99
+ - lib/tmx2snes/tmx2snes.rb
100
+ homepage: https://rubygems.org/gems/snes_utils
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ source_code_uri: https://github.com/vivi168/SNES_Utils
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.0.6
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: SNES Utils
124
+ test_files: []