Handle a <param name="game_id"> by passing it in to the C side as
[sgt/puzzles] / PuzzleApplet.java
1 /*
2 * PuzzleApplet.java: NestedVM applet for the puzzle collection
3 */
4 import java.awt.*;
5 import java.awt.event.*;
6 import java.awt.image.BufferedImage;
7 import java.util.*;
8 import javax.swing.*;
9 import javax.swing.border.BevelBorder;
10 import javax.swing.Timer;
11 import java.util.List;
12
13 import org.ibex.nestedvm.Runtime;
14
15 public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
16
17 private static final long serialVersionUID = 1L;
18
19 private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2,
20 LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202,
21 LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205,
22 LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a,
23 CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000,
24 MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100,
25 ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0,
26 C_CHOICES = 1, C_BOOLEAN = 2;
27
28 private JFrame mainWindow;
29
30 private JMenu typeMenu;
31 private JMenuItem solveCommand;
32 private Color[] colors;
33 private JLabel statusBar;
34 private PuzzlePanel pp;
35 private Runtime runtime;
36 private String[] puzzle_args;
37 private Graphics2D gg;
38 private Timer timer;
39 private int xarg1, xarg2, xarg3;
40 private int[] xPoints, yPoints;
41 private BufferedImage[] blitters = new BufferedImage[512];
42 private ConfigDialog dlg;
43
44 static {
45 try {
46 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
47 } catch (Exception ex) {
48 ex.printStackTrace();
49 }
50 }
51
52 public void init() {
53 try {
54 Container cp = getContentPane();
55 cp.setLayout(new BorderLayout());
56 runtime = (Runtime) Class.forName("PuzzleEngine").newInstance();
57 runtime.setCallJavaCB(this);
58 JMenuBar menubar = new JMenuBar();
59 JMenu jm;
60 menubar.add(jm = new JMenu("Game"));
61 addMenuItemWithKey(jm, "New", 'n');
62 addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
63 addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
64 addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
65 jm.addSeparator();
66 addMenuItemWithKey(jm, "Undo", 'u');
67 addMenuItemWithKey(jm, "Redo", 'r');
68 jm.addSeparator();
69 solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
70 solveCommand.setEnabled(false);
71 if (mainWindow != null) {
72 jm.addSeparator();
73 addMenuItemWithKey(jm, "Exit", 'q');
74 }
75 menubar.add(typeMenu = new JMenu("Type"));
76 typeMenu.setVisible(false);
77 menubar.add(jm = new JMenu("Help"));
78 addMenuItemCallback(jm, "About", "jcallback_about_event");
79 setJMenuBar(menubar);
80 cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER);
81 pp.addKeyListener(new KeyAdapter() {
82 public void keyPressed(KeyEvent e) {
83 int key = -1;
84 int shift = e.isShiftDown() ? MOD_SHFT : 0;
85 int ctrl = e.isControlDown() ? MOD_CTRL : 0;
86 switch (e.getKeyCode()) {
87 case KeyEvent.VK_LEFT:
88 case KeyEvent.VK_KP_LEFT:
89 key = shift | ctrl | CURSOR_LEFT;
90 break;
91 case KeyEvent.VK_RIGHT:
92 case KeyEvent.VK_KP_RIGHT:
93 key = shift | ctrl | CURSOR_RIGHT;
94 break;
95 case KeyEvent.VK_UP:
96 case KeyEvent.VK_KP_UP:
97 key = shift | ctrl | CURSOR_UP;
98 break;
99 case KeyEvent.VK_DOWN:
100 case KeyEvent.VK_KP_DOWN:
101 key = shift | ctrl | CURSOR_DOWN;
102 break;
103 case KeyEvent.VK_PAGE_UP:
104 key = shift | ctrl | MOD_NUM_KEYPAD | '9';
105 break;
106 case KeyEvent.VK_PAGE_DOWN:
107 key = shift | ctrl | MOD_NUM_KEYPAD | '3';
108 break;
109 case KeyEvent.VK_HOME:
110 key = shift | ctrl | MOD_NUM_KEYPAD | '7';
111 break;
112 case KeyEvent.VK_END:
113 key = shift | ctrl | MOD_NUM_KEYPAD | '1';
114 break;
115 default:
116 if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) {
117 key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0');
118 }
119 break;
120 }
121 if (key != -1) {
122 runtimeCall("jcallback_key_event", new int[] {0, 0, key});
123 }
124 }
125 public void keyTyped(KeyEvent e) {
126 runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
127 }
128 });
129 pp.addMouseListener(new MouseAdapter() {
130 public void mouseReleased(MouseEvent e) {
131 mousePressedReleased(e, true);
132 }
133 public void mousePressed(MouseEvent e) {
134 pp.requestFocus();
135 mousePressedReleased(e, false);
136 }
137 private void mousePressedReleased(MouseEvent e, boolean released) {
138 int button;
139 if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
140 button = MIDDLE_BUTTON;
141 else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
142 button = RIGHT_BUTTON;
143 else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0)
144 button = LEFT_BUTTON;
145 else
146 return;
147 if (released)
148 button += LEFT_RELEASE - LEFT_BUTTON;
149 runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
150 }
151 });
152 pp.addMouseMotionListener(new MouseMotionAdapter() {
153 public void mouseDragged(MouseEvent e) {
154 int button;
155 if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
156 button = MIDDLE_DRAG;
157 else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
158 button = RIGHT_DRAG;
159 else
160 button = LEFT_DRAG;
161 runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
162 }
163 });
164 pp.addComponentListener(new ComponentAdapter() {
165 public void componentResized(ComponentEvent e) {
166 handleResized();
167 }
168 });
169 pp.setFocusable(true);
170 pp.requestFocus();
171 timer = new Timer(20, new ActionListener() {
172 public void actionPerformed(ActionEvent e) {
173 runtimeCall("jcallback_timer_func", new int[0]);
174 }
175 });
176 String gameid;
177 try {
178 gameid = getParameter("game_id");
179 } catch (java.lang.NullPointerException ex) {
180 gameid = null;
181 }
182 System.out.println("ooh " + gameid);
183 if (gameid == null) {
184 puzzle_args = null;
185 } else {
186 puzzle_args = new String[2];
187 puzzle_args[0] = "puzzle";
188 puzzle_args[1] = gameid;
189 }
190 SwingUtilities.invokeLater(new Runnable() {
191 public void run() {
192 runtime.start(puzzle_args);
193 runtime.execute();
194 }
195 });
196 } catch (Exception ex) {
197 ex.printStackTrace();
198 }
199 }
200
201 public void destroy() {
202 SwingUtilities.invokeLater(new Runnable() {
203 public void run() {
204 runtime.execute();
205 if (mainWindow != null) {
206 mainWindow.dispose();
207 System.exit(0);
208 }
209 }
210 });
211 }
212
213 protected void handleResized() {
214 pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]);
215 runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
216 }
217
218 private void addMenuItemWithKey(JMenu jm, String name, int key) {
219 addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
220 }
221
222 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
223 return addMenuItemCallback(jm, name, callback, new int[] {arg});
224 }
225
226 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
227 return addMenuItemCallback(jm, name, callback, new int[0]);
228 }
229
230 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args) {
231 JMenuItem jmi;
232 if (jm == typeMenu)
233 typeMenu.add(jmi = new JCheckBoxMenuItem(name));
234 else
235 jm.add(jmi = new JMenuItem(name));
236 jmi.addActionListener(new ActionListener() {
237 public void actionPerformed(ActionEvent e) {
238 runtimeCall(callback, args);
239 }
240 });
241 return jmi;
242 }
243
244 protected void runtimeCall(String func, int[] args) {
245 if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) {
246 destroy();
247 }
248 }
249
250 protected int runtimeCallWithResult(String func, int[] args) {
251 try {
252 return runtime.call(func, args);
253 } catch (Runtime.CallException ex) {
254 ex.printStackTrace();
255 return 42;
256 }
257 }
258
259 private void buildConfigureMenuItem() {
260 if (typeMenu.isVisible()) {
261 typeMenu.addSeparator();
262 } else {
263 typeMenu.setVisible(true);
264 }
265 addMenuItemCallback(typeMenu, "Custom...", "jcallback_config_event", CFG_SETTINGS);
266 }
267
268 private void addTypeItem(String name, final int ptrGameParams) {
269 typeMenu.setVisible(true);
270 addMenuItemCallback(typeMenu, name, "jcallback_preset_event", ptrGameParams);
271 }
272
273 public int call(int cmd, int arg1, int arg2, int arg3) {
274 try {
275 switch(cmd) {
276 case 0: // initialize
277 if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1));
278 if ((arg2 & 1) != 0) buildConfigureMenuItem();
279 if ((arg2 & 2) != 0) addStatusBar();
280 if ((arg2 & 4) != 0) solveCommand.setEnabled(true);
281 colors = new Color[arg3];
282 return 0;
283 case 1: // Type menu item
284 addTypeItem(runtime.cstring(arg1), arg2);
285 return 0;
286 case 2: // MessageBox
287 JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE);
288 return 0;
289 case 3: // Resize
290 pp.setPreferredSize(new Dimension(arg1, arg2));
291 if (mainWindow != null) mainWindow.pack();
292 handleResized();
293 if (mainWindow != null) mainWindow.setVisible(true);
294 return 0;
295 case 4: // drawing tasks
296 switch(arg1) {
297 case 0:
298 String text = runtime.cstring(arg2);
299 if (text.equals("")) text = " ";
300 System.out.println("status '" + text + "'");
301 statusBar.setText(text); break;
302 case 1:
303 gg = pp.backBuffer.createGraphics();
304 if (arg2 != 0 || arg3 != 0) {
305 gg.setColor(Color.black);
306 gg.fillRect(0, 0, arg2, getHeight());
307 gg.fillRect(0, 0, getWidth(), arg3);
308 gg.fillRect(getWidth() - arg2, 0, arg2, getHeight());
309 gg.fillRect(0, getHeight() - arg3, getWidth(), arg3);
310 gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
311 }
312 break;
313 case 2: gg.dispose(); pp.repaint(); break;
314 case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break;
315 case 4:
316 if (arg2 == 0 && arg3 == 0) {
317 gg.fillRect(0, 0, getWidth(), getHeight());
318 } else {
319 gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
320 }
321 break;
322 case 5:
323 gg.setColor(colors[xarg3]);
324 gg.fillRect(arg2, arg3, xarg1, xarg2);
325 break;
326 case 6:
327 gg.setColor(colors[xarg3]);
328 gg.drawLine(arg2, arg3, xarg1, xarg2);
329 break;
330 case 7:
331 xPoints = new int[arg2];
332 yPoints = new int[arg2];
333 break;
334 case 8:
335 if (arg3 != -1) {
336 gg.setColor(colors[arg3]);
337 gg.fillPolygon(xPoints, yPoints, xPoints.length);
338 }
339 gg.setColor(colors[arg2]);
340 gg.drawPolygon(xPoints, yPoints, xPoints.length);
341 break;
342 case 9:
343 if (arg3 != -1) {
344 gg.setColor(colors[arg3]);
345 gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
346 }
347 gg.setColor(colors[arg2]);
348 gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
349 break;
350 case 10:
351 for(int i=0; i<blitters.length; i++) {
352 if (blitters[i] == null) {
353 blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR);
354 return i;
355 }
356 }
357 throw new RuntimeException("No free blitter found!");
358 case 11: blitters[arg2] = null; break;
359 case 12:
360 timer.start(); break;
361 case 13:
362 timer.stop(); break;
363 }
364 return 0;
365 case 5: // more arguments
366 xarg1 = arg1;
367 xarg2 = arg2;
368 xarg3 = arg3;
369 return 0;
370 case 6: // polygon vertex
371 xPoints[arg1]=arg2;
372 yPoints[arg1]=arg3;
373 return 0;
374 case 7: // string
375 gg.setColor(colors[arg2]);
376 {
377 String text = runtime.cstring(arg3);
378 Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
379 Font.PLAIN, 100);
380 int height100 = this.getFontMetrics(ft).getHeight();
381 ft = ft.deriveFont(arg1 * 100 / (float)height100);
382 FontMetrics fm = this.getFontMetrics(ft);
383 int asc = fm.getAscent(), desc = fm.getDescent();
384 if ((xarg3 & ALIGN_VCENTRE) != 0)
385 xarg2 += asc - (asc+desc)/2;
386 else
387 xarg2 += asc;
388 int wid = fm.stringWidth(text);
389 if ((xarg3 & ALIGN_HCENTRE) != 0)
390 xarg1 -= wid / 2;
391 else if ((xarg3 & ALIGN_HRIGHT) != 0)
392 xarg1 -= wid;
393 gg.setFont(ft);
394 gg.drawString(text, xarg1, xarg2);
395 }
396 return 0;
397 case 8: // blitter_save
398 Graphics g2 = blitters[arg1].createGraphics();
399 g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(),
400 arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this);
401 g2.dispose();
402 return 0;
403 case 9: // blitter_load
404 gg.drawImage(blitters[arg1], arg2, arg3, this);
405 return 0;
406 case 10: // dialog_init
407 dlg= new ConfigDialog(this, runtime.cstring(arg1));
408 return 0;
409 case 11: // dialog_add_control
410 {
411 int sval_ptr = arg1;
412 int ival = arg2;
413 int ptr = xarg1;
414 int type=xarg2;
415 String name = runtime.cstring(xarg3);
416 switch(type) {
417 case C_STRING:
418 dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr));
419 break;
420 case C_BOOLEAN:
421 dlg.addCheckBox(ptr, name, ival != 0);
422 break;
423 case C_CHOICES:
424 dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival);
425 }
426 }
427 return 0;
428 case 12:
429 dlg.finish();
430 dlg = null;
431 return 0;
432 case 13: // tick a menu item
433 if (arg1 < 0) arg1 = typeMenu.getItemCount() - 1;
434 for (int i = 0; i < typeMenu.getItemCount(); i++) {
435 if (typeMenu.getMenuComponent(i) instanceof JCheckBoxMenuItem) {
436 ((JCheckBoxMenuItem)typeMenu.getMenuComponent(i)).setSelected(arg1 == i);
437 }
438 }
439 return 0;
440 default:
441 if (cmd >= 1024 && cmd < 2048) { // palette
442 colors[cmd-1024] = new Color(arg1, arg2, arg3);
443 }
444 if (cmd == 1024) {
445 pp.setBackground(colors[0]);
446 if (statusBar != null) statusBar.setBackground(colors[0]);
447 this.setBackground(colors[0]);
448 }
449 return 0;
450 }
451 } catch (Throwable ex) {
452 ex.printStackTrace();
453 System.exit(-1);
454 return 0;
455 }
456 }
457
458 private void addStatusBar() {
459 statusBar = new JLabel("test");
460 statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
461 getContentPane().add(BorderLayout.SOUTH,statusBar);
462 }
463
464 // Standalone runner
465 public static void main(String[] args) {
466 final PuzzleApplet a = new PuzzleApplet();
467 JFrame jf = new JFrame("Loading...");
468 jf.getContentPane().setLayout(new BorderLayout());
469 jf.getContentPane().add(a, BorderLayout.CENTER);
470 a.mainWindow=jf;
471 a.init();
472 a.start();
473 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
474 jf.addWindowListener(new WindowAdapter() {
475 public void windowClosing(WindowEvent e) {
476 a.stop();
477 a.destroy();
478 }
479 });
480 jf.setVisible(true);
481 }
482
483 public static class PuzzlePanel extends JPanel {
484
485 private static final long serialVersionUID = 1L;
486 protected BufferedImage backBuffer;
487
488 public PuzzlePanel() {
489 setPreferredSize(new Dimension(100,100));
490 createBackBuffer(100,100, Color.black);
491 }
492
493 public void createBackBuffer(int w, int h, Color bg) {
494 if (w > 0 && h > 0) {
495 backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
496 Graphics g = backBuffer.createGraphics();
497 g.setColor(bg);
498 g.fillRect(0, 0, w, h);
499 g.dispose();
500 }
501 }
502
503 protected void paintComponent(Graphics g) {
504 g.drawImage(backBuffer, 0, 0, this);
505 }
506 }
507
508 public static class ConfigComponent {
509 public int type;
510 public int configItemPointer;
511 public JComponent component;
512
513 public ConfigComponent(int type, int configItemPointer, JComponent component) {
514 this.type = type;
515 this.configItemPointer = configItemPointer;
516 this.component = component;
517 }
518 }
519
520 public class ConfigDialog extends JDialog {
521
522 private GridBagConstraints gbcLeft = new GridBagConstraints(
523 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
524 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
525 new Insets(0, 0, 0, 0), 0, 0);
526 private GridBagConstraints gbcRight = new GridBagConstraints(
527 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
528 GridBagConstraints.REMAINDER, 1, 1.0, 0,
529 GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
530 new Insets(5, 5, 5, 5), 0, 0);
531 private GridBagConstraints gbcBottom = new GridBagConstraints(
532 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
533 GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
534 1.0, 1.0, GridBagConstraints.CENTER,
535 GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
536
537 private static final long serialVersionUID = 1L;
538 private List components = new ArrayList();
539
540 public ConfigDialog(JApplet parent, String title) {
541 super(JOptionPane.getFrameForComponent(parent), title, true);
542 getContentPane().setLayout(new GridBagLayout());
543 }
544
545 public void addTextBox(int ptr, String name, String value) {
546 getContentPane().add(new JLabel(name), gbcLeft);
547 JComponent c = new JTextField(value, 25);
548 getContentPane().add(c, gbcRight);
549 components.add(new ConfigComponent(C_STRING, ptr, c));
550 }
551
552
553 public void addCheckBox(int ptr, String name, boolean selected) {
554 JComponent c = new JCheckBox(name, selected);
555 getContentPane().add(c, gbcRight);
556 components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
557 }
558
559 public void addComboBox(int ptr, String name, String values, int selected) {
560 getContentPane().add(new JLabel(name), gbcLeft);
561 StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
562 JComboBox c = new JComboBox();
563 c.setEditable(false);
564 while(st.hasMoreTokens())
565 c.addItem(st.nextToken());
566 c.setSelectedIndex(selected);
567 getContentPane().add(c, gbcRight);
568 components.add(new ConfigComponent(C_CHOICES, ptr, c));
569 }
570
571 public void finish() {
572 JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
573 getContentPane().add(buttons, gbcBottom);
574 JButton b;
575 buttons.add(b=new JButton("OK"));
576 b.addActionListener(new ActionListener() {
577 public void actionPerformed(ActionEvent e) {
578 save();
579 dispose();
580 }
581 });
582 getRootPane().setDefaultButton(b);
583 buttons.add(b=new JButton("Cancel"));
584 b.addActionListener(new ActionListener() {
585 public void actionPerformed(ActionEvent e) {
586 dispose();
587 }
588 });
589 setDefaultCloseOperation(DISPOSE_ON_CLOSE);
590 pack();
591 setLocationRelativeTo(null);
592 setVisible(true);
593 }
594 private void save() {
595 for (int i = 0; i < components.size(); i++) {
596 ConfigComponent cc = (ConfigComponent) components.get(i);
597 switch(cc.type) {
598 case C_STRING:
599 JTextField jtf = (JTextField)cc.component;
600 runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
601 break;
602 case C_BOOLEAN:
603 JCheckBox jcb = (JCheckBox)cc.component;
604 runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
605 break;
606 case C_CHOICES:
607 JComboBox jcm = (JComboBox)cc.component;
608 runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
609 break;
610 }
611 }
612 runtimeCall("jcallback_config_ok", new int[0]);
613 }
614 }
615 }