Browse Source

add macro to restore previous bank when switching rom banks. score no longer crashes but shows garbage.

Thomas B 1 month ago
parent
commit
0f915b5e9f
6 changed files with 127 additions and 75 deletions
  1. 27
    0
      src/banks.h
  2. 8
    6
      src/input.c
  3. 12
    11
      src/main.c
  4. 48
    36
      src/maps.c
  5. 12
    6
      src/score.ba0.c
  6. 20
    16
      src/sprites.c

+ 27
- 0
src/banks.h View File

1
+/*
2
+ * banks.h
3
+ * Duality
4
+ *
5
+ * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * See <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+#ifndef __BANKS_H__
21
+#define __BANKS_H__
22
+
23
+#define START_ROM_BANK_2(x) __previous__bank = CURRENT_BANK; SWITCH_ROM(x)
24
+#define START_ROM_BANK(x) uint8_t START_ROM_BANK_2(x)
25
+#define END_ROM_BANK() SWITCH_ROM(__previous__bank)
26
+
27
+#endif // __BANKS_H__

+ 8
- 6
src/input.c View File

19
 
19
 
20
 #include <gbdk/platform.h>
20
 #include <gbdk/platform.h>
21
 
21
 
22
+#include "banks.h"
22
 #include "input.h"
23
 #include "input.h"
23
 
24
 
24
 static uint8_t joyp = 0;
25
 static uint8_t joyp = 0;
39
     old_joyp = joyp;
40
     old_joyp = joyp;
40
     joyp = joypad();
41
     joyp = joypad();
41
 
42
 
42
-    SWITCH_ROM(BANK(input));
43
     if (debug_cnt < DEBUG_SEQUENCE_COUNT) {
43
     if (debug_cnt < DEBUG_SEQUENCE_COUNT) {
44
-        if (key_pressed(key_debug_sequence[debug_cnt])) {
45
-            debug_cnt++;
46
-        } else if (key_pressed(0xFF)) {
47
-            debug_cnt = 0;
48
-        }
44
+        START_ROM_BANK(BANK(input));
45
+            if (key_pressed(key_debug_sequence[debug_cnt])) {
46
+                debug_cnt++;
47
+            } else if (key_pressed(0xFF)) {
48
+                debug_cnt = 0;
49
+            }
50
+        END_ROM_BANK();
49
     } else {
51
     } else {
50
         if (key_pressed(0xFF ^ J_START)) {
52
         if (key_pressed(0xFF ^ J_START)) {
51
             debug_cnt = 0;
53
             debug_cnt = 0;

+ 12
- 11
src/main.c View File

24
 #include <rand.h>
24
 #include <rand.h>
25
 
25
 
26
 #include "asm/types.h"
26
 #include "asm/types.h"
27
+#include "banks.h"
27
 #include "gb/gb.h"
28
 #include "gb/gb.h"
28
 #include "maps.h"
29
 #include "maps.h"
29
 #include "obj.h"
30
 #include "obj.h"
105
         move_win(MINWNDPOSX, MINWNDPOSY);
106
         move_win(MINWNDPOSX, MINWNDPOSY);
106
     } else {
107
     } else {
107
         // initially show the top 1 scores
108
         // initially show the top 1 scores
108
-        //int32_t low = score_lowest(0).score;
109
-        //int32_t high = score_highest(0).score;
110
-        //win_splash_draw(-low, high);
109
+        int32_t low = score_lowest(0).score;
110
+        int32_t high = score_highest(0).score;
111
+        win_splash_draw(-low, high);
111
 
112
 
112
         move_win(MINWNDPOSX, MINWNDPOSY + DEVICE_SCREEN_PX_HEIGHT - (8 * 4));
113
         move_win(MINWNDPOSX, MINWNDPOSY + DEVICE_SCREEN_PX_HEIGHT - (8 * 4));
113
     }
114
     }
242
                     }
243
                     }
243
                     splash_win();
244
                     splash_win();
244
                 } else if (key_pressed(J_A)) {
245
                 } else if (key_pressed(J_A)) {
245
-                    SWITCH_ROM(BANK(main));
246
-                    debug_flags ^= debug_entries[debug_menu_index].flag;
246
+                    START_ROM_BANK(BANK(main));
247
+                        debug_flags ^= debug_entries[debug_menu_index].flag;
248
+                    END_ROM_BANK();
247
                     splash_win();
249
                     splash_win();
248
                 } else if (key_pressed(J_B)) {
250
                 } else if (key_pressed(J_B)) {
249
                     debug_flags &= ~DBG_MENU;
251
                     debug_flags &= ~DBG_MENU;
359
 
361
 
360
     DISPLAY_ON;
362
     DISPLAY_ON;
361
 
363
 
362
-    SWITCH_ROM(BANK(border_sgb));
363
-
364
-    set_sgb_border((const uint8_t *)border_sgb_tiles, sizeof(border_sgb_tiles),
365
-                   (const uint8_t *)border_sgb_map, sizeof(border_sgb_map),
366
-                   (const uint8_t *)border_sgb_palettes, sizeof(border_sgb_palettes));
364
+    START_ROM_BANK(BANK(border_sgb));
365
+        set_sgb_border((const uint8_t *)border_sgb_tiles, sizeof(border_sgb_tiles),
366
+                       (const uint8_t *)border_sgb_map, sizeof(border_sgb_map),
367
+                       (const uint8_t *)border_sgb_palettes, sizeof(border_sgb_palettes));
368
+    END_ROM_BANK();
367
 
369
 
368
     DISPLAY_OFF;
370
     DISPLAY_OFF;
369
-
370
 }
371
 }
371
 
372
 
372
 void main(void) NONBANKED {
373
 void main(void) NONBANKED {

+ 48
- 36
src/maps.c View File

19
 
19
 
20
 #include <gbdk/platform.h>
20
 #include <gbdk/platform.h>
21
 #include <string.h>
21
 #include <string.h>
22
-#include "gb/gb.h"
22
+
23
+#include "banks.h"
23
 #include "score.h"
24
 #include "score.h"
24
 #include "title_map.h"
25
 #include "title_map.h"
25
 #include "bg_map.h"
26
 #include "bg_map.h"
62
 static uint8_t fnt_off = 0;
63
 static uint8_t fnt_off = 0;
63
 
64
 
64
 void map_title(void) NONBANKED {
65
 void map_title(void) NONBANKED {
65
-    SWITCH_ROM(BANK(title_map));
66
-    set_bkg_palette(OAMF_CGB_PAL0, title_map_PALETTE_COUNT, title_map_palettes);
67
-    set_bkg_data(0, title_map_TILE_COUNT, title_map_tiles);
68
-    set_bkg_attributes(0, 0, title_map_MAP_ATTRIBUTES_WIDTH, title_map_MAP_ATTRIBUTES_HEIGHT, title_map_MAP_ATTRIBUTES);
69
-    set_bkg_tiles(0, 0, title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H, title_map_map);
66
+    START_ROM_BANK(BANK(title_map));
67
+        set_bkg_palette(OAMF_CGB_PAL0, title_map_PALETTE_COUNT, title_map_palettes);
68
+        set_bkg_data(0, title_map_TILE_COUNT, title_map_tiles);
69
+        set_bkg_attributes(0, 0, title_map_MAP_ATTRIBUTES_WIDTH, title_map_MAP_ATTRIBUTES_HEIGHT, title_map_MAP_ATTRIBUTES);
70
+        set_bkg_tiles(0, 0, title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H, title_map_map);
71
+    END_ROM_BANK();
70
 }
72
 }
71
 
73
 
72
 void map_game(void) NONBANKED {
74
 void map_game(void) NONBANKED {
73
-    SWITCH_ROM(BANK(bg_map));
74
-    set_bkg_palette(OAMF_CGB_PAL0, bg_map_PALETTE_COUNT, bg_map_palettes);
75
-    set_bkg_data(0, bg_map_TILE_COUNT, bg_map_tiles);
76
-    set_bkg_attributes(0, 0, bg_map_MAP_ATTRIBUTES_WIDTH, bg_map_MAP_ATTRIBUTES_HEIGHT, bg_map_MAP_ATTRIBUTES);
77
-    set_bkg_tiles(0, 0, bg_map_WIDTH / bg_map_TILE_W, bg_map_HEIGHT / bg_map_TILE_H, bg_map_map);
75
+    START_ROM_BANK(BANK(bg_map));
76
+        set_bkg_palette(OAMF_CGB_PAL0, bg_map_PALETTE_COUNT, bg_map_palettes);
77
+        set_bkg_data(0, bg_map_TILE_COUNT, bg_map_tiles);
78
+        set_bkg_attributes(0, 0, bg_map_MAP_ATTRIBUTES_WIDTH, bg_map_MAP_ATTRIBUTES_HEIGHT, bg_map_MAP_ATTRIBUTES);
79
+        set_bkg_tiles(0, 0, bg_map_WIDTH / bg_map_TILE_W, bg_map_HEIGHT / bg_map_TILE_H, bg_map_map);
80
+    END_ROM_BANK();
78
 }
81
 }
79
 
82
 
80
 void win_init(uint8_t is_splash) NONBANKED {
83
 void win_init(uint8_t is_splash) NONBANKED {
81
     fnt_off = is_splash ? title_map_TILE_COUNT : bg_map_TILE_COUNT;
84
     fnt_off = is_splash ? title_map_TILE_COUNT : bg_map_TILE_COUNT;
82
 
85
 
83
-    SWITCH_ROM(BANK(numbers_fnt));
84
-    set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT, numbers_fnt_PALETTE_COUNT, numbers_fnt_palettes);
85
-    set_win_data(fnt_off, numbers_fnt_TILE_COUNT, numbers_fnt_tiles);
86
+    START_ROM_BANK(BANK(numbers_fnt));
87
+        set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT, numbers_fnt_PALETTE_COUNT, numbers_fnt_palettes);
88
+        set_win_data(fnt_off, numbers_fnt_TILE_COUNT, numbers_fnt_tiles);
89
+    END_ROM_BANK();
86
 
90
 
87
-    SWITCH_ROM(BANK(maps));
88
-    set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT + numbers_fnt_PALETTE_COUNT, numbers_fnt_PALETTE_COUNT, num_pal_inv);
91
+    START_ROM_BANK_2(BANK(maps));
92
+        set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT + numbers_fnt_PALETTE_COUNT, numbers_fnt_PALETTE_COUNT, num_pal_inv);
93
+    END_ROM_BANK();
89
 
94
 
90
     if (is_splash) {
95
     if (is_splash) {
91
-        SWITCH_ROM(BANK(text_fnt));
92
-        set_win_data(fnt_off + numbers_fnt_TILE_COUNT, text_fnt_TILE_COUNT, text_fnt_tiles);
96
+        START_ROM_BANK_2(BANK(text_fnt));
97
+            set_win_data(fnt_off + numbers_fnt_TILE_COUNT, text_fnt_TILE_COUNT, text_fnt_tiles);
98
+        END_ROM_BANK();
93
     }
99
     }
94
 }
100
 }
95
 
101
 
96
 static void set_win_based(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
102
 static void set_win_based(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
97
                           const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
103
                           const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
98
                           const uint8_t *attributes, uint8_t attr_bank) NONBANKED {
104
                           const uint8_t *attributes, uint8_t attr_bank) NONBANKED {
99
-    SWITCH_ROM(attr_bank);
100
-    VBK_REG = VBK_ATTRIBUTES;
101
-    set_win_tiles(x, y, w, h, attributes);
102
-
103
-    SWITCH_ROM(tile_bank);
104
-    VBK_REG = VBK_TILES;
105
-    set_win_based_tiles(x, y, w, h, tiles, base_tile);
105
+    START_ROM_BANK(attr_bank);
106
+        VBK_REG = VBK_ATTRIBUTES;
107
+        set_win_tiles(x, y, w, h, attributes);
108
+    END_ROM_BANK();
109
+
110
+    START_ROM_BANK_2(tile_bank);
111
+        VBK_REG = VBK_TILES;
112
+        set_win_based_tiles(x, y, w, h, tiles, base_tile);
113
+    END_ROM_BANK();
106
 }
114
 }
107
 
115
 
108
 static void character(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) NONBANKED {
116
 static void character(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) NONBANKED {
243
     str_center("Duality", 0, 1);
251
     str_center("Duality", 0, 1);
244
     str_center("xythobuz", 2, 1);
252
     str_center("xythobuz", 2, 1);
245
 
253
 
246
-    SWITCH_ROM(BANK(git));
247
     char line_buff[2 * LINE_WIDTH + 1] = {0};
254
     char line_buff[2 * LINE_WIDTH + 1] = {0};
248
-    strncpy(line_buff, git_version, 2 * LINE_WIDTH);
255
+
256
+    START_ROM_BANK(BANK(git));
257
+        strncpy(line_buff, git_version, 2 * LINE_WIDTH);
258
+    END_ROM_BANK();
249
 
259
 
250
     str_lines(line_buff, 7, 0);
260
     str_lines(line_buff, 7, 0);
251
 
261
 
265
 
275
 
266
     str_center("Debug Menu", 0, 0);
276
     str_center("Debug Menu", 0, 0);
267
 
277
 
268
-    char name_buff[DEBUG_ENTRY_NAME_LEN + 2 + 1] = {0};
269
     for (uint8_t i = 0; (i < DEBUG_ENTRY_COUNT) && (i < 7); i++) {
278
     for (uint8_t i = 0; (i < DEBUG_ENTRY_COUNT) && (i < 7); i++) {
270
-        SWITCH_ROM(BANK(main));
271
-        strncpy(name_buff, debug_entries[i].name, DEBUG_ENTRY_NAME_LEN + 1);
272
-
273
-        uint8_t n_len = strlen(name_buff);
274
-        name_buff[n_len] = ' ';
275
-        name_buff[n_len + 1] = (debug_flags & debug_entries[i].flag) ? '1' : '0';
276
-        name_buff[n_len + 2] = '\0';
277
-        n_len += 2;
279
+        char name_buff[DEBUG_ENTRY_NAME_LEN + 2 + 1] = {0};
280
+
281
+        START_ROM_BANK(BANK(main));
282
+            strncpy(name_buff, debug_entries[i].name, DEBUG_ENTRY_NAME_LEN + 1);
283
+
284
+            uint8_t n_len = strlen(name_buff);
285
+            name_buff[n_len] = ' ';
286
+            name_buff[n_len + 1] = (debug_flags & debug_entries[i].flag) ? '1' : '0';
287
+            name_buff[n_len + 2] = '\0';
288
+            n_len += 2;
289
+        END_ROM_BANK();
278
 
290
 
279
         str(name_buff, (LINE_WIDTH - n_len) * 2, (i * 2) + 3, (debug_menu_index == i) ? 1 : 0);
291
         str(name_buff, (LINE_WIDTH - n_len) * 2, (i * 2) + 3, (debug_menu_index == i) ? 1 : 0);
280
     }
292
     }

+ 12
- 6
src/score.ba0.c View File

19
 
19
 
20
 #include <string.h>
20
 #include <string.h>
21
 
21
 
22
+#include "banks.h"
22
 #include "score.h"
23
 #include "score.h"
23
 
24
 
24
 static struct scores scores[SCORE_NUM * 2];
25
 static struct scores scores[SCORE_NUM * 2];
71
     return (a << 10) | (b << 5) | c;
72
     return (a << 10) | (b << 5) | c;
72
 }
73
 }
73
 
74
 
74
-static uint32_t calc_crc(void) BANKED {
75
+static uint32_t calc_crc(void) {
75
     const uint8_t *d = (const uint8_t *)scores;
76
     const uint8_t *d = (const uint8_t *)scores;
76
 
77
 
77
     uint32_t c = 0xFFFFFFFF;
78
     uint32_t c = 0xFFFFFFFF;
87
     return ~c;
88
     return ~c;
88
 }
89
 }
89
 
90
 
90
-static uint8_t check_crc(void) BANKED {
91
+static uint8_t check_crc(void) {
91
     return (calc_crc() == scores_crc) ? 1 : 0;
92
     return (calc_crc() == scores_crc) ? 1 : 0;
92
 }
93
 }
93
 
94
 
94
 static void score_init(void) NONBANKED {
95
 static void score_init(void) NONBANKED {
95
-    SWITCH_ROM(BANK(score));
96
-    memcpy(scores, initial_scores, sizeof(scores));
97
-    scores_crc = calc_crc();
96
+    START_ROM_BANK(BANK(score));
97
+        memcpy(scores, initial_scores, sizeof(scores));
98
+    END_ROM_BANK();
98
 }
99
 }
99
 
100
 
100
-static uint8_t score_pos(int32_t score) BANKED {
101
+static uint8_t score_pos(int32_t score) {
101
     if (score > 0) {
102
     if (score > 0) {
102
         for (uint8_t i = 0; i < SCORE_NUM; i++) {
103
         for (uint8_t i = 0; i < SCORE_NUM; i++) {
103
             if (score > scores[i].score) {
104
             if (score > scores[i].score) {
122
     // initialize score table when data is invalid
123
     // initialize score table when data is invalid
123
     if (!check_crc()) {
124
     if (!check_crc()) {
124
         score_init();
125
         score_init();
126
+        scores_crc = calc_crc();
125
     }
127
     }
126
 
128
 
127
     uint8_t r = (score_pos(score) < (SCORE_NUM * 2)) ? 1 : 0;
129
     uint8_t r = (score_pos(score) < (SCORE_NUM * 2)) ? 1 : 0;
137
     // initialize score table when data is invalid
139
     // initialize score table when data is invalid
138
     if (!check_crc()) {
140
     if (!check_crc()) {
139
         score_init();
141
         score_init();
142
+        scores_crc = calc_crc();
140
     }
143
     }
141
 
144
 
142
     uint8_t new = score_pos(score.score);
145
     uint8_t new = score_pos(score.score);
162
     // initialize score table when data is invalid
165
     // initialize score table when data is invalid
163
     if (!check_crc()) {
166
     if (!check_crc()) {
164
         score_init();
167
         score_init();
168
+        scores_crc = calc_crc();
165
     }
169
     }
166
 
170
 
167
     if (off >= SCORE_NUM) {
171
     if (off >= SCORE_NUM) {
180
     // initialize score table when data is invalid
184
     // initialize score table when data is invalid
181
     if (!check_crc()) {
185
     if (!check_crc()) {
182
         score_init();
186
         score_init();
187
+        scores_crc = calc_crc();
183
     }
188
     }
184
 
189
 
185
     if (off >= SCORE_NUM) {
190
     if (off >= SCORE_NUM) {
195
     ENABLE_RAM;
200
     ENABLE_RAM;
196
     SWITCH_RAM(0);
201
     SWITCH_RAM(0);
197
     score_init();
202
     score_init();
203
+    scores_crc = calc_crc();
198
     DISABLE_RAM;
204
     DISABLE_RAM;
199
 }
205
 }

+ 20
- 16
src/sprites.c View File

20
  * See <http://www.gnu.org/licenses/>.
20
  * See <http://www.gnu.org/licenses/>.
21
  */
