table_parser.cxx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. #include <toolbox/dir_walk.h>
  2. #include <toolbox/path.h>
  3. #include <toolbox/stream/stream.h>
  4. #include <toolbox/stream/file_stream.h>
  5. #include <toolbox/args.h>
  6. #include "nxjson/nxjson.h"
  7. #include "pinball0.h"
  8. #include "table.h"
  9. #include "notifications.h"
  10. namespace {
  11. bool ON_TABLE(const Vec2& p) {
  12. return 0 <= p.x && p.x <= 630 && 0 <= p.y && p.y <= 1270;
  13. }
  14. };
  15. void table_table_list_init(void* ctx) {
  16. PinballApp* pb = (PinballApp*)ctx;
  17. // using the asset file path, read the table files, and for each one, extract their
  18. // display name (oof). let's just use their filenames for now (stripping any XX_ prefix)
  19. // sort tables by original filename
  20. const char* paths[] = {APP_ASSETS_PATH("tables"), APP_DATA_PATH("tables")};
  21. const size_t ext_len_max = 32;
  22. char ext[ext_len_max];
  23. for(size_t p = 0; p < 2; p++) {
  24. const char* path = paths[p];
  25. // const char* asset_path = APP_ASSETS_PATH("tables");
  26. FURI_LOG_I(TAG, "Loading table list from: %s", path);
  27. FuriString* table_path = furi_string_alloc();
  28. DirWalk* dir_walk = dir_walk_alloc(pb->storage);
  29. dir_walk_set_recursive(dir_walk, false);
  30. if(dir_walk_open(dir_walk, path)) {
  31. while(dir_walk_read(dir_walk, table_path, NULL) == DirWalkOK) {
  32. path_extract_extension(table_path, ext, ext_len_max);
  33. if(strcmp(ext, ".json") != 0) {
  34. FURI_LOG_W(
  35. TAG, "Skipping non-json file: %s", furi_string_get_cstr(table_path));
  36. continue;
  37. }
  38. const char* cpath = furi_string_get_cstr(table_path);
  39. FuriString* filename_no_ext = furi_string_alloc();
  40. path_extract_filename_no_ext(cpath, filename_no_ext);
  41. // If filename starts with XX_ (for custom sorting) strip the prefix
  42. char c = furi_string_get_char(filename_no_ext, 2);
  43. if(c == '_') {
  44. char a = furi_string_get_char(filename_no_ext, 0);
  45. char b = furi_string_get_char(filename_no_ext, 1);
  46. if(a >= '0' && a <= '9' && b >= '0' && b <= '9') {
  47. furi_string_right(filename_no_ext, 3);
  48. }
  49. }
  50. if(!pb->settings.debug_mode &&
  51. !strncmp("dbg", furi_string_get_cstr(filename_no_ext), 3)) {
  52. furi_string_free(filename_no_ext);
  53. continue;
  54. }
  55. FURI_LOG_I(
  56. TAG,
  57. "Found table: name=%s | path=%s",
  58. furi_string_get_cstr(filename_no_ext),
  59. furi_string_get_cstr(table_path));
  60. // set display 'name' and 'filename'
  61. TableList::TableMenuItem tmi;
  62. tmi.filename = furi_string_alloc_set_str(cpath);
  63. tmi.name = filename_no_ext;
  64. // Insert in sorted order
  65. size_t i = 0;
  66. auto it = pb->table_list.menu_items.begin();
  67. for(; it != pb->table_list.menu_items.end(); it++, i++) {
  68. if(strcmp(
  69. furi_string_get_cstr(tmi.filename),
  70. furi_string_get_cstr(it->filename)) > 0) {
  71. continue;
  72. }
  73. pb->table_list.menu_items.insert(it, tmi);
  74. break;
  75. }
  76. if(pb->table_list.menu_items.size() == i) {
  77. pb->table_list.menu_items.push_back(tmi);
  78. }
  79. }
  80. }
  81. furi_string_free(table_path);
  82. dir_walk_free(dir_walk);
  83. }
  84. // Add 'Settings' as last element
  85. TableList::TableMenuItem settings;
  86. settings.filename = furi_string_alloc_set_str("99_Settings");
  87. settings.name = furi_string_alloc_set_str("SETTINGS");
  88. pb->table_list.menu_items.push_back(settings);
  89. FURI_LOG_I(TAG, "Found %d tables", pb->table_list.menu_items.size());
  90. for(auto& tmi : pb->table_list.menu_items) {
  91. FURI_LOG_I(TAG, "%s", furi_string_get_cstr(tmi.name));
  92. }
  93. pb->table_list.display_size = 5; // how many tables to display at once
  94. pb->table_list.selected = 0;
  95. }
  96. // json parse helper function
  97. bool table_file_parse_vec2(const nx_json* json, const char* key, Vec2& v) {
  98. const nx_json* item = nx_json_get(json, key);
  99. if(!item || item->children.length != 2) {
  100. return false;
  101. }
  102. v.x = nx_json_item(item, 0)->num.dbl_value;
  103. v.y = nx_json_item(item, 1)->num.dbl_value;
  104. return true;
  105. }
  106. bool table_file_parse_int(const nx_json* json, const char* key, int& v) {
  107. const nx_json* item = nx_json_get(json, key);
  108. if(!item) return false;
  109. v = item->num.u_value;
  110. return true;
  111. }
  112. bool table_file_parse_bool(const nx_json* json, const char* key, bool& v) {
  113. int value = v == true ? 1 : 0; // set default value
  114. if(table_file_parse_int(json, key, value)) {
  115. v = value > 0 ? true : false;
  116. return true;
  117. }
  118. return false;
  119. }
  120. bool table_file_parse_float(const nx_json* json, const char* key, float& v) {
  121. const nx_json* item = nx_json_get(json, key);
  122. if(!item) return false;
  123. v = item->num.dbl_value;
  124. return true;
  125. }
  126. void table_file_parse_signal(const nx_json* json, Table* table, FixedObject* obj) {
  127. const nx_json* signal = nx_json_get(json, "signal");
  128. if(signal) {
  129. int tx = INVALID_ID;
  130. int rx = INVALID_ID;
  131. if(table_file_parse_int(signal, "tx", tx) && tx != INVALID_ID) {
  132. obj->tx_id = tx;
  133. table->sm.register_signal(tx, obj);
  134. }
  135. if(table_file_parse_int(signal, "rx", rx) && rx != INVALID_ID) {
  136. obj->rx_id = rx;
  137. table->sm.register_slot(rx, obj);
  138. }
  139. bool any = false;
  140. table_file_parse_bool(signal, "any", any);
  141. obj->tx_type = any ? SignalType::ANY : SignalType::ALL;
  142. }
  143. }
  144. Table* table_load_table_from_file(PinballApp* pb, size_t index) {
  145. auto& tmi = pb->table_list.menu_items[index];
  146. FURI_LOG_I(TAG, "Reading file: %s", furi_string_get_cstr(tmi.filename));
  147. File* file = storage_file_alloc(pb->storage);
  148. FileInfo fileinfo;
  149. FS_Error error =
  150. storage_common_stat(pb->storage, furi_string_get_cstr(tmi.filename), &fileinfo);
  151. if(error != FSE_OK) {
  152. FURI_LOG_E(TAG, "Could not find file");
  153. storage_file_free(file);
  154. return NULL;
  155. }
  156. // TODO: determine an appropriate max file size and make configurable
  157. FURI_LOG_I(TAG, "Found file ok!");
  158. if(fileinfo.size >= 8192) {
  159. FURI_LOG_E(TAG, "Table file size too big");
  160. snprintf(pb->text, 256, "Table file\nis too big!\n> 8192 bytes");
  161. storage_file_free(file);
  162. return NULL;
  163. }
  164. bool ok =
  165. storage_file_open(file, furi_string_get_cstr(tmi.filename), FSAM_READ, FSOM_OPEN_EXISTING);
  166. if(!ok) {
  167. FURI_LOG_E(TAG, "Failed to open table file: %s", furi_string_get_cstr(tmi.filename));
  168. snprintf(pb->text, 256, "Failed\nto open\nfile!");
  169. storage_file_free(file);
  170. return NULL;
  171. }
  172. // read the file as a string
  173. uint8_t* buffer;
  174. uint64_t file_size = storage_file_size(file);
  175. if(file_size > 8192) { // TODO - what's the right size?
  176. FURI_LOG_E(TAG, "Table file is too large! (> 8192 bytes)");
  177. snprintf(pb->text, 256, "Table file\nis too big!\n> 8192 bytes");
  178. storage_file_free(file);
  179. return NULL;
  180. }
  181. buffer = (uint8_t*)malloc(file_size);
  182. size_t read_count = storage_file_read(file, buffer, file_size);
  183. // if(storage_file_get_error(file) != FSE_OK) {
  184. // FURI_LOG_E(TAG, "Um, couldn't read file");
  185. // storage_file_free(file);
  186. // return NULL;
  187. // }
  188. storage_file_free(file);
  189. if(read_count != file_size) {
  190. FURI_LOG_E(TAG, "Error reading file. expected %lld, got %d", file_size, read_count);
  191. free(buffer);
  192. return NULL;
  193. }
  194. FURI_LOG_I(TAG, "Read file into buffer! %d bytes", read_count);
  195. // let's parse this shit
  196. char* json_buffer = (char*)malloc(read_count * sizeof(char) + 1);
  197. for(uint16_t i = 0; i < read_count; i++) {
  198. json_buffer[i] = buffer[i];
  199. }
  200. json_buffer[read_count] = 0;
  201. free(buffer);
  202. const nx_json* json = nx_json_parse(json_buffer, 0);
  203. if(!json) {
  204. FURI_LOG_E(TAG, "Failed to parse table json!");
  205. snprintf(pb->text, 256, "Failed to\nparse table\njson!!");
  206. free(json_buffer);
  207. return NULL;
  208. }
  209. Table* table = new Table();
  210. do {
  211. const nx_json* lives = nx_json_get(json, "lives");
  212. if(lives) {
  213. table_file_parse_int(lives, "value", table->lives.value);
  214. table_file_parse_bool(lives, "display", table->lives.display);
  215. table_file_parse_vec2(lives, "position", table->lives.p);
  216. const nx_json* align = nx_json_get(lives, "align");
  217. if(align && !strcmp(align->text_value, "VERTICAL")) {
  218. table->lives.alignment = Lives::Vertical;
  219. }
  220. }
  221. const nx_json* tilt = nx_json_get(json, "tilt_detect");
  222. if(tilt) {
  223. table->tilt_detect_enabled = tilt->num.u_value > 0 ? true : false;
  224. }
  225. const nx_json* score = nx_json_get(json, "score");
  226. if(score) {
  227. table_file_parse_bool(score, "display", table->score.display);
  228. table_file_parse_vec2(score, "position", table->score.p);
  229. }
  230. const nx_json* balls = nx_json_get(json, "balls");
  231. if(balls) {
  232. for(int i = 0; i < balls->children.length; i++) {
  233. const nx_json* ball = nx_json_item(balls, i);
  234. if(!ball) continue;
  235. Vec2 p;
  236. if(!table_file_parse_vec2(ball, "position", p)) {
  237. FURI_LOG_E(TAG, "Ball missing \"position\", skipping");
  238. continue;
  239. }
  240. if(!ON_TABLE(p)) {
  241. FURI_LOG_W(
  242. TAG,
  243. "Ball with position %.1f,%.1f is not on table!",
  244. (double)p.x,
  245. (double)p.y);
  246. }
  247. Ball new_ball(p);
  248. table_file_parse_float(ball, "radius", new_ball.r);
  249. Vec2 v = (Vec2){0, 0};
  250. table_file_parse_vec2(ball, "velocity", v);
  251. new_ball.accelerate(v);
  252. table->balls_initial.push_back(new_ball);
  253. table->balls.push_back(new_ball);
  254. }
  255. }
  256. if(table->balls.size() == 0) {
  257. FURI_LOG_E(TAG, "Table has NO BALLS");
  258. snprintf(pb->text, 256, "No balls\nfound in\ntable file!");
  259. delete table;
  260. table = NULL;
  261. break;
  262. }
  263. // TODO: plungers need work
  264. const nx_json* plunger = nx_json_get(json, "plunger");
  265. if(plunger) {
  266. Vec2 p;
  267. table_file_parse_vec2(plunger, "position", p);
  268. int s = 100;
  269. table_file_parse_int(plunger, "size", s);
  270. table->plunger = new Plunger(p);
  271. } else {
  272. FURI_LOG_W(
  273. TAG, "Table has NO PLUNGER - s'ok, we don't really support one anyway (yet)");
  274. }
  275. const nx_json* flippers = nx_json_get(json, "flippers");
  276. if(flippers) {
  277. for(int i = 0; i < flippers->children.length; i++) {
  278. const nx_json* flipper = nx_json_item(flippers, i);
  279. Vec2 p;
  280. if(!table_file_parse_vec2(flipper, "position", p)) {
  281. FURI_LOG_E(TAG, "Flipper missing \"position\", skipping");
  282. continue;
  283. }
  284. if(!ON_TABLE(p)) {
  285. FURI_LOG_W(
  286. TAG,
  287. "Flipper with position %.1f,%.1f is not on table!",
  288. (double)p.x,
  289. (double)p.y);
  290. }
  291. const nx_json* side = nx_json_get(flipper, "side");
  292. Flipper::Side sd = Flipper::LEFT;
  293. if(side && !strcmp(side->text_value, "RIGHT")) {
  294. sd = Flipper::RIGHT;
  295. }
  296. int sz = DEF_FLIPPER_SIZE;
  297. table_file_parse_int(flipper, "size", sz);
  298. Flipper flip(p, sd, sz);
  299. // flip.notification = &notify_flipper;
  300. table->flippers.push_back(flip);
  301. }
  302. }
  303. const nx_json* bumpers = nx_json_get(json, "bumpers");
  304. if(bumpers) {
  305. for(int i = 0; i < bumpers->children.length; i++) {
  306. const nx_json* bumper = nx_json_item(bumpers, i);
  307. Vec2 p;
  308. if(!table_file_parse_vec2(bumper, "position", p)) {
  309. FURI_LOG_E(TAG, "Bumper missing \"position\", skipping");
  310. continue;
  311. }
  312. if(!ON_TABLE(p)) {
  313. FURI_LOG_W(
  314. TAG,
  315. "Bumper with position %.1f,%.1f is not on table!",
  316. (double)p.x,
  317. (double)p.y);
  318. }
  319. int r = DEF_BUMPER_RADIUS;
  320. table_file_parse_int(bumper, "radius", r);
  321. float bnc = DEF_BUMPER_BOUNCE;
  322. table_file_parse_float(bumper, "bounce", bnc);
  323. bool physical = true;
  324. table_file_parse_bool(bumper, "physical", physical);
  325. bool hidden = false;
  326. table_file_parse_bool(bumper, "hidden", hidden);
  327. Bumper* new_bumper = new Bumper(p, r);
  328. new_bumper->bounce = bnc;
  329. new_bumper->notification = notify_bumper_hit;
  330. new_bumper->physical = physical;
  331. new_bumper->hidden = hidden;
  332. table_file_parse_signal(bumper, table, new_bumper);
  333. table->objects.push_back(new_bumper);
  334. }
  335. }
  336. constexpr float pi_180 = M_PI / 180;
  337. const nx_json* arcs = nx_json_get(json, "arcs");
  338. if(arcs) {
  339. for(int i = 0; i < arcs->children.length; i++) {
  340. const nx_json* arc = nx_json_item(arcs, i);
  341. Vec2 p;
  342. if(!table_file_parse_vec2(arc, "position", p)) {
  343. FURI_LOG_E(TAG, "Arc missing \"position\"");
  344. continue;
  345. }
  346. if(!ON_TABLE(p)) {
  347. FURI_LOG_W(
  348. TAG,
  349. "Arc with position %.1f,%.1f is not on table!",
  350. (double)p.x,
  351. (double)p.y);
  352. }
  353. int r = DEF_BUMPER_RADIUS;
  354. table_file_parse_int(arc, "radius", r);
  355. float bnc = 0.95f; // DEF_BUMPER_BOUNCE?
  356. table_file_parse_float(arc, "bounce", bnc);
  357. float start_angle = 0.0;
  358. table_file_parse_float(arc, "start_angle", start_angle);
  359. start_angle *= pi_180;
  360. float end_angle = 0.0;
  361. table_file_parse_float(arc, "end_angle", end_angle);
  362. end_angle *= pi_180;
  363. Arc::Surface surface = Arc::OUTSIDE;
  364. const nx_json* stype = nx_json_get(arc, "surface");
  365. if(stype && !strcmp(stype->text_value, "INSIDE")) {
  366. surface = Arc::INSIDE;
  367. }
  368. Arc* new_bumper = new Arc(p, r, start_angle, end_angle, surface);
  369. new_bumper->bounce = bnc;
  370. table->objects.push_back(new_bumper);
  371. }
  372. }
  373. const nx_json* rails = nx_json_get(json, "rails");
  374. if(rails) {
  375. for(int i = 0; i < rails->children.length; i++) {
  376. const nx_json* rail = nx_json_item(rails, i);
  377. Vec2 s;
  378. if(!table_file_parse_vec2(rail, "start", s)) {
  379. FURI_LOG_E(TAG, "Rail missing \"start\", skipping");
  380. continue;
  381. }
  382. if(!ON_TABLE(s)) {
  383. FURI_LOG_W(
  384. TAG,
  385. "Rail with starting position %.1f,%.1f is not on table!",
  386. (double)s.x,
  387. (double)s.y);
  388. }
  389. Vec2 e;
  390. if(!table_file_parse_vec2(rail, "end", e)) {
  391. FURI_LOG_E(TAG, "Rail missing \"end\", skipping");
  392. continue;
  393. }
  394. if(!ON_TABLE(e)) {
  395. FURI_LOG_W(
  396. TAG,
  397. "Rail with ending position %.1f,%.1f is not on table!",
  398. (double)e.x,
  399. (double)e.y);
  400. }
  401. Polygon* new_rail = new Polygon();
  402. new_rail->add_point(s);
  403. new_rail->add_point(e);
  404. float bnc = DEF_RAIL_BOUNCE;
  405. table_file_parse_float(rail, "bounce", bnc);
  406. new_rail->bounce = bnc;
  407. int double_sided = 0;
  408. table_file_parse_int(rail, "double_sided", double_sided);
  409. new_rail->finalize();
  410. new_rail->notification = &notify_rail_hit;
  411. table->objects.push_back(new_rail);
  412. if(double_sided) {
  413. new_rail = new Polygon();
  414. new_rail->add_point(e);
  415. new_rail->add_point(s);
  416. new_rail->bounce = bnc;
  417. new_rail->finalize();
  418. new_rail->notification = &notify_rail_hit;
  419. table->objects.push_back(new_rail);
  420. }
  421. }
  422. }
  423. const nx_json* portals = nx_json_get(json, "portals");
  424. if(portals) {
  425. for(int i = 0; i < portals->children.length; i++) {
  426. const nx_json* portal = nx_json_item(portals, i);
  427. Vec2 a1;
  428. if(!table_file_parse_vec2(portal, "a_start", a1)) {
  429. FURI_LOG_E(TAG, "Portal missing \"a_start\", skipping");
  430. continue;
  431. }
  432. if(!ON_TABLE(a1)) {
  433. FURI_LOG_W(
  434. TAG,
  435. "Portal A with starting position %.1f,%.1f is not on table!",
  436. (double)a1.x,
  437. (double)a1.y);
  438. }
  439. Vec2 a2;
  440. if(!table_file_parse_vec2(portal, "a_end", a2)) {
  441. FURI_LOG_E(TAG, "Portal missing \"a_end\", skipping");
  442. continue;
  443. }
  444. if(!ON_TABLE(a2)) {
  445. FURI_LOG_W(
  446. TAG,
  447. "Portal A with ending position %.1f,%.1f is not on table!",
  448. (double)a2.x,
  449. (double)a2.y);
  450. }
  451. Vec2 b1;
  452. if(!table_file_parse_vec2(portal, "b_start", b1)) {
  453. FURI_LOG_E(TAG, "Portal missing \"b_start\", skipping");
  454. continue;
  455. }
  456. if(!ON_TABLE(b1)) {
  457. FURI_LOG_W(
  458. TAG,
  459. "Portal B with starting position %.1f,%.1f is not on table!",
  460. (double)b1.x,
  461. (double)b1.y);
  462. }
  463. Vec2 b2;
  464. if(!table_file_parse_vec2(portal, "b_end", b2)) {
  465. FURI_LOG_E(TAG, "Portal missing \"b_end\", skipping");
  466. continue;
  467. }
  468. if(!ON_TABLE(b2)) {
  469. FURI_LOG_W(
  470. TAG,
  471. "Portal B with ending position %.1f,%.1f is not on table!",
  472. (double)b2.x,
  473. (double)b2.y);
  474. }
  475. Portal* new_portal = new Portal(a1, a2, b1, b2);
  476. new_portal->finalize();
  477. new_portal->notification = &notify_portal;
  478. table->objects.push_back(new_portal);
  479. }
  480. }
  481. const nx_json* rollovers = nx_json_get(json, "rollovers");
  482. if(rollovers) {
  483. for(int i = 0; i < rollovers->children.length; i++) {
  484. const nx_json* rollover = nx_json_item(rollovers, i);
  485. Vec2 p;
  486. if(!table_file_parse_vec2(rollover, "position", p)) {
  487. FURI_LOG_E(TAG, "Rollover missing \"position\", skipping");
  488. continue;
  489. }
  490. if(!ON_TABLE(p)) {
  491. FURI_LOG_W(
  492. TAG,
  493. "Rollover with position %.1f,%.1f is not on table!",
  494. (double)p.x,
  495. (double)p.y);
  496. }
  497. char sym = '*';
  498. const nx_json* symbol = nx_json_get(rollover, "symbol");
  499. if(symbol) {
  500. sym = symbol->text_value[0];
  501. }
  502. Rollover* new_rollover = new Rollover(p, sym);
  503. table_file_parse_signal(rollover, table, new_rollover);
  504. table->objects.push_back(new_rollover);
  505. }
  506. }
  507. const nx_json* turbos = nx_json_get(json, "turbos");
  508. if(turbos) {
  509. for(int i = 0; i < turbos->children.length; i++) {
  510. const nx_json* turbo = nx_json_item(turbos, i);
  511. Vec2 p;
  512. if(!table_file_parse_vec2(turbo, "position", p)) {
  513. FURI_LOG_E(TAG, "Turbo missing \"position\"");
  514. continue;
  515. }
  516. if(!ON_TABLE(p)) {
  517. FURI_LOG_W(
  518. TAG,
  519. "Turbo with position %.1f,%.1f is not on table!",
  520. (double)p.x,
  521. (double)p.y);
  522. }
  523. float angle = 0;
  524. table_file_parse_float(turbo, "angle", angle);
  525. angle *= pi_180;
  526. float boost = DEF_TURBO_BOOST;
  527. table_file_parse_float(turbo, "boost", boost);
  528. float radius = DEF_TURBO_RADIUS;
  529. table_file_parse_float(turbo, "radius", radius);
  530. Turbo* new_turbo = new Turbo(p, angle, boost, radius);
  531. table->objects.push_back(new_turbo);
  532. }
  533. }
  534. for(auto& o : table->objects) {
  535. o->save_state();
  536. }
  537. } while(false);
  538. if(!table->sm.validate(pb->text, 256)) {
  539. FURI_LOG_E(TAG, "Signal validation failed!");
  540. delete table;
  541. table = NULL;
  542. }
  543. nx_json_free(json);
  544. free(json_buffer);
  545. return table;
  546. }