21
  */
22
 
22
 
23
+#include "banks.h"
23
 #include "sprite_data.h"
24
 #include "sprite_data.h"
24
 
25
 
25
 void spr_init(void) NONBANKED {
26
 void spr_init(void) NONBANKED {
26
     uint8_t off = TILE_NUM_START;
27
     uint8_t off = TILE_NUM_START;
27
     for (uint8_t i = 0; i < SPRITE_COUNT; i++) {
28
     for (uint8_t i = 0; i < SPRITE_COUNT; i++) {
28
-        SWITCH_ROM(metasprites[i].bank);
29
-
30
-        if (metasprites[i].off == TILE_NUM_START) {
31
-            metasprites[i].off = off;
32
-            off += metasprites[i].cnt;
33
-            set_sprite_data(metasprites[i].off, metasprites[i].cnt, metasprites[i].ti);
34
-        } else {
35
-            metasprites[i].off = metasprites[metasprites[i].off].off;
36
-        }
29
+        START_ROM_BANK(metasprites[i].bank);
30
+            if (metasprites[i].off == TILE_NUM_START) {
31
+                metasprites[i].off = off;
32
+                off += metasprites[i].cnt;
33
+                set_sprite_data(metasprites[i].off, metasprites[i].cnt, metasprites[i].ti);
34
+            } else {
35
+                metasprites[i].off = metasprites[metasprites[i].off].off;
36
+            }
37
+        END_ROM_BANK();
37
     }
38
     }
38
 }
39
 }
39
 
40
 
40
 void spr_init_pal(void) NONBANKED {
41
 void spr_init_pal(void) NONBANKED {
41
     for (uint8_t i = 0; i < SPRITE_COUNT; i++) {
42
     for (uint8_t i = 0; i < SPRITE_COUNT; i++) {
43
+        uint8_t bank = metasprites[i].bank;
42
         if (metasprites[i].pa == power_palettes) {
44
         if (metasprites[i].pa == power_palettes) {
43
-            SWITCH_ROM(BANK(power_palettes));
44
-        } else {
45
-            SWITCH_ROM(metasprites[i].bank);
45
+            bank = BANK(power_palettes);
46
         }
46
         }
47
 
47
 
48
-        if ((metasprites[i].pa != NULL) && ((metasprites[i].pa_i & PALETTE_ALL_FLAGS) == PALETTE_PRELOAD)) {
49
-            set_sprite_palette(metasprites[i].pa_i, metasprites[i].pa_n, metasprites[i].pa);
50
-        }
48
+        START_ROM_BANK(bank);
49
+            if ((metasprites[i].pa != NULL) && ((metasprites[i].pa_i & PALETTE_ALL_FLAGS) == PALETTE_PRELOAD)) {
50
+                set_sprite_palette(metasprites[i].pa_i, metasprites[i].pa_n, metasprites[i].pa);
51
+            }
52
+        END_ROM_BANK();
51
     }
53
     }
52
 }
54
 }
53
 
55
 
54
 void spr_draw(enum SPRITES sprite, enum SPRITE_FLIP flip,
56
 void spr_draw(enum SPRITES sprite, enum SPRITE_FLIP flip,
55
               int8_t x_off, int8_t y_off, uint8_t frame,
57
               int8_t x_off, int8_t y_off, uint8_t frame,
56
               uint8_t *hiwater) NONBANKED {
58
               uint8_t *hiwater) NONBANKED {
57
-    SWITCH_ROM(metasprites[sprite].bank);
59
+    START_ROM_BANK(metasprites[sprite].bank);
58
 
60
 
59
     if (frame >= metasprites[sprite].ms_n) {
61
     if (frame >= metasprites[sprite].ms_n) {
60
         frame = 0;
62
         frame = 0;
112
                     DEVICE_SPRITE_PX_OFFSET_Y + (DEVICE_SCREEN_PX_HEIGHT / 2) + y_off);
114
                     DEVICE_SPRITE_PX_OFFSET_Y + (DEVICE_SCREEN_PX_HEIGHT / 2) + y_off);
113
             break;
115
             break;
114
     }
116
     }
117
+
118
+    END_ROM_BANK();
115
 }
119
 }
116
 
120
 
117
 void spr_ship(enum SPRITE_ROT rot, uint8_t moving, uint8_t *hiwater) NONBANKED {
121
 void spr_ship(enum SPRITE_ROT rot, uint8_t moving, uint8_t *hiwater) NONBANKED {

Loading…
Cancel
Save