├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── contrib └── lz4 │ ├── LICENSE │ ├── lz4.c │ └── lz4.h ├── doc └── save_format.txt ├── link.ld ├── manifest.txt ├── res ├── LICENSE ├── atlas.png ├── calm1.ogg ├── calm2.ogg ├── calm3.ogg ├── font.png ├── grass1.wav ├── grass2.wav ├── grass3.wav ├── grass4.wav ├── gravel1.ogg ├── gravel2.ogg ├── gravel3.ogg ├── gravel4.ogg ├── icon.png ├── icon.xcf ├── music.txt ├── music_volume.txt ├── stone1.ogg ├── stone2.ogg ├── stone3.ogg ├── stone4.ogg ├── wood1.ogg ├── wood2.ogg ├── wood3.ogg └── wood4.ogg ├── src ├── block_info.h ├── cdrom.c ├── common.h ├── config.h ├── gpu.c ├── gpu_dma.c ├── gui.c ├── joy.c ├── main.c ├── meshes.h ├── options.c ├── save.c ├── sound.c ├── util.c ├── world.c └── worldgen.c ├── system.cnf └── tools ├── 32to15.py ├── bin2h.py ├── bin2s.py ├── lz4pack.c ├── mkatlas.py ├── mkatlas_simple.py ├── mkfont.py ├── mkicon.py ├── mkmusichdr.py └── mksoundbank.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Necessary files 2 | isolicence.* 3 | pscd-new 4 | 5 | # Output 6 | *.o 7 | *.elf 8 | boot.exe 9 | /fromage 10 | fromage.bin 11 | fromage.cue 12 | 13 | # Python 14 | *.pyc 15 | *.pyo 16 | __pycache__ 17 | .mypy_cache 18 | 19 | # VIM 20 | .sw? 21 | .*.sw? 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | = fromage = 2 | 3 | Copyright (c) 2015, 2016, 2017, 2018, 2019, 2020, 2021 asie, GreaseMonkey 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CANDYK=../candyk-psx 2 | CROSSPREFIX=/opt/wonderful/toolchain/gcc-mipsel-elf/bin/mipsel-elf- 3 | 4 | CROSS_CC=$(CROSSPREFIX)gcc 5 | CROSS_AS=$(CROSSPREFIX)as 6 | CROSS_OBJCOPY=$(CROSSPREFIX)objcopy 7 | 8 | RM_F=rm -f 9 | MKISOFS=mkisofs 10 | PYTHON3=python3 11 | 12 | SPUENC=$(CANDYK)/bin/psxavenc 13 | 14 | ASFLAGS = -g -msoft-float 15 | 16 | CFLAGS = -g -c -flto -pipe \ 17 | -fomit-frame-pointer \ 18 | -fno-stack-protector \ 19 | -mno-check-zero-division \ 20 | -msoft-float -nostdlib -mips1 -march=3000 -mtune=3000 \ 21 | -Isrc -Icontrib/lz4 -I$(CANDYK)/include -Wall -Wextra \ 22 | -Wno-shift-negative-value \ 23 | -Wno-unused-variable -Wno-unused-function -Wno-pointer-sign \ 24 | 25 | CFLAGS_FAST = -O3 26 | CFLAGS_SMALL = -Os 27 | ELF2PSXFLAGS = 28 | 29 | LDFLAGS := -Wl,--defsym=vfprintf=__i_vfprintf -Wl,--defsym=vfscanf=__i_vfscanf \ 30 | -g -O3 -flto -Wl,-Tlink.ld -pipe \ 31 | -mtune=3000 -march=3000 \ 32 | -fomit-frame-pointer \ 33 | -fno-stack-protector \ 34 | -mno-check-zero-division \ 35 | \ 36 | -msoft-float \ 37 | -L$(CANDYK)/lib 38 | 39 | LIBS = -lm -lorelei -lsawpads -lseedy -lc -lgcc -lchenboot 40 | 41 | # stuff omitted: 42 | # O2: 43 | # O3: 44 | # -funswitch-loops - slows things down 45 | # -fipa-cp-clone - also slows things down 46 | # 47 | 48 | EXE_NAME=boot 49 | ISO_NAME?=fromage 50 | 51 | OBJDIR = obj 52 | RESDIR ?= res 53 | SRCDIR = src 54 | TOOLSDIR = tools 55 | TOOLSOBJDIR = $(OBJDIR)/tools 56 | 57 | INCLUDES = src/block_info.h src/common.h src/config.h 58 | 59 | MUSIC = \ 60 | $(OBJDIR)/calm1.mus \ 61 | $(OBJDIR)/calm2.mus \ 62 | $(OBJDIR)/calm3.mus 63 | 64 | SOUNDS = \ 65 | $(OBJDIR)/grass1.snd \ 66 | $(OBJDIR)/grass2.snd \ 67 | $(OBJDIR)/grass3.snd \ 68 | $(OBJDIR)/grass4.snd \ 69 | $(OBJDIR)/gravel1.snd \ 70 | $(OBJDIR)/gravel2.snd \ 71 | $(OBJDIR)/gravel3.snd \ 72 | $(OBJDIR)/gravel4.snd \ 73 | $(OBJDIR)/stone1.snd \ 74 | $(OBJDIR)/stone2.snd \ 75 | $(OBJDIR)/stone3.snd \ 76 | $(OBJDIR)/stone4.snd \ 77 | $(OBJDIR)/wood1.snd \ 78 | $(OBJDIR)/wood2.snd \ 79 | $(OBJDIR)/wood3.snd \ 80 | $(OBJDIR)/wood4.snd 81 | 82 | OBJS_FAST = \ 83 | $(OBJDIR)/cdrom.o \ 84 | $(OBJDIR)/gpu.o \ 85 | $(OBJDIR)/gpu_dma.o \ 86 | $(OBJDIR)/joy.o \ 87 | $(OBJDIR)/sound.o \ 88 | $(OBJDIR)/util.o \ 89 | $(OBJDIR)/world.o \ 90 | $(OBJDIR)/worldgen.o \ 91 | \ 92 | $(OBJDIR)/main.o 93 | 94 | OBJS_SMALL = \ 95 | $(OBJDIR)/options.o \ 96 | $(OBJDIR)/gui.o \ 97 | $(OBJDIR)/save.o \ 98 | \ 99 | $(OBJDIR)/font1x.o \ 100 | $(OBJDIR)/font2x.o \ 101 | $(OBJDIR)/icon.o \ 102 | $(OBJDIR)/license_text.o \ 103 | \ 104 | $(OBJDIR)/lz4.o 105 | 106 | # uncomment for standalone boot.exe build 107 | ifeq ($(TYPE),exe) 108 | CFLAGS += -DSTANDALONE_EXE 109 | endif 110 | 111 | # uncomment for PAL build 112 | ifeq ($(REGION),europe) 113 | CFLAGS += -DREGION_EUROPE 114 | ELF2PSXFLAGS += -p 115 | endif 116 | 117 | # uncomment for NTSC build 118 | ifeq ($(REGION),usa) 119 | CFLAGS += -DREGION_USA 120 | ELF2PSXFLAGS += -n 121 | endif 122 | 123 | ifeq ($(VIDEO_WIDTH),640) 124 | CFLAGS += -DVID_USE_640 125 | endif 126 | 127 | OBJS = $(OBJS_FAST) $(OBJS_SMALL) 128 | 129 | ifeq ($(TYPE),exe) 130 | all: $(EXE_NAME).exe 131 | else 132 | all: $(EXE_NAME).exe $(ISO_NAME).cue 133 | endif 134 | 135 | clean: 136 | $(RM_F) $(OBJS) $(OBJDIR)/$(EXE_NAME).elf $(ISO_NAME).bin $(ISO_NAME).cue 137 | $(RM_F) $(OBJDIR)/font.s $(OBJDIR)/icon.s 138 | $(RM_F) $(OBJDIR)/atlas.raw $(OBJDIR)/font.raw $(OBJDIR)/icon.raw 139 | $(RM_F) atlas.lz4 sounds.lz4 $(OBJDIR)/atlas.lz4.h 140 | $(RM_F) $(OBJDIR)/atlas.lz4.o 141 | $(RM_F) $(OBJDIR)/soundbank.raw 142 | $(RM_F) $(SOUNDS) 143 | $(RM_F) $(MUSIC) music.hdr music.xa 144 | $(RM_F) $(OBJDIR)/license_text.s $(OBJDIR)/license_text.txt 145 | 146 | $(ISO_NAME).cue: $(ISO_NAME) music.hdr music.xa atlas.lz4 sounds.lz4 manifest.txt 147 | $(CANDYK)/bin/pscd-new manifest.txt 148 | 149 | $(ISO_NAME): $(EXE_NAME).exe 150 | $(MKISOFS) -o $(ISO_NAME) system.cnf $(EXE_NAME).exe 151 | 152 | $(EXE_NAME).exe: $(OBJDIR)/$(EXE_NAME).elf 153 | $(CANDYK)/bin/elf2psx $(ELF2PSXFLAGS) $(OBJDIR)/$(EXE_NAME).elf $(EXE_NAME).exe 154 | 155 | $(OBJDIR)/$(EXE_NAME).elf: $(OBJS) link.ld 156 | $(CROSS_CC) -o $(OBJDIR)/$(EXE_NAME).elf $(LDFLAGS) $(OBJS) $(LIBS) 157 | 158 | music.hdr: $(MUSIC) $(RESDIR)/music_volume.txt 159 | $(PYTHON3) $(TOOLSDIR)/mkmusichdr.py music.hdr $(RESDIR)/music_volume.txt $(MUSIC) 160 | 161 | music.xa: $(MUSIC) $(RESDIR)/music.txt 162 | $(CANDYK)/bin/xainterleave $(RESDIR)/music.txt music.xa 163 | 164 | $(OBJDIR)/soundbank.raw: $(SOUNDS) 165 | $(PYTHON3) $(TOOLSDIR)/mksoundbank.py $(OBJDIR)/soundbank.raw $(SOUNDS) 166 | 167 | $(OBJDIR)/%.snd: $(RESDIR)/%.ogg 168 | $(SPUENC) -f 22050 -t spu -c 1 -b 4 $< $@ 169 | 170 | $(OBJDIR)/%.snd: $(RESDIR)/%.wav 171 | $(SPUENC) -f 22050 -t spu -c 1 -b 4 $< $@ 172 | 173 | $(OBJDIR)/calm1.mus: $(RESDIR)/calm1.ogg 174 | $(SPUENC) -f 37800 -t xacd -c 2 -b 4 -F 1 -C 0 $< $@ 175 | 176 | $(OBJDIR)/calm2.mus: $(RESDIR)/calm2.ogg 177 | $(SPUENC) -f 37800 -t xacd -c 2 -b 4 -F 1 -C 1 $< $@ 178 | 179 | $(OBJDIR)/calm3.mus: $(RESDIR)/calm3.ogg 180 | $(SPUENC) -f 37800 -t xacd -c 2 -b 4 -F 1 -C 2 $< $@ 181 | 182 | $(OBJS_FAST): CFLAGS := $(CFLAGS) $(CFLAGS_FAST) 183 | $(OBJS_SMALL): CFLAGS := $(CFLAGS) $(CFLAGS_SMALL) 184 | 185 | $(OBJDIR)/%.o: $(SRCDIR)/%.c $(INCLUDES) atlas.lz4 186 | $(CROSS_CC) -c -o $@ $(CFLAGS) $< 187 | 188 | $(OBJDIR)/%.o: $(SRCDIR)/%.s 189 | $(CROSS_CC) -g -c -o $@ $(CFLAGS) $< 190 | 191 | sounds.lz4: $(OBJDIR)/soundbank.raw $(OBJDIR)/lz4pack 192 | $(OBJDIR)/lz4pack -o 32 -p $(OBJDIR)/soundbank.raw sounds.lz4 193 | 194 | $(OBJDIR)/lz4.o: contrib/lz4/lz4.c contrib/lz4/lz4.h 195 | $(CROSS_CC) -c -o $@ $(CFLAGS) $< 196 | 197 | $(OBJDIR)/lz4pack: contrib/lz4/lz4.c contrib/lz4/lz4.h tools/lz4pack.c 198 | $(CC) -O2 -Icontrib/lz4 -o $@ contrib/lz4/lz4.c tools/lz4pack.c 199 | 200 | atlas.lz4: $(OBJDIR)/atlas.raw $(OBJDIR)/lz4pack $(TOOLSDIR)/bin2h.py 201 | $(OBJDIR)/lz4pack $(OBJDIR)/atlas.raw atlas.lz4 202 | $(PYTHON3) $(TOOLSDIR)/bin2h.py atlas.lz4 > $(OBJDIR)/atlas.lz4.h 203 | 204 | $(OBJDIR)/atlas.raw: $(RESDIR)/atlas.png $(TOOLSDIR)/mkatlas.py 205 | $(PYTHON3) $(TOOLSDIR)/mkatlas.py $(RESDIR)/atlas.png $(OBJDIR)/atlas.raw 206 | 207 | $(OBJDIR)/font1x.o: $(OBJDIR)/font1x.raw $(TOOLSDIR)/bin2s.py $(INCLUDES) 208 | $(PYTHON3) $(TOOLSDIR)/bin2s.py $(OBJDIR)/font1x.raw > $(OBJDIR)/font1x.s 209 | $(CROSS_AS) -o $@ $(ASFLAGS) $(OBJDIR)/font1x.s 210 | 211 | $(OBJDIR)/font2x.o: $(OBJDIR)/font2x.raw $(TOOLSDIR)/bin2s.py $(INCLUDES) 212 | $(PYTHON3) $(TOOLSDIR)/bin2s.py $(OBJDIR)/font2x.raw > $(OBJDIR)/font2x.s 213 | $(CROSS_AS) -o $@ $(ASFLAGS) $(OBJDIR)/font2x.s 214 | 215 | $(OBJDIR)/font1x.raw: $(RESDIR)/font.png 216 | $(PYTHON3) $(TOOLSDIR)/mkfont.py $(RESDIR)/font.png $(OBJDIR)/font1x.raw 1x 217 | 218 | $(OBJDIR)/font2x.raw: $(RESDIR)/font.png 219 | $(PYTHON3) $(TOOLSDIR)/mkfont.py $(RESDIR)/font.png $(OBJDIR)/font2x.raw 2x 220 | 221 | $(OBJDIR)/icon.raw: $(RESDIR)/icon.png 222 | $(PYTHON3) $(TOOLSDIR)/mkicon.py $(RESDIR)/icon.png $(OBJDIR)/icon.raw 223 | 224 | $(OBJDIR)/icon.o: $(OBJDIR)/icon.raw $(TOOLSDIR)/bin2s.py $(INCLUDES) 225 | $(PYTHON3) $(TOOLSDIR)/bin2s.py $(OBJDIR)/icon.raw > $(OBJDIR)/icon.s 226 | $(CROSS_AS) -o $@ $(ASFLAGS) $(OBJDIR)/icon.s 227 | 228 | $(OBJDIR)/license_text.txt: LICENSE $(RESDIR)/LICENSE contrib/lz4/LICENSE 229 | cat LICENSE > $(OBJDIR)/license_text.txt 230 | echo "" >> $(OBJDIR)/license_text.txt 231 | echo "--------" >> $(OBJDIR)/license_text.txt 232 | echo "" >> $(OBJDIR)/license_text.txt 233 | cat $(RESDIR)/LICENSE >> $(OBJDIR)/license_text.txt 234 | echo "" >> $(OBJDIR)/license_text.txt 235 | echo "--------" >> $(OBJDIR)/license_text.txt 236 | echo "" >> $(OBJDIR)/license_text.txt 237 | cat contrib/lz4/LICENSE >> $(OBJDIR)/license_text.txt 238 | 239 | $(OBJDIR)/license_text.o: $(OBJDIR)/license_text.txt $(TOOLSDIR)/bin2s.py $(INCLUDES) 240 | $(PYTHON3) $(TOOLSDIR)/bin2s.py $(OBJDIR)/license_text.txt > $(OBJDIR)/license_text.s 241 | $(CROSS_AS) -o $@ $(ASFLAGS) $(OBJDIR)/license_text.s 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fromage 2 | 3 | fromage is a voxel engine implementation for the PlayStation 1 video game console. 4 | 5 | ## Compilation (2024) 6 | 7 | At this time, fromage is **very** unsupported. You're on your own with this one - I can't promise I will be able to help. 8 | 9 | Requirements: 10 | 11 | * Linux 12 | * [Wonderful Toolchain](https://wonderful.asie.pl/docs/getting-started/) 13 | * Python 3 and requisite dependencies: python3-numpy, python3-scipy, python3-Pillow 14 | * mkisofs 15 | 16 | 1. Install the MIPS GCC toolchain from Wonderful: `wf-pacman -S toolchain-gcc-mipsel-elf` 17 | 2. Clone candyk-psx to be in a directory adjacent to fromage: `cd ..`, `git clone https://github.com/ChenThread/candyk-psx` 18 | 3. Compile candyk-psx using the Wonderful toolchain: `cd candyk-psx`, `export PATH=/opt/wonderful/toolchain/gcc-mipsel-elf/bin:$PATH`, `make` 19 | 4. Compile Fromage: `cd ../fromage`, `make TYPE=exe REGION=europe` 20 | -------------------------------------------------------------------------------- /contrib/lz4/LICENSE: -------------------------------------------------------------------------------- 1 | LZ4 Library 2 | Copyright (c) 2011-2016, Yann Collet 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, this 12 | list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /contrib/lz4/lz4.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LZ4 - Fast LZ compression algorithm 3 | * Header File 4 | * Copyright (C) 2011-present, Yann Collet. 5 | 6 | BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the following disclaimer 16 | in the documentation and/or other materials provided with the 17 | distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | You can contact the author at : 32 | - LZ4 homepage : http://www.lz4.org 33 | - LZ4 source repository : https://github.com/lz4/lz4 34 | */ 35 | #if defined (__cplusplus) 36 | extern "C" { 37 | #endif 38 | 39 | #ifndef LZ4_H_2983827168210 40 | #define LZ4_H_2983827168210 41 | 42 | /* --- Dependency --- */ 43 | #include /* size_t */ 44 | 45 | 46 | /** 47 | Introduction 48 | 49 | LZ4 is lossless compression algorithm, providing compression speed at 500 MB/s per core, 50 | scalable with multi-cores CPU. It features an extremely fast decoder, with speed in 51 | multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. 52 | 53 | The LZ4 compression library provides in-memory compression and decompression functions. 54 | It gives full buffer control to user. 55 | Compression can be done in: 56 | - a single step (described as Simple Functions) 57 | - a single step, reusing a context (described in Advanced Functions) 58 | - unbounded multiple steps (described as Streaming compression) 59 | 60 | lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). 61 | Decompressing a block requires additional metadata, such as its compressed size. 62 | Each application is free to encode and pass such metadata in whichever way it wants. 63 | 64 | lz4.h only handle blocks, it can not generate Frames. 65 | 66 | Blocks are different from Frames (doc/lz4_Frame_format.md). 67 | Frames bundle both blocks and metadata in a specified manner. 68 | Embedding metadata is required for compressed data to be self-contained and portable. 69 | Frame format is delivered through a companion API, declared in lz4frame.h. 70 | The `lz4` CLI can only manage frames. 71 | */ 72 | 73 | /*^*************************************************************** 74 | * Export parameters 75 | *****************************************************************/ 76 | /* 77 | * LZ4_DLL_EXPORT : 78 | * Enable exporting of functions when building a Windows DLL 79 | * LZ4LIB_VISIBILITY : 80 | * Control library symbols visibility. 81 | */ 82 | #ifndef LZ4LIB_VISIBILITY 83 | # if defined(__GNUC__) && (__GNUC__ >= 4) 84 | # define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) 85 | # else 86 | # define LZ4LIB_VISIBILITY 87 | # endif 88 | #endif 89 | #if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) 90 | # define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY 91 | #elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) 92 | # define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ 93 | #else 94 | # define LZ4LIB_API LZ4LIB_VISIBILITY 95 | #endif 96 | 97 | /*------ Version ------*/ 98 | #define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ 99 | #define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ 100 | #define LZ4_VERSION_RELEASE 1 /* for tweaks, bug-fixes, or development */ 101 | 102 | #define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) 103 | 104 | #define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE 105 | #define LZ4_QUOTE(str) #str 106 | #define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) 107 | #define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) 108 | 109 | LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ 110 | LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ 111 | 112 | 113 | /*-************************************ 114 | * Tuning parameter 115 | **************************************/ 116 | /*! 117 | * LZ4_MEMORY_USAGE : 118 | * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) 119 | * Increasing memory usage improves compression ratio. 120 | * Reduced memory usage may improve speed, thanks to better cache locality. 121 | * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache 122 | */ 123 | #ifndef LZ4_MEMORY_USAGE 124 | # define LZ4_MEMORY_USAGE 14 125 | #endif 126 | 127 | 128 | /*-************************************ 129 | * Simple Functions 130 | **************************************/ 131 | /*! LZ4_compress_default() : 132 | Compresses 'srcSize' bytes from buffer 'src' 133 | into already allocated 'dst' buffer of size 'dstCapacity'. 134 | Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). 135 | It also runs faster, so it's a recommended setting. 136 | If the function cannot compress 'src' into a more limited 'dst' budget, 137 | compression stops *immediately*, and the function result is zero. 138 | In which case, 'dst' content is undefined (invalid). 139 | srcSize : max supported value is LZ4_MAX_INPUT_SIZE. 140 | dstCapacity : size of buffer 'dst' (which must be already allocated) 141 | @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) 142 | or 0 if compression fails 143 | Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). 144 | */ 145 | LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); 146 | 147 | /*! LZ4_decompress_safe() : 148 | compressedSize : is the exact complete size of the compressed block. 149 | dstCapacity : is the size of destination buffer, which must be already allocated. 150 | @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) 151 | If destination buffer is not large enough, decoding will stop and output an error code (negative value). 152 | If the source stream is detected malformed, the function will stop decoding and return a negative result. 153 | Note : This function is protected against malicious data packets (never writes outside 'dst' buffer, nor read outside 'source' buffer). 154 | */ 155 | LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); 156 | 157 | 158 | /*-************************************ 159 | * Advanced Functions 160 | **************************************/ 161 | #define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ 162 | #define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) 163 | 164 | /*! LZ4_compressBound() : 165 | Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) 166 | This function is primarily useful for memory allocation purposes (destination buffer size). 167 | Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). 168 | Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) 169 | inputSize : max supported value is LZ4_MAX_INPUT_SIZE 170 | return : maximum output size in a "worst case" scenario 171 | or 0, if input size is incorrect (too large or negative) 172 | */ 173 | LZ4LIB_API int LZ4_compressBound(int inputSize); 174 | 175 | /*! LZ4_compress_fast() : 176 | Same as LZ4_compress_default(), but allows selection of "acceleration" factor. 177 | The larger the acceleration value, the faster the algorithm, but also the lesser the compression. 178 | It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. 179 | An acceleration value of "1" is the same as regular LZ4_compress_default() 180 | Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). 181 | */ 182 | LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); 183 | 184 | 185 | /*! LZ4_compress_fast_extState() : 186 | * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. 187 | * Use LZ4_sizeofState() to know how much memory must be allocated, 188 | * and allocate it on 8-bytes boundaries (using `malloc()` typically). 189 | * Then, provide this buffer as `void* state` to compression function. 190 | */ 191 | LZ4LIB_API int LZ4_sizeofState(void); 192 | LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); 193 | 194 | 195 | /*! LZ4_compress_destSize() : 196 | * Reverse the logic : compresses as much data as possible from 'src' buffer 197 | * into already allocated buffer 'dst', of size >= 'targetDestSize'. 198 | * This function either compresses the entire 'src' content into 'dst' if it's large enough, 199 | * or fill 'dst' buffer completely with as much data as possible from 'src'. 200 | * note: acceleration parameter is fixed to "default". 201 | * 202 | * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. 203 | * New value is necessarily <= input value. 204 | * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) 205 | * or 0 if compression fails. 206 | */ 207 | LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); 208 | 209 | 210 | /*! LZ4_decompress_safe_partial() : 211 | * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', 212 | * into destination buffer 'dst' of size 'dstCapacity'. 213 | * Up to 'targetOutputSize' bytes will be decoded. 214 | * The function stops decoding on reaching this objective, 215 | * which can boost performance when only the beginning of a block is required. 216 | * 217 | * @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) 218 | * If source stream is detected malformed, function returns a negative result. 219 | * 220 | * Note : @return can be < targetOutputSize, if compressed block contains less data. 221 | * 222 | * Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, 223 | * and expects targetOutputSize <= dstCapacity. 224 | * It effectively stops decoding on reaching targetOutputSize, 225 | * so dstCapacity is kind of redundant. 226 | * This is because in a previous version of this function, 227 | * decoding operation would not "break" a sequence in the middle. 228 | * As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, 229 | * it could write more bytes, though only up to dstCapacity. 230 | * Some "margin" used to be required for this operation to work properly. 231 | * This is no longer necessary. 232 | * The function nonetheless keeps its signature, in an effort to not break API. 233 | */ 234 | LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); 235 | 236 | 237 | /*-********************************************* 238 | * Streaming Compression Functions 239 | ***********************************************/ 240 | typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ 241 | 242 | LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); 243 | LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); 244 | 245 | /*! LZ4_resetStream_fast() : v1.9.0+ 246 | * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks 247 | * (e.g., LZ4_compress_fast_continue()). 248 | * 249 | * An LZ4_stream_t must be initialized once before usage. 250 | * This is automatically done when created by LZ4_createStream(). 251 | * However, should the LZ4_stream_t be simply declared on stack (for example), 252 | * it's necessary to initialize it first, using LZ4_initStream(). 253 | * 254 | * After init, start any new stream with LZ4_resetStream_fast(). 255 | * A same LZ4_stream_t can be re-used multiple times consecutively 256 | * and compress multiple streams, 257 | * provided that it starts each new stream with LZ4_resetStream_fast(). 258 | * 259 | * LZ4_resetStream_fast() is much faster than LZ4_initStream(), 260 | * but is not compatible with memory regions containing garbage data. 261 | * 262 | * Note: it's only useful to call LZ4_resetStream_fast() 263 | * in the context of streaming compression. 264 | * The *extState* functions perform their own resets. 265 | * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. 266 | */ 267 | LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); 268 | 269 | /*! LZ4_loadDict() : 270 | * Use this function to reference a static dictionary into LZ4_stream_t. 271 | * The dictionary must remain available during compression. 272 | * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. 273 | * The same dictionary will have to be loaded on decompression side for successful decoding. 274 | * Dictionary are useful for better compression of small data (KB range). 275 | * While LZ4 accept any input as dictionary, 276 | * results are generally better when using Zstandard's Dictionary Builder. 277 | * Loading a size of 0 is allowed, and is the same as reset. 278 | * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) 279 | */ 280 | LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); 281 | 282 | /*! LZ4_compress_fast_continue() : 283 | * Compress 'src' content using data from previously compressed blocks, for better compression ratio. 284 | * 'dst' buffer must be already allocated. 285 | * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. 286 | * 287 | * @return : size of compressed block 288 | * or 0 if there is an error (typically, cannot fit into 'dst'). 289 | * 290 | * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. 291 | * Each block has precise boundaries. 292 | * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. 293 | * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. 294 | * 295 | * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! 296 | * 297 | * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. 298 | * Make sure that buffers are separated, by at least one byte. 299 | * This construction ensures that each block only depends on previous block. 300 | * 301 | * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. 302 | * 303 | * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. 304 | */ 305 | LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); 306 | 307 | /*! LZ4_saveDict() : 308 | * If last 64KB data cannot be guaranteed to remain available at its current memory location, 309 | * save it into a safer place (char* safeBuffer). 310 | * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), 311 | * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. 312 | * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. 313 | */ 314 | LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); 315 | 316 | 317 | /*-********************************************** 318 | * Streaming Decompression Functions 319 | * Bufferless synchronous API 320 | ************************************************/ 321 | typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ 322 | 323 | /*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : 324 | * creation / destruction of streaming decompression tracking context. 325 | * A tracking context can be re-used multiple times. 326 | */ 327 | LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); 328 | LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); 329 | 330 | /*! LZ4_setStreamDecode() : 331 | * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. 332 | * Use this function to start decompression of a new stream of blocks. 333 | * A dictionary can optionally be set. Use NULL or size 0 for a reset order. 334 | * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. 335 | * @return : 1 if OK, 0 if error 336 | */ 337 | LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); 338 | 339 | /*! LZ4_decoderRingBufferSize() : v1.8.2+ 340 | * Note : in a ring buffer scenario (optional), 341 | * blocks are presumed decompressed next to each other 342 | * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), 343 | * at which stage it resumes from beginning of ring buffer. 344 | * When setting such a ring buffer for streaming decompression, 345 | * provides the minimum size of this ring buffer 346 | * to be compatible with any source respecting maxBlockSize condition. 347 | * @return : minimum ring buffer size, 348 | * or 0 if there is an error (invalid maxBlockSize). 349 | */ 350 | LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); 351 | #define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ 352 | 353 | /*! LZ4_decompress_*_continue() : 354 | * These decoding functions allow decompression of consecutive blocks in "streaming" mode. 355 | * A block is an unsplittable entity, it must be presented entirely to a decompression function. 356 | * Decompression functions only accepts one block at a time. 357 | * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. 358 | * If less than 64KB of data has been decoded, all the data must be present. 359 | * 360 | * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : 361 | * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). 362 | * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. 363 | * In which case, encoding and decoding buffers do not need to be synchronized. 364 | * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. 365 | * - Synchronized mode : 366 | * Decompression buffer size is _exactly_ the same as compression buffer size, 367 | * and follows exactly same update rule (block boundaries at same positions), 368 | * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), 369 | * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). 370 | * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. 371 | * In which case, encoding and decoding buffers do not need to be synchronized, 372 | * and encoding ring buffer can have any size, including small ones ( < 64 KB). 373 | * 374 | * Whenever these conditions are not possible, 375 | * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, 376 | * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. 377 | */ 378 | LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); 379 | 380 | 381 | /*! LZ4_decompress_*_usingDict() : 382 | * These decoding functions work the same as 383 | * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() 384 | * They are stand-alone, and don't need an LZ4_streamDecode_t structure. 385 | * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. 386 | * Performance tip : Decompression speed can be substantially increased 387 | * when dst == dictStart + dictSize. 388 | */ 389 | LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); 390 | 391 | #endif /* LZ4_H_2983827168210 */ 392 | 393 | 394 | /*^************************************* 395 | * !!!!!! STATIC LINKING ONLY !!!!!! 396 | ***************************************/ 397 | 398 | /*-**************************************************************************** 399 | * Experimental section 400 | * 401 | * Symbols declared in this section must be considered unstable. Their 402 | * signatures or semantics may change, or they may be removed altogether in the 403 | * future. They are therefore only safe to depend on when the caller is 404 | * statically linked against the library. 405 | * 406 | * To protect against unsafe usage, not only are the declarations guarded, 407 | * the definitions are hidden by default 408 | * when building LZ4 as a shared/dynamic library. 409 | * 410 | * In order to access these declarations, 411 | * define LZ4_STATIC_LINKING_ONLY in your application 412 | * before including LZ4's headers. 413 | * 414 | * In order to make their implementations accessible dynamically, you must 415 | * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. 416 | ******************************************************************************/ 417 | 418 | #ifdef LZ4_STATIC_LINKING_ONLY 419 | 420 | #ifndef LZ4_STATIC_3504398509 421 | #define LZ4_STATIC_3504398509 422 | 423 | #ifdef LZ4_PUBLISH_STATIC_FUNCTIONS 424 | #define LZ4LIB_STATIC_API LZ4LIB_API 425 | #else 426 | #define LZ4LIB_STATIC_API 427 | #endif 428 | 429 | 430 | /*! LZ4_compress_fast_extState_fastReset() : 431 | * A variant of LZ4_compress_fast_extState(). 432 | * 433 | * Using this variant avoids an expensive initialization step. 434 | * It is only safe to call if the state buffer is known to be correctly initialized already 435 | * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). 436 | * From a high level, the difference is that 437 | * this function initializes the provided state with a call to something like LZ4_resetStream_fast() 438 | * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). 439 | */ 440 | LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); 441 | 442 | /*! LZ4_attach_dictionary() : 443 | * This is an experimental API that allows 444 | * efficient use of a static dictionary many times. 445 | * 446 | * Rather than re-loading the dictionary buffer into a working context before 447 | * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a 448 | * working LZ4_stream_t, this function introduces a no-copy setup mechanism, 449 | * in which the working stream references the dictionary stream in-place. 450 | * 451 | * Several assumptions are made about the state of the dictionary stream. 452 | * Currently, only streams which have been prepared by LZ4_loadDict() should 453 | * be expected to work. 454 | * 455 | * Alternatively, the provided dictionaryStream may be NULL, 456 | * in which case any existing dictionary stream is unset. 457 | * 458 | * If a dictionary is provided, it replaces any pre-existing stream history. 459 | * The dictionary contents are the only history that can be referenced and 460 | * logically immediately precede the data compressed in the first subsequent 461 | * compression call. 462 | * 463 | * The dictionary will only remain attached to the working stream through the 464 | * first compression call, at the end of which it is cleared. The dictionary 465 | * stream (and source buffer) must remain in-place / accessible / unchanged 466 | * through the completion of the first compression call on the stream. 467 | */ 468 | LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream); 469 | 470 | 471 | /*! In-place compression and decompression 472 | * 473 | * It's possible to have input and output sharing the same buffer, 474 | * for highly contrained memory environments. 475 | * In both cases, it requires input to lay at the end of the buffer, 476 | * and decompression to start at beginning of the buffer. 477 | * Buffer size must feature some margin, hence be larger than final size. 478 | * 479 | * |<------------------------buffer--------------------------------->| 480 | * |<-----------compressed data--------->| 481 | * |<-----------decompressed size------------------>| 482 | * |<----margin---->| 483 | * 484 | * This technique is more useful for decompression, 485 | * since decompressed size is typically larger, 486 | * and margin is short. 487 | * 488 | * In-place decompression will work inside any buffer 489 | * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). 490 | * This presumes that decompressedSize > compressedSize. 491 | * Otherwise, it means compression actually expanded data, 492 | * and it would be more efficient to store such data with a flag indicating it's not compressed. 493 | * This can happen when data is not compressible (already compressed, or encrypted). 494 | * 495 | * For in-place compression, margin is larger, as it must be able to cope with both 496 | * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, 497 | * and data expansion, which can happen when input is not compressible. 498 | * As a consequence, buffer size requirements are much higher, 499 | * and memory savings offered by in-place compression are more limited. 500 | * 501 | * There are ways to limit this cost for compression : 502 | * - Reduce history size, by modifying LZ4_DISTANCE_MAX. 503 | * Note that it is a compile-time constant, so all compressions will apply this limit. 504 | * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, 505 | * so it's a reasonable trick when inputs are known to be small. 506 | * - Require the compressor to deliver a "maximum compressed size". 507 | * This is the `dstCapacity` parameter in `LZ4_compress*()`. 508 | * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, 509 | * in which case, the return code will be 0 (zero). 510 | * The caller must be ready for these cases to happen, 511 | * and typically design a backup scheme to send data uncompressed. 512 | * The combination of both techniques can significantly reduce 513 | * the amount of margin required for in-place compression. 514 | * 515 | * In-place compression can work in any buffer 516 | * which size is >= (maxCompressedSize) 517 | * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. 518 | * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, 519 | * so it's possible to reduce memory requirements by playing with them. 520 | */ 521 | 522 | #define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) 523 | #define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ 524 | 525 | #ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ 526 | # define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ 527 | #endif 528 | 529 | #define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ 530 | #define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ 531 | 532 | #endif /* LZ4_STATIC_3504398509 */ 533 | #endif /* LZ4_STATIC_LINKING_ONLY */ 534 | 535 | 536 | 537 | #ifndef LZ4_H_98237428734687 538 | #define LZ4_H_98237428734687 539 | 540 | /*-************************************************************ 541 | * PRIVATE DEFINITIONS 542 | ************************************************************** 543 | * Do not use these definitions directly. 544 | * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. 545 | * Accessing members will expose code to API and/or ABI break in future versions of the library. 546 | **************************************************************/ 547 | #define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) 548 | #define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) 549 | #define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ 550 | 551 | #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) 552 | #include 553 | 554 | typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; 555 | struct LZ4_stream_t_internal { 556 | uint32_t hashTable[LZ4_HASH_SIZE_U32]; 557 | uint32_t currentOffset; 558 | uint16_t dirty; 559 | uint16_t tableType; 560 | const uint8_t* dictionary; 561 | const LZ4_stream_t_internal* dictCtx; 562 | uint32_t dictSize; 563 | }; 564 | 565 | typedef struct { 566 | const uint8_t* externalDict; 567 | size_t extDictSize; 568 | const uint8_t* prefixEnd; 569 | size_t prefixSize; 570 | } LZ4_streamDecode_t_internal; 571 | 572 | #else 573 | 574 | typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; 575 | struct LZ4_stream_t_internal { 576 | unsigned int hashTable[LZ4_HASH_SIZE_U32]; 577 | unsigned int currentOffset; 578 | unsigned short dirty; 579 | unsigned short tableType; 580 | const unsigned char* dictionary; 581 | const LZ4_stream_t_internal* dictCtx; 582 | unsigned int dictSize; 583 | }; 584 | 585 | typedef struct { 586 | const unsigned char* externalDict; 587 | const unsigned char* prefixEnd; 588 | size_t extDictSize; 589 | size_t prefixSize; 590 | } LZ4_streamDecode_t_internal; 591 | 592 | #endif 593 | 594 | /*! LZ4_stream_t : 595 | * information structure to track an LZ4 stream. 596 | * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. 597 | * The structure definition can be convenient for static allocation 598 | * (on stack, or as part of larger structure). 599 | * Init this structure with LZ4_initStream() before first use. 600 | * note : only use this definition in association with static linking ! 601 | * this definition is not API/ABI safe, and may change in a future version. 602 | */ 603 | #define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ ) 604 | #define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) 605 | union LZ4_stream_u { 606 | unsigned long long table[LZ4_STREAMSIZE_U64]; 607 | LZ4_stream_t_internal internal_donotuse; 608 | } ; /* previously typedef'd to LZ4_stream_t */ 609 | 610 | /*! LZ4_initStream() : v1.9.0+ 611 | * An LZ4_stream_t structure must be initialized at least once. 612 | * This is automatically done when invoking LZ4_createStream(), 613 | * but it's not when the structure is simply declared on stack (for example). 614 | * 615 | * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. 616 | * It can also initialize any arbitrary buffer of sufficient size, 617 | * and will @return a pointer of proper type upon initialization. 618 | * 619 | * Note : initialization fails if size and alignment conditions are not respected. 620 | * In which case, the function will @return NULL. 621 | * Note2: An LZ4_stream_t structure guarantees correct alignment and size. 622 | * Note3: Before v1.9.0, use LZ4_resetStream() instead 623 | */ 624 | LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); 625 | 626 | 627 | /*! LZ4_streamDecode_t : 628 | * information structure to track an LZ4 stream during decompression. 629 | * init this structure using LZ4_setStreamDecode() before first use. 630 | * note : only use in association with static linking ! 631 | * this definition is not API/ABI safe, 632 | * and may change in a future version ! 633 | */ 634 | #define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ ) 635 | #define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) 636 | union LZ4_streamDecode_u { 637 | unsigned long long table[LZ4_STREAMDECODESIZE_U64]; 638 | LZ4_streamDecode_t_internal internal_donotuse; 639 | } ; /* previously typedef'd to LZ4_streamDecode_t */ 640 | 641 | 642 | 643 | /*-************************************ 644 | * Obsolete Functions 645 | **************************************/ 646 | 647 | /*! Deprecation warnings 648 | * 649 | * Deprecated functions make the compiler generate a warning when invoked. 650 | * This is meant to invite users to update their source code. 651 | * Should deprecation warnings be a problem, it is generally possible to disable them, 652 | * typically with -Wno-deprecated-declarations for gcc 653 | * or _CRT_SECURE_NO_WARNINGS in Visual. 654 | * 655 | * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS 656 | * before including the header file. 657 | */ 658 | #ifdef LZ4_DISABLE_DEPRECATE_WARNINGS 659 | # define LZ4_DEPRECATED(message) /* disable deprecation warnings */ 660 | #else 661 | # define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) 662 | # if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ 663 | # define LZ4_DEPRECATED(message) [[deprecated(message)]] 664 | # elif (LZ4_GCC_VERSION >= 405) || defined(__clang__) 665 | # define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) 666 | # elif (LZ4_GCC_VERSION >= 301) 667 | # define LZ4_DEPRECATED(message) __attribute__((deprecated)) 668 | # elif defined(_MSC_VER) 669 | # define LZ4_DEPRECATED(message) __declspec(deprecated(message)) 670 | # else 671 | # pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") 672 | # define LZ4_DEPRECATED(message) 673 | # endif 674 | #endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ 675 | 676 | /* Obsolete compression functions */ 677 | LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); 678 | LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); 679 | LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); 680 | LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); 681 | LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); 682 | LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); 683 | 684 | /* Obsolete decompression functions */ 685 | LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); 686 | LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); 687 | 688 | /* Obsolete streaming functions; degraded functionality; do not use! 689 | * 690 | * In order to perform streaming compression, these functions depended on data 691 | * that is no longer tracked in the state. They have been preserved as well as 692 | * possible: using them will still produce a correct output. However, they don't 693 | * actually retain any history between compression calls. The compression ratio 694 | * achieved will therefore be no better than compressing each chunk 695 | * independently. 696 | */ 697 | LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); 698 | LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); 699 | LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); 700 | LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); 701 | 702 | /* Obsolete streaming decoding functions */ 703 | LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); 704 | LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); 705 | 706 | /*! LZ4_decompress_fast() : **unsafe!** 707 | * These functions used to be faster than LZ4_decompress_safe(), 708 | * but it has changed, and they are now slower than LZ4_decompress_safe(). 709 | * This is because LZ4_decompress_fast() doesn't know the input size, 710 | * and therefore must progress more cautiously in the input buffer to not read beyond the end of block. 711 | * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. 712 | * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. 713 | * 714 | * The last remaining LZ4_decompress_fast() specificity is that 715 | * it can decompress a block without knowing its compressed size. 716 | * Such functionality could be achieved in a more secure manner, 717 | * by also providing the maximum size of input buffer, 718 | * but it would require new prototypes, and adaptation of the implementation to this new use case. 719 | * 720 | * Parameters: 721 | * originalSize : is the uncompressed size to regenerate. 722 | * `dst` must be already allocated, its size must be >= 'originalSize' bytes. 723 | * @return : number of bytes read from source buffer (== compressed size). 724 | * The function expects to finish at block's end exactly. 725 | * If the source stream is detected malformed, the function stops decoding and returns a negative result. 726 | * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. 727 | * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. 728 | * Also, since match offsets are not validated, match reads from 'src' may underflow too. 729 | * These issues never happen if input (compressed) data is correct. 730 | * But they may happen if input data is invalid (error or intentional tampering). 731 | * As a consequence, use these functions in trusted environments with trusted data **only**. 732 | */ 733 | 734 | LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") 735 | LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); 736 | LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") 737 | LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); 738 | LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") 739 | LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); 740 | 741 | /*! LZ4_resetStream() : 742 | * An LZ4_stream_t structure must be initialized at least once. 743 | * This is done with LZ4_initStream(), or LZ4_resetStream(). 744 | * Consider switching to LZ4_initStream(), 745 | * invoking LZ4_resetStream() will trigger deprecation warnings in the future. 746 | */ 747 | LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); 748 | 749 | 750 | #endif /* LZ4_H_98237428734687 */ 751 | 752 | 753 | #if defined (__cplusplus) 754 | } 755 | #endif 756 | -------------------------------------------------------------------------------- /doc/save_format.txt: -------------------------------------------------------------------------------- 1 | filename: "B?CHEN-00001_N", where N >= 0 (1..5 typically) 2 | 3 | sector 0: 4 | PS1 title sector 5 | 6 | sector 1: 7 | PS1 icon sector 8 | 9 | sector 2: 10 | fromage header sector 11 | 12 | addr bytes desc 13 | 0x00 1 major data version (1) 14 | 0x01 1 minor data version (0) 15 | 0x02 2 x level size 16 | 0x04 2 y level size 17 | 0x06 2 z level size 18 | 0x08 2 compressed buffer size, in sectors 19 | 0x0A 1 bits 0-6: compressed buffer - size of last sector - 1 (1-128 - 1) 20 | bit 7: reserved 21 | 0x0B 1 setting flags 22 | bit 0: movement (1 - "fun" variant) 23 | bit 1: movement control scheme (0 - left analog, 1 - d-pad) 24 | 0x0C 1 setting render distance, in blocks (the engine may treat this value in any way it wants) 25 | 0x0D 3 resrved (settings?) 26 | 0x10 4 cam_x 27 | 0x14 4 cam_y 28 | 0x18 4 cam_z 29 | 0x1C 2 cam_rx 30 | 0x1E 2 cam_ry 31 | 0x20 1 bits 0-3: hotbar position 32 | bits 4-7: reserved 33 | 0x21 9 hotbar blocks 34 | 0x2A 6 reserved 35 | 0x30 80 reserved 36 | 37 | sector 3-N: 38 | lz4-compressed data of size (xsize*ysize*zsize) 39 | -------------------------------------------------------------------------------- /link.ld: -------------------------------------------------------------------------------- 1 | /* Script for -z combreloc: combine and sort reloc sections */ 2 | /* Copyright (C) 2014-2019 Free Software Foundation, Inc. 3 | Copying and distribution of this script, with or without modification, 4 | are permitted in any medium without royalty provided the copyright 5 | notice and this notice are preserved. */ 6 | OUTPUT_FORMAT("elf32-littlemips", "elf32-bigmips", 7 | "elf32-littlemips") 8 | OUTPUT_ARCH(mips) 9 | ENTRY(_start) 10 | SEARCH_DIR("/home/asie/candyk/mipsel-elf/lib"); 11 | SECTIONS 12 | { 13 | /* Read-only sections, merged into text segment: */ 14 | PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x80010000)); . = SEGMENT_START("text-segment", 0x80010000); 15 | .interp : { *(.interp) } 16 | .MIPS.abiflags : { *(.MIPS.abiflags) } 17 | .reginfo : { *(.reginfo) } 18 | .note.gnu.build-id : { *(.note.gnu.build-id) } 19 | .dynamic : { *(.dynamic) } 20 | .hash : { *(.hash) } 21 | .gnu.hash : { *(.gnu.hash) } 22 | .dynsym : { *(.dynsym) } 23 | .dynstr : { *(.dynstr) } 24 | .gnu.version : { *(.gnu.version) } 25 | .gnu.version_d : { *(.gnu.version_d) } 26 | .gnu.version_r : { *(.gnu.version_r) } 27 | .rel.dyn : 28 | { 29 | *(.rel.init) 30 | *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) 31 | *(.rel.fini) 32 | *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) 33 | *(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*) 34 | *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) 35 | *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) 36 | *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) 37 | *(.rel.ctors) 38 | *(.rel.dtors) 39 | *(.rel.got) 40 | *(.rel.dyn) 41 | *(.rel.sdata .rel.sdata.* .rel.gnu.linkonce.s.*) 42 | *(.rel.sbss .rel.sbss.* .rel.gnu.linkonce.sb.*) 43 | *(.rel.sdata2 .rel.sdata2.* .rel.gnu.linkonce.s2.*) 44 | *(.rel.sbss2 .rel.sbss2.* .rel.gnu.linkonce.sb2.*) 45 | *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) 46 | PROVIDE_HIDDEN (__rel_iplt_start = .); 47 | *(.rel.iplt) 48 | PROVIDE_HIDDEN (__rel_iplt_end = .); 49 | } 50 | .rela.dyn : 51 | { 52 | *(.rela.init) 53 | *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) 54 | *(.rela.fini) 55 | *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) 56 | *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) 57 | *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) 58 | *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) 59 | *(.rela.ctors) 60 | *(.rela.dtors) 61 | *(.rela.got) 62 | *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) 63 | *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) 64 | *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) 65 | *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) 66 | *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) 67 | PROVIDE_HIDDEN (__rela_iplt_start = .); 68 | *(.rela.iplt) 69 | PROVIDE_HIDDEN (__rela_iplt_end = .); 70 | } 71 | .rel.plt : 72 | { 73 | *(.rel.plt) 74 | } 75 | .rela.plt : 76 | { 77 | *(.rela.plt) 78 | } 79 | .init : 80 | { 81 | KEEP (*(SORT_NONE(.init))) 82 | } 83 | .plt : { *(.plt) } 84 | .iplt : { *(.iplt) } 85 | .text : 86 | { 87 | _ftext = .; 88 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 89 | *(.text.exit .text.exit.*) 90 | *(.text.startup .text.startup.*) 91 | *(.text.hot .text.hot.*) 92 | *(.text .stub .text.* .gnu.linkonce.t.*) 93 | /* .gnu.warning sections are handled specially by elf32.em. */ 94 | *(.gnu.warning) 95 | *(.mips16.fn.*) *(.mips16.call.*) 96 | } 97 | .fini : 98 | { 99 | KEEP (*(SORT_NONE(.fini))) 100 | } 101 | PROVIDE (__etext = .); 102 | PROVIDE (_etext = .); 103 | PROVIDE (etext = .); 104 | .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } 105 | .rodata1 : { *(.rodata1) } 106 | .sdata2 : 107 | { 108 | *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) 109 | } 110 | .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } 111 | .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } 112 | .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } 113 | .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } 114 | .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } 115 | /* These sections are generated by the Sun/Oracle C++ compiler. */ 116 | .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } 117 | /* Adjust the address for the data segment. We want to adjust up to 118 | the same address within the page on the next page up. */ 119 | . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); 120 | /* Exception handling */ 121 | .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } 122 | .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } 123 | .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } 124 | .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } 125 | /* Thread Local Storage sections */ 126 | .tdata : 127 | { 128 | PROVIDE_HIDDEN (__tdata_start = .); 129 | *(.tdata .tdata.* .gnu.linkonce.td.*) 130 | } 131 | .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } 132 | .preinit_array : 133 | { 134 | PROVIDE_HIDDEN (__preinit_array_start = .); 135 | KEEP (*(.preinit_array)) 136 | PROVIDE_HIDDEN (__preinit_array_end = .); 137 | } 138 | .init_array : 139 | { 140 | PROVIDE_HIDDEN (__init_array_start = .); 141 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 142 | KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) 143 | PROVIDE_HIDDEN (__init_array_end = .); 144 | } 145 | .fini_array : 146 | { 147 | PROVIDE_HIDDEN (__fini_array_start = .); 148 | KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) 149 | KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) 150 | PROVIDE_HIDDEN (__fini_array_end = .); 151 | } 152 | .ctors : 153 | { 154 | /* gcc uses crtbegin.o to find the start of 155 | the constructors, so we make sure it is 156 | first. Because this is a wildcard, it 157 | doesn't matter if the user does not 158 | actually link against crtbegin.o; the 159 | linker won't look for a file to match a 160 | wildcard. The wildcard also means that it 161 | doesn't matter which directory crtbegin.o 162 | is in. */ 163 | KEEP (*crtbegin.o(.ctors)) 164 | KEEP (*crtbegin?.o(.ctors)) 165 | /* We don't want to include the .ctor section from 166 | the crtend.o file until after the sorted ctors. 167 | The .ctor section from the crtend file contains the 168 | end of ctors marker and it must be last */ 169 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) 170 | KEEP (*(SORT(.ctors.*))) 171 | KEEP (*(.ctors)) 172 | } 173 | .dtors : 174 | { 175 | KEEP (*crtbegin.o(.dtors)) 176 | KEEP (*crtbegin?.o(.dtors)) 177 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) 178 | KEEP (*(SORT(.dtors.*))) 179 | KEEP (*(.dtors)) 180 | } 181 | .jcr : { KEEP (*(.jcr)) } 182 | .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } 183 | . = DATA_SEGMENT_RELRO_END (0, .); 184 | .data : 185 | { 186 | _fdata = .; 187 | *(.data .data.* .gnu.linkonce.d.*) 188 | SORT(CONSTRUCTORS) 189 | } 190 | .data1 : { *(.data1) } 191 | .got.plt : { *(.got.plt) } 192 | . = .; HIDDEN (_gp = ALIGN (16) + 0x7ff0); 193 | .got : { *(.got) } 194 | /* We want the small data sections together, so single-instruction offsets 195 | can access them all, and initialized data all before uninitialized, so 196 | we can shorten the on-disk segment size. */ 197 | .sdata : 198 | { 199 | *(.sdata .sdata.* .gnu.linkonce.s.*) 200 | } 201 | .lit8 : { *(.lit8) } 202 | .lit4 : { *(.lit4) } 203 | _edata = .; PROVIDE (edata = .); 204 | . = .; 205 | __bss_start = .; 206 | _fbss = .; 207 | .sbss : 208 | { 209 | *(.dynsbss) 210 | *(.sbss .sbss.* .gnu.linkonce.sb.*) 211 | *(.scommon) 212 | } 213 | .bss : 214 | { 215 | *(.dynbss) 216 | *(.bss .bss.* .gnu.linkonce.b.*) 217 | *(COMMON) 218 | /* Align here to ensure that the .bss section occupies space up to 219 | _end. Align after .bss to ensure correct alignment even if the 220 | .bss section disappears because there are no input sections. 221 | FIXME: Why do we need it? When there is no .bss section, we do not 222 | pad the .data section. */ 223 | . = ALIGN(. != 0 ? 32 / 8 : 1); 224 | } 225 | . = ALIGN(32 / 8); 226 | . = SEGMENT_START("ldata-segment", .); 227 | . = ALIGN(32 / 8); 228 | _end = .; PROVIDE (end = .); 229 | . = DATA_SEGMENT_END (.); 230 | 231 | /* PS1 fastmem. */ 232 | .fastmem 0x1F800000 (NOLOAD) : { 233 | *(.fastmem) 234 | } 235 | 236 | /* Stabs debugging sections. */ 237 | .stab 0 : { *(.stab) } 238 | .stabstr 0 : { *(.stabstr) } 239 | .stab.excl 0 : { *(.stab.excl) } 240 | .stab.exclstr 0 : { *(.stab.exclstr) } 241 | .stab.index 0 : { *(.stab.index) } 242 | .stab.indexstr 0 : { *(.stab.indexstr) } 243 | .comment 0 : { *(.comment) } 244 | .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } 245 | /* DWARF debug sections. 246 | Symbols in the DWARF debugging sections are relative to the beginning 247 | of the section so we begin them at 0. */ 248 | /* DWARF 1 */ 249 | .debug 0 : { *(.debug) } 250 | .line 0 : { *(.line) } 251 | /* GNU DWARF 1 extensions */ 252 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 253 | .debug_sfnames 0 : { *(.debug_sfnames) } 254 | /* DWARF 1.1 and DWARF 2 */ 255 | .debug_aranges 0 : { *(.debug_aranges) } 256 | .debug_pubnames 0 : { *(.debug_pubnames) } 257 | /* DWARF 2 */ 258 | .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 259 | .debug_abbrev 0 : { *(.debug_abbrev) } 260 | .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } 261 | .debug_frame 0 : { *(.debug_frame) } 262 | .debug_str 0 : { *(.debug_str) } 263 | .debug_loc 0 : { *(.debug_loc) } 264 | .debug_macinfo 0 : { *(.debug_macinfo) } 265 | /* SGI/MIPS DWARF 2 extensions */ 266 | .debug_weaknames 0 : { *(.debug_weaknames) } 267 | .debug_funcnames 0 : { *(.debug_funcnames) } 268 | .debug_typenames 0 : { *(.debug_typenames) } 269 | .debug_varnames 0 : { *(.debug_varnames) } 270 | /* DWARF 3 */ 271 | .debug_pubtypes 0 : { *(.debug_pubtypes) } 272 | .debug_ranges 0 : { *(.debug_ranges) } 273 | /* DWARF Extension. */ 274 | .debug_macro 0 : { *(.debug_macro) } 275 | .debug_addr 0 : { *(.debug_addr) } 276 | .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } 277 | .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) } 278 | .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) } 279 | .mdebug.abi32 0 : { KEEP(*(.mdebug.abi32)) } 280 | .mdebug.abiN32 0 : { KEEP(*(.mdebug.abiN32)) } 281 | .mdebug.abi64 0 : { KEEP(*(.mdebug.abi64)) } 282 | .mdebug.abiO64 0 : { KEEP(*(.mdebug.abiO64)) } 283 | .mdebug.eabi32 0 : { KEEP(*(.mdebug.eabi32)) } 284 | .mdebug.eabi64 0 : { KEEP(*(.mdebug.eabi64)) } 285 | .gcc_compiled_long32 0 : { KEEP(*(.gcc_compiled_long32)) } 286 | .gcc_compiled_long64 0 : { KEEP(*(.gcc_compiled_long64)) } 287 | /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } 288 | } 289 | -------------------------------------------------------------------------------- /manifest.txt: -------------------------------------------------------------------------------- 1 | bin=fromage.bin 2 | cue=fromage.cue 3 | lic=isolicence.dat 4 | 5 | # / 6 | dat=./system.cnf 7 | dat=./boot.exe 8 | dat=./music.hdr 9 | dat=./sounds.lz4 10 | dat=./atlas.lz4 11 | raw=./music.xa 12 | -------------------------------------------------------------------------------- /res/LICENSE: -------------------------------------------------------------------------------- 1 | == fromage resources - texture atlas == 2 | 3 | Pixel Perfection by XSSheep is licensed under a Creative Commons Attribution-Share Alike 4.0 International License. Additional modifications copyright (c) 2019 asie. 4 | 5 | http://creativecommons.org/licenses/by-sa/4.0/ 6 | 7 | == fromage resources - music == 8 | 9 | Fromage OST - Copyright (c) 2019 GreaseMonkey 10 | 11 | == fromage resources - sounds == 12 | 13 | The following sounds come from freesound: 14 | 15 | grass1 by Snoman ( https://freesound.org/people/Snoman/sounds/9904/ ) 16 | grass2 by Snoman ( https://freesound.org/people/Snoman/sounds/9905/ ) 17 | grass3 by Snoman ( https://freesound.org/people/Snoman/sounds/9906/ ) 18 | grass4 by Snoman ( https://freesound.org/people/Snoman/sounds/9907/ ) 19 | 20 | Creative Commons Attribution 3.0 Unported (CC BY 3.0) 21 | 22 | http://creativecommons.org/licenses/by/3.0/ 23 | 24 | Remaining sounds come from the Minetest project (https://www.minetest.net/) 25 | 26 | Copyright (c) 2010-2016 Mito551 27 | 28 | Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) 29 | 30 | http://creativecommons.org/licenses/by-sa/3.0/ 31 | 32 | == fromage resources - font == 33 | 34 | "k6x8" font from Little Limit 35 | 36 | http://littlelimit.net/k6x8.htm 37 | 38 | These fonts are free software. Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially. THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY. 39 | 40 | == fromage resources - icon == 41 | 42 | Copyright (c) 2019 asie 43 | -------------------------------------------------------------------------------- /res/atlas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/atlas.png -------------------------------------------------------------------------------- /res/calm1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/calm1.ogg -------------------------------------------------------------------------------- /res/calm2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/calm2.ogg -------------------------------------------------------------------------------- /res/calm3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/calm3.ogg -------------------------------------------------------------------------------- /res/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/font.png -------------------------------------------------------------------------------- /res/grass1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/grass1.wav -------------------------------------------------------------------------------- /res/grass2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/grass2.wav -------------------------------------------------------------------------------- /res/grass3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/grass3.wav -------------------------------------------------------------------------------- /res/grass4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/grass4.wav -------------------------------------------------------------------------------- /res/gravel1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/gravel1.ogg -------------------------------------------------------------------------------- /res/gravel2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/gravel2.ogg -------------------------------------------------------------------------------- /res/gravel3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/gravel3.ogg -------------------------------------------------------------------------------- /res/gravel4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/gravel4.ogg -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/icon.png -------------------------------------------------------------------------------- /res/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/icon.xcf -------------------------------------------------------------------------------- /res/music.txt: -------------------------------------------------------------------------------- 1 | 1 xacd obj/calm1.mus 1 0 2 | 1 xacd obj/calm2.mus 1 1 3 | 1 xacd obj/calm3.mus 1 2 4 | 1 null 5 | -------------------------------------------------------------------------------- /res/music_volume.txt: -------------------------------------------------------------------------------- 1 | 0x16D0 2 | -------------------------------------------------------------------------------- /res/stone1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/stone1.ogg -------------------------------------------------------------------------------- /res/stone2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/stone2.ogg -------------------------------------------------------------------------------- /res/stone3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/stone3.ogg -------------------------------------------------------------------------------- /res/stone4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/stone4.ogg -------------------------------------------------------------------------------- /res/wood1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/wood1.ogg -------------------------------------------------------------------------------- /res/wood2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/wood2.ogg -------------------------------------------------------------------------------- /res/wood3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/wood3.ogg -------------------------------------------------------------------------------- /res/wood4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenThread/fromage/6ecc7a9c0e695c9241fdc366255e0d268a5a07ed/res/wood4.ogg -------------------------------------------------------------------------------- /src/block_info.h: -------------------------------------------------------------------------------- 1 | #define QUAD(x,y) {(((y)+8)<<12)|((x)<<4), 0x001C, 0x4034 | ( (((y)+8) * 16 + (x)) << 6), (((y) << 4) + (x))} 2 | 3 | #define INFO_BLOCK_CUBE(x,y) {QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(x,y)} 4 | #define INFO_BLOCK_CUBE_TB(x,y,xt,yt,xb,yb) {QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(xb,yb),QUAD(xt,yt)} 5 | #define INFO_BLOCK_PLANT(x,y) {QUAD(x,y),QUAD(x,y),QUAD(x,y),QUAD(x,y)} 6 | #define INFO_BLOCK_WOOL(i) INFO_BLOCK_CUBE(i,4) 7 | 8 | uint8_t block_sel_slots[] = { 9 | 1, 4, 45, 3, 5, 17, 18, 20, 44, 10 | 48, 6, 37, 38, 39, 40, 12, 13, 19, 11 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 12 | 30, 31, 32, 33, 34, 35, 36, 14, 15, 13 | 16, 42, 41, 47, 46, 49, 0, 8, 10 14 | }; 15 | 16 | block_info_t block_info[BLOCK_MAX][QUAD_MAX] = { 17 | {}, // 0: Air 18 | INFO_BLOCK_CUBE(1, 0), // 1: Stone 19 | INFO_BLOCK_CUBE_TB(3, 0, 0, 0, 2, 0), // 2: Grass 20 | INFO_BLOCK_CUBE(2, 0), // 3: Dirt 21 | INFO_BLOCK_CUBE(0, 1), // 4: Cobblestone 22 | INFO_BLOCK_CUBE(4, 0), // 5: Wood Planks 23 | INFO_BLOCK_PLANT(15, 0), // 6: Sapling 24 | INFO_BLOCK_CUBE(1, 1), // 7: Bedrock 25 | INFO_BLOCK_CUBE(14, 0), // 8: Water (Flowing) 26 | INFO_BLOCK_CUBE(14, 0), // 9: Water (Still) 27 | INFO_BLOCK_CUBE(14, 1), // 10: Lava (Flowing) 28 | INFO_BLOCK_CUBE(14, 1), // 11: Lava (Still) 29 | INFO_BLOCK_CUBE(2, 1), // 12: Sand 30 | INFO_BLOCK_CUBE(3, 1), // 13: Gravel 31 | INFO_BLOCK_CUBE(0, 2), // 14: Gold Ore 32 | INFO_BLOCK_CUBE(1, 2), // 15: Iron Ore 33 | INFO_BLOCK_CUBE(2, 2), // 16: Coal Ore 34 | INFO_BLOCK_CUBE_TB(4, 1, 5, 1, 5, 1), // 17: Wood Log 35 | INFO_BLOCK_CUBE(6, 1), // 18: Leaves 36 | INFO_BLOCK_CUBE(0, 3), // 19: Sponge 37 | INFO_BLOCK_CUBE(1, 3), // 20: Glass 38 | 39 | INFO_BLOCK_WOOL(0), 40 | INFO_BLOCK_WOOL(1), 41 | INFO_BLOCK_WOOL(2), 42 | INFO_BLOCK_WOOL(3), 43 | INFO_BLOCK_WOOL(4), 44 | INFO_BLOCK_WOOL(5), 45 | INFO_BLOCK_WOOL(6), 46 | INFO_BLOCK_WOOL(7), 47 | INFO_BLOCK_WOOL(8), 48 | INFO_BLOCK_WOOL(9), 49 | INFO_BLOCK_WOOL(10), 50 | INFO_BLOCK_WOOL(11), 51 | INFO_BLOCK_WOOL(12), 52 | INFO_BLOCK_WOOL(13), 53 | INFO_BLOCK_WOOL(14), 54 | INFO_BLOCK_WOOL(15), 55 | 56 | INFO_BLOCK_PLANT(13, 0), // 37: Dandelion 57 | INFO_BLOCK_PLANT(12, 0), // 38: Rose 58 | INFO_BLOCK_PLANT(13, 1), // 39: Brown Mushroom 59 | INFO_BLOCK_PLANT(12, 1), // 40: Red Mushroom 60 | 61 | INFO_BLOCK_CUBE_TB(8, 2, 8, 1, 8, 3), // 41: Block of Gold 62 | INFO_BLOCK_CUBE_TB(7, 2, 7, 1, 7, 3), // 42: Block of Iron 63 | 64 | INFO_BLOCK_CUBE_TB(5, 0, 6, 0, 6, 0), // 43: Double Slab 65 | INFO_BLOCK_CUBE_TB(5, 0, 6, 0, 6, 0), // 44: Slab 66 | 67 | INFO_BLOCK_CUBE(7, 0), // 45: Brick 68 | INFO_BLOCK_CUBE_TB(8, 0, 9, 0, 10, 0), // 46: TNT 69 | INFO_BLOCK_CUBE_TB(3, 2, 4, 0, 4, 0), // 47: Bookshelf 70 | INFO_BLOCK_CUBE(4, 2), // 48: Mossy Cobblestone 71 | INFO_BLOCK_CUBE(5, 2), // 49: Obsidian 72 | }; 73 | -------------------------------------------------------------------------------- /src/cdrom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "common.h" 6 | 7 | void cdrom_isr(void) { 8 | seedy_isr_cdrom(); 9 | } 10 | 11 | #define FILE_RECORD_MAX 16 12 | 13 | static file_record_t files[FILE_RECORD_MAX]; 14 | static int files_count = 0; 15 | 16 | static uint32_t rand_seed = 1; 17 | static uint16_t song_volume = 0x3FFF; 18 | static uint16_t song_lengths[4]; 19 | static int32_t song_count = 0; 20 | static int32_t song_vblanks = 0; 21 | static int32_t song_stop_req = 0; 22 | 23 | int cdrom_has_songs(void) { 24 | return song_count > 0; 25 | } 26 | 27 | void cdrom_tick_vblank(void) { 28 | if (song_vblanks > 0) { 29 | song_vblanks--; 30 | if (song_vblanks <= 0) { 31 | song_stop_req = 1; 32 | } 33 | } 34 | } 35 | 36 | void cdrom_tick_song_player(int vbls, int music_on) { 37 | if (song_count <= 0) return; 38 | 39 | if (song_stop_req > 0 || !music_on) { 40 | orelei_close_cd_audio(); 41 | seedy_stop_xa(); 42 | song_stop_req = 0; 43 | song_vblanks = 0; 44 | } 45 | 46 | if (song_vblanks <= 0 && music_on) { 47 | int it_time = 0; 48 | for (int i = 0; i < vbls; i++) { 49 | if ((RAND(rand_seed) & 0x3FF) == 0x121) it_time++; 50 | } 51 | 52 | if (it_time > 0) { 53 | file_record_t *record = cdrom_get_file("MUSIC.XA"); 54 | if (record != NULL) { 55 | int val = ((rand_seed >> 16) % song_count); 56 | orelei_open_cd_audio(song_volume, song_volume); 57 | seedy_read_xa(record->lba, 58 | SEEDY_PLAY_XA_37800 59 | | SEEDY_PLAY_XA_STEREO 60 | | SEEDY_READ_SINGLE_SPEED, 61 | 1, val 62 | ); 63 | song_vblanks = (song_lengths[val] + 3) * VBLANKS_PER_SEC; 64 | } 65 | } 66 | } else { 67 | } 68 | } 69 | 70 | file_record_t *cdrom_get_file(const char *name) { 71 | char fname[16]; 72 | snprintf(fname, 16, "%s;1", name); 73 | 74 | for (int i = 0; i < files_count; i++) { 75 | if (strcmp(fname, files[i].filename) == 0) { 76 | return &files[i]; 77 | } 78 | } 79 | 80 | return NULL; 81 | } 82 | 83 | int cdrom_read_record(file_record_t *record, uint8_t *buffer) { 84 | return seedy_read_data_sync(record->lba, 0, buffer, record->size); 85 | } 86 | 87 | #define CDROM_INIT_STEPS 5 88 | 89 | void cdrom_init(save_progress_callback *pc) { 90 | // this is NOT how you write an ISO9660 parser 91 | // EVER 92 | 93 | uint8_t *buffer; 94 | 95 | buffer = malloc(2048); 96 | if (pc != NULL) pc(0, CDROM_INIT_STEPS); 97 | 98 | seedy_init_cdrom(); 99 | if (pc != NULL) pc(1, CDROM_INIT_STEPS); 100 | 101 | // read pvd 102 | seedy_read_data_sync(16, 0, buffer, 2048); 103 | if (pc != NULL) pc(2, CDROM_INIT_STEPS); 104 | 105 | // parse pvd 106 | int record_lba = READ32LE(buffer, 0x9C + 0x02); 107 | int record_size = READ32LE(buffer, 0x9C + 0x0A); 108 | 109 | // read root directory records 110 | buffer = realloc(buffer, record_size); 111 | seedy_read_data_sync(record_lba, 0, buffer, record_size); 112 | if (pc != NULL) pc(3, CDROM_INIT_STEPS); 113 | 114 | // parse root directory records 115 | files_count = 0; 116 | uint8_t *ptr = buffer; 117 | 118 | int j = 0; 119 | while (*ptr >= 33 && files_count < FILE_RECORD_MAX) { 120 | int i = files_count; 121 | int fnsize = *(ptr + 0x20); 122 | if (fnsize > 15) fnsize = 15; 123 | 124 | files[i].lba = READ32LE(ptr, 0x02); 125 | files[i].size = READ32LE(ptr, 0x0A); 126 | files[i].flags = *(ptr + 0x19); 127 | 128 | memcpy(files[i].filename, ptr + 0x21, fnsize); 129 | files[i].filename[fnsize] = 0; 130 | 131 | ptr += (*ptr); 132 | files_count++; 133 | } 134 | if (pc != NULL) pc(4, CDROM_INIT_STEPS); 135 | 136 | { 137 | file_record_t *record = cdrom_get_file("MUSIC.HDR"); 138 | if (record != NULL) { 139 | song_count = (record->size - 1) >> 1; 140 | seedy_read_data_sync(record->lba, 0, buffer, record->size); 141 | song_volume = READ16LE(buffer, 0); 142 | for (int i = 0; i < song_count; i++) { 143 | song_lengths[i] = READ16LE(buffer, (i << 1) + 2); 144 | } 145 | } 146 | } 147 | 148 | if (pc != NULL) pc(5, CDROM_INIT_STEPS); 149 | free(buffer); 150 | } 151 | 152 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define ASSERT(x) if(!(x)) { for(;;) {} } 11 | #define ABS(x) ((x)>=0 ? (x) : -(x)) 12 | #define READ16LE(secbuf,i) ((secbuf)[(i)] | ((secbuf)[(i)+1]<<8)) 13 | #define READ32LE(secbuf,i) ((secbuf)[(i)] | ((secbuf)[(i)+1]<<8) | ((secbuf)[(i)+2]<<16) | ((secbuf)[(i)+3]<<24)) 14 | 15 | #define FASTMEM __attribute__((section(".fastmem"))) 16 | 17 | #include "config.h" 18 | 19 | typedef int32_t fixed; 20 | typedef int64_t fixed64; 21 | typedef fixed vec3[3]; 22 | typedef fixed vec4[4]; 23 | typedef vec4 mat4[4]; 24 | 25 | #define FM_PI ((fixed)0x00008000) 26 | #define FACE_ZN 0 27 | #define FACE_ZP 1 28 | #define FACE_XN 2 29 | #define FACE_XP 3 30 | #define FACE_YN 4 31 | #define FACE_YP 5 32 | 33 | #define RAND(seed) ((seed) = ((seed) * 16843009) + 826366247) 34 | typedef void save_progress_callback(int progress, int max); 35 | 36 | typedef struct { 37 | bool pro_jumps; 38 | bool move_dpad; 39 | uint8_t render_distance; 40 | uint8_t show_fps; 41 | uint8_t debug_mode; 42 | uint8_t sound_on; 43 | uint8_t music_on; 44 | uint8_t fog_on; 45 | uint8_t fov_mode; 46 | } options_t; 47 | 48 | // Files 49 | extern uint8_t font_raw[]; 50 | extern uint8_t fsys_level[LEVEL_LY][LEVEL_LZ][LEVEL_LX]; 51 | 52 | // block_info.h 53 | #define BLOCK_MAX 50 54 | #define QUAD_MAX 6 55 | typedef struct block_info { 56 | uint16_t tc, tp, cl, idx; 57 | } block_info_t; 58 | 59 | extern block_info_t block_info[BLOCK_MAX][QUAD_MAX]; 60 | 61 | // cdrom.c 62 | typedef struct { 63 | int32_t lba, size; 64 | uint8_t flags; 65 | char filename[16]; 66 | } file_record_t; 67 | 68 | void cdrom_tick_vblank(void); 69 | void cdrom_tick_song_player(int vblanks, int music_on); 70 | file_record_t *cdrom_get_file(const char *name); 71 | int cdrom_read_record(file_record_t *record, uint8_t *buffer); 72 | void cdrom_isr(void); 73 | int cdrom_has_songs(void); 74 | void cdrom_init(save_progress_callback *pc); 75 | 76 | // main.c 77 | extern int32_t cam_x; 78 | extern int32_t cam_y; 79 | extern int32_t cam_z; 80 | 81 | extern int32_t mat_rt11, mat_rt12, mat_rt13; 82 | extern int32_t mat_rt21, mat_rt22, mat_rt23; 83 | extern int32_t mat_rt31, mat_rt32, mat_rt33; 84 | 85 | #define OT_WORLD 2 86 | 87 | extern int hotbar_pos; 88 | #define HOTBAR_MAX 9 89 | extern int current_block[HOTBAR_MAX]; 90 | 91 | void draw_block(int32_t cx, int32_t cy, int32_t cz, int di, int block, uint32_t facemask, bool transparent); 92 | 93 | // joy.c 94 | extern int32_t joy_pressed; 95 | void joy_update(int ticks, int autorepeat_divisor); 96 | 97 | // gpu.c 98 | extern volatile uint32_t vblank_counter; 99 | 100 | void frame_start(void); 101 | void frame_flip(void); 102 | void frame_flip_nosync(void); 103 | void wait_for_next_vblank(void); 104 | void wait_for_vblanks(uint32_t count); 105 | void gp0_command(uint32_t v); 106 | void gp0_data(uint32_t v); 107 | void gp0_data_xy(uint32_t x, uint32_t y); 108 | void gp1_command(uint32_t v); 109 | 110 | // gpu_dma.c 111 | extern uint32_t dma_pos; 112 | #define DMA_BUFFER_COUNT 4 113 | #define DMA_ORDER_MAX 128 114 | #define DMA_BUFFER_SIZE (256*384) 115 | 116 | extern uint32_t dma_buffer[DMA_BUFFER_SIZE]; 117 | extern uint32_t dma_order_table[DMA_BUFFER_COUNT][DMA_ORDER_MAX]; 118 | extern uint32_t dma_buffer_current; 119 | #define DMA_PUSH(len, ot) \ 120 | if(dma_pos >= ((sizeof(dma_buffer)-32)/sizeof(int32_t))) { \ 121 | dma_pos = 0; \ 122 | } \ 123 | dma_buffer[dma_pos] = \ 124 | (dma_order_table[dma_buffer_current][ot] & 0x00FFFFFF) \ 125 | | ((len)<<24); \ 126 | dma_order_table[dma_buffer_current][ot] = ((uint32_t)&dma_buffer[dma_pos])&0x00FFFFFF; \ 127 | dma_pos++; \ 128 | 129 | void gpu_dma_init(void); 130 | int gpu_dma_finish(void); 131 | 132 | void gpu_dma_load(uint32_t *buffer, int x, int y, int width, int height, int use_lz4); 133 | 134 | // gui.c 135 | #define FONT_CHARS 128 136 | int get_text_width(const char *format, ...); 137 | void draw_text(int x, int y, int color, const char *format, ...); 138 | void draw_status_progress(int progress, int max); 139 | void draw_block_icon(int bx, int by, int bw, int bh, int bid); 140 | void draw_block_background(block_info_t *bi); 141 | void draw_dirt_background(void); 142 | void draw_status_window(int style, const char *format, ...); 143 | void draw_block_sel_menu(int pos, uint8_t *slots, int slotcount); 144 | void draw_current_block(void); 145 | void draw_hotbar(void); 146 | void draw_crosshair(void); 147 | void draw_liquid_overlay(void); 148 | int gui_menu(int optcount, int optstartpos, ...); 149 | void gui_terrible_text_viewer(const char* text); 150 | 151 | // options.c 152 | int gui_options_menu(options_t *options); 153 | int gui_worldgen_menu(void); 154 | 155 | // save.c 156 | #define SAVE_ERROR_COMPRESSION -1 157 | #define SAVE_ERROR_OUT_OF_SPACE -2 158 | #define SAVE_ERROR_NOT_FOUND -3 159 | #define SAVE_ERROR_INVALID_ARGUMENTS -4 160 | #define SAVE_ERROR_CARD -5 161 | #define SAVE_ERROR_CARD_FATAL -6 162 | #define SAVE_ERROR_CORRUPT_DATA -7 163 | #define SAVE_ERROR_MAP_TOO_LARGE -8 164 | #define SAVE_ERROR_OUT_OF_MEMORY -9 165 | #define SAVE_ERROR_UNSUPPORTED_DATA -10 166 | 167 | typedef struct { 168 | int16_t xsize, ysize, zsize; 169 | int32_t cam_x, cam_y, cam_z, cam_rx, cam_ry; 170 | uint8_t hotbar_blocks[HOTBAR_MAX]; 171 | uint8_t hotbar_pos; 172 | options_t options; 173 | } level_info; 174 | 175 | const char *save_get_error_string(int value); 176 | int load_level(int save_id, level_info *info, uint8_t *target, int32_t target_size, save_progress_callback *pc); 177 | int save_level(int save_id, level_info *info, const uint8_t *data, save_progress_callback *pc); 178 | 179 | // sound.c 180 | void sound_init(void); 181 | void sound_play(int id, int vol_left, int vol_right); 182 | int sound_get_id(int32_t block_id); 183 | 184 | // util.c 185 | uint8_t *lz4_alloc_and_unpack(uint8_t *buf, int cmpsize, int size); 186 | 187 | // world.c 188 | uint8_t world_get_top_opaque(int32_t cx, int32_t cz); 189 | uint32_t world_get_vis_blocks_unsafe(int32_t cx, int32_t cy, int32_t cz); 190 | int32_t world_get_block_unsafe(int32_t cx, int32_t cy, int32_t cz); 191 | int32_t world_get_render_faces_unsafe(int32_t cx, int32_t cy, int32_t cz); 192 | int32_t world_get_block(int32_t cx, int32_t cy, int32_t cz); 193 | void world_set_block(int32_t cx, int32_t cy, int32_t cz, uint8_t b, uint8_t flags); 194 | int32_t world_cast_ray(int32_t px, int32_t py, int32_t pz, int32_t vx, int32_t vy, int32_t vz, int32_t *ocx, int32_t *ocy, int32_t *ocz, int32_t max_steps, bool use_block_before_hit); 195 | uint32_t world_is_translucent(int32_t b); 196 | uint32_t world_is_walkable(int32_t b); 197 | int32_t world_is_colliding(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2); 198 | int32_t world_is_colliding_fixed(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2); 199 | void world_schedule_block_update(int32_t cx, int32_t cy, int32_t cz, uint32_t delay); 200 | void world_init(); 201 | void world_update(uint32_t ticks, uint32_t *vblank_counter); 202 | 203 | // worldgen.c 204 | typedef void worldgen_stage_callback(const char *message); 205 | void world_generate(int mode, uint8_t *map, int32_t lx, int32_t ly, int32_t lz, uint32_t seed, worldgen_stage_callback *wc, save_progress_callback *pc); 206 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #define SAVING_ENABLED 2 | 3 | #if defined(REGION_JAPAN) 4 | #define REGION_SAVE_FILENAME "BICHEN-00001_%d" 5 | #elif defined(REGION_USA) 6 | #define REGION_SAVE_FILENAME "BACHEN-00001_%d" 7 | #elif defined(REGION_EUROPE) 8 | #define TV_PAL 9 | #define REGION_SAVE_FILENAME "BECHEN-00001_%d" 10 | #else 11 | #error No defined region! 12 | #endif 13 | 14 | #ifdef VID_USE_640 15 | #define VID_WIDTH 640 16 | #define VID_WIDTH_MULTIPLIER 2 17 | #define font_raw font2x_raw 18 | #else 19 | #define VID_WIDTH 320 20 | #define VID_WIDTH_MULTIPLIER 1 21 | #define font_raw font1x_raw 22 | #endif 23 | #define VID_HEIGHT 240 24 | #define VID_HEIGHT_MULTIPLIER 1 25 | 26 | #ifdef TV_PAL 27 | #define VBLANKS_PER_SEC 50 28 | #define TEXT_BORDER_X (3*VID_WIDTH_MULTIPLIER) 29 | #define TEXT_BORDER_Y 3 30 | #else 31 | #define VBLANKS_PER_SEC 60 32 | #define TEXT_BORDER_X (3*VID_WIDTH_MULTIPLIER) 33 | #define TEXT_BORDER_Y 9 34 | #endif 35 | 36 | #define VBLANKS_PER_CARD_WRITE 2 37 | 38 | #define LAVA_ANIMATION_START (14<<4) 39 | #define LAVA_ANIMATION_FRAMES 16 40 | #define WATER_ANIMATION_START (13<<4) 41 | #define WATER_ANIMATION_FRAMES 16 42 | 43 | #define LEVEL_LX 96 44 | #define LEVEL_LY 64 45 | #define LEVEL_LZ 96 46 | -------------------------------------------------------------------------------- /src/gpu.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | volatile uint32_t vblank_counter = 0; 4 | 5 | volatile uint32_t frame_x = 0; 6 | volatile uint32_t frame_y = 0; 7 | volatile uint32_t vis_frame_x = 0; 8 | volatile uint32_t vis_frame_y = 0; 9 | 10 | void frame_start(void) 11 | { 12 | DMA_PUSH(1, DMA_ORDER_MAX-1); 13 | dma_buffer[dma_pos++] = 0xE3000000 | ((frame_x+0)<<0) | ((frame_y+0)<<10); // XY1 draw range 14 | DMA_PUSH(1, DMA_ORDER_MAX-1); 15 | dma_buffer[dma_pos++] = 0xE4000000 | ((frame_x+VID_WIDTH-1)<<0) | ((frame_y+VID_HEIGHT-1)<<10); // XY2 draw range 16 | DMA_PUSH(1, DMA_ORDER_MAX-1); 17 | dma_buffer[dma_pos++] = 0xE5000000 | ((frame_x+VID_WIDTH/2)<<0) | ((frame_y+VID_HEIGHT/2)<<11); // Draw offset 18 | } 19 | 20 | void frame_flip_nosync(void) 21 | { 22 | vis_frame_x = frame_x; 23 | vis_frame_y = frame_y; 24 | frame_y = 256 - vis_frame_y; 25 | while((PSXREG_Dn_CHCR(2) & (1<<24)) != 0) {} 26 | 27 | gp1_command(0x05000000 | ((vis_frame_x)<<0) | ((vis_frame_y)<<10)); // Display start (x,y) 28 | } 29 | 30 | void frame_flip(void) 31 | { 32 | vis_frame_x = frame_x; 33 | vis_frame_y = frame_y; 34 | frame_y = 256 - vis_frame_y; 35 | 36 | while((PSXREG_Dn_CHCR(2) & (1<<24)) != 0) {} 37 | wait_for_next_vblank(); 38 | gp1_command(0x05000000 | ((vis_frame_x)<<0) | ((vis_frame_y)<<10)); // Display start (x,y) 39 | } 40 | 41 | void wait_for_next_vblank(void) 42 | { 43 | uint32_t last_counter = vblank_counter; 44 | 45 | while(last_counter == vblank_counter) { 46 | // do nothing 47 | } 48 | } 49 | 50 | void wait_for_vblanks(uint32_t count) 51 | { 52 | uint32_t last_counter = vblank_counter + count; 53 | 54 | while(last_counter > vblank_counter) { 55 | // do nothing 56 | } 57 | } 58 | 59 | void gp0_command(uint32_t v) 60 | { 61 | //while((PSXREG_GPU_GP1 & (1<<26)) == 0) {} 62 | for(int i = 0; i < 1000; i++) { 63 | if((PSXREG_GPU_GP1 & (1<<26)) != 0) { 64 | break; 65 | } 66 | } 67 | PSXREG_GPU_GP0 = v; 68 | } 69 | 70 | void gp0_data(uint32_t v) 71 | { 72 | //while((PSXREG_GPU_GP1 & (1<<28)) == 0) {} 73 | for(int i = 0; i < 1000; i++) { 74 | if((PSXREG_GPU_GP1 & (1<<28)) != 0) { 75 | break; 76 | } 77 | } 78 | PSXREG_GPU_GP0 = v; 79 | } 80 | 81 | void gp0_data_xy(uint32_t x, uint32_t y) 82 | { 83 | gp0_data((x&0xFFFF) | (y<<16)); 84 | } 85 | 86 | void gp1_command(uint32_t v) 87 | { 88 | //while ((PSXREG_GPU_GP1 & (1<<26)) == 0) {} 89 | for(int i = 0; i < 10000; i++) { 90 | if((PSXREG_GPU_GP1 & (1<<28)) != 0) { 91 | break; 92 | } 93 | } 94 | PSXREG_GPU_GP1 = v; 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/gpu_dma.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | FASTMEM uint32_t dma_pos = 0; 4 | uint32_t dma_pos_start = 0; 5 | uint32_t dma_start_ptr; 6 | 7 | volatile uint32_t dma_next_start = 0; 8 | uint32_t dma_buffer[DMA_BUFFER_SIZE]; 9 | uint32_t dma_order_table[DMA_BUFFER_COUNT][DMA_ORDER_MAX]; 10 | uint32_t dma_buffer_current = 0; 11 | 12 | void gpu_dma_init(void) { 13 | if(dma_pos_start >= sizeof(dma_buffer)/sizeof(dma_buffer[0])/2) { 14 | dma_pos = 0; 15 | } 16 | 17 | dma_pos_start = dma_pos; 18 | dma_buffer_current += 1; 19 | dma_buffer_current &= 3; 20 | dma_start_ptr = 0x00FFFFFF&(uint32_t)&dma_order_table[dma_buffer_current][DMA_ORDER_MAX-1]; 21 | 22 | for(int i = 0; i < DMA_ORDER_MAX; i++) { 23 | dma_order_table[dma_buffer_current][i] = (i == 0 24 | ? 0x00FFFFFF 25 | : ((uint32_t)&dma_order_table[dma_buffer_current][i-1])&0xFFFFFF); 26 | } 27 | } 28 | 29 | int gpu_dma_finish(void) { 30 | DMA_PUSH(1, 0); 31 | dma_buffer[dma_pos++] = 0x00000000; 32 | 33 | while((PSXREG_Dn_CHCR(2) & (1<<24)) != 0) {} 34 | 35 | PSXREG_Dn_CHCR(2) = 0x00000001; 36 | PSXREG_DICR = 0; 37 | PSXREG_DPCR = 0x07654321; 38 | PSXREG_Dn_MADR(2) = dma_start_ptr; 39 | PSXREG_Dn_BCR(2) = 0; 40 | PSXREG_DPCR |= (0x8<<(4*2)); // Enable DMA 41 | PSXREG_Dn_CHCR(2) = 0x01000401; 42 | 43 | if (dma_pos_start > dma_pos) { 44 | return (sizeof(dma_buffer)/sizeof(int32_t)) + dma_pos - dma_pos_start; 45 | } else { 46 | return dma_pos - dma_pos_start; 47 | } 48 | } 49 | 50 | void gpu_dma_load(uint32_t *buffer, int x, int y, int width, int height, int use_lz4) 51 | { 52 | if (use_lz4 > 0) { 53 | buffer = (uint32_t *) lz4_alloc_and_unpack((uint8_t *) buffer, use_lz4, width*height*2); 54 | } 55 | 56 | #if 0 57 | // DMA-less version (FIFO) 58 | gp1_command(0x04000001); // DMA mode: FIFO (1) 59 | gp0_command(0xA0000000); 60 | gp0_data_xy(x,y); 61 | gp0_data_xy(width,height); 62 | for(int i = 0; i < width*height/2; i++) { 63 | gp0_data(buffer[i]); 64 | } 65 | gp0_command(0x01000000); 66 | gp1_command(0x04000002); // DMA mode: DMA to GPU (2) 67 | #else 68 | PSXREG_Dn_CHCR(2) = 1; 69 | PSXREG_DICR = 0; 70 | PSXREG_DPCR = 0x07654321; 71 | PSXREG_Dn_MADR(2) = ((uint32_t) buffer)&0x00FFFFFF; 72 | PSXREG_Dn_BCR(2) = ((width*height/2)<<13)|0x08; 73 | gp0_command(0xA0000000); 74 | gp0_data_xy(x,y); 75 | gp0_data_xy(width,height); 76 | PSXREG_DPCR |= (0x8<<(4*2)); // Enable DMA 77 | PSXREG_Dn_CHCR(2) = 0x01000201; 78 | while((PSXREG_Dn_CHCR(2) & (1<<24)) != 0) { 79 | // 80 | } 81 | //PSXREG_Dn_CHCR(2) = 1; 82 | //gp0_command(0x01000000); 83 | #endif 84 | if (use_lz4) { 85 | free(buffer); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/gui.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "common.h" 5 | 6 | extern uint32_t block_lighting[6]; 7 | 8 | #define CHAR_WIDTH(c) ((font_raw[(uint8_t)(c) >> 1] >> (((uint8_t)(c) & 1) << 2)) & 0x0F) 9 | 10 | static int get_text_width_buffer(const char *buffer) 11 | { 12 | int len = 0; 13 | for (size_t i = 0; i < strlen(buffer); i++) { 14 | len += (CHAR_WIDTH(buffer[i])) + 1; 15 | } 16 | return len * VID_WIDTH_MULTIPLIER; 17 | } 18 | 19 | int get_text_width(const char *format, ...) 20 | { 21 | char buffer[256]; 22 | int len; 23 | 24 | va_list args; 25 | va_start(args, format); 26 | vsnprintf(buffer, sizeof(buffer), format, args); 27 | 28 | len = get_text_width_buffer(buffer); 29 | 30 | va_end(args); 31 | return len; 32 | } 33 | 34 | static void draw_text_buffer(int x, int y, int color, const char *buffer) 35 | { 36 | x -= VID_WIDTH/2; 37 | y -= VID_HEIGHT/2; 38 | 39 | // Undo texpage 40 | DMA_PUSH(1, 1); 41 | dma_buffer[dma_pos++] = 0x00000000; 42 | DMA_PUSH(1, 1); 43 | dma_buffer[dma_pos++] = 0xE1000600; 44 | 45 | for (size_t i = 0; i < strlen(buffer); i++) { 46 | uint8_t c = buffer[i]; 47 | int width = CHAR_WIDTH(c); 48 | int widthmul = width * VID_WIDTH_MULTIPLIER; 49 | int pos = (x & 0xFFFF) + ((y & 0xFFFF) << 16); 50 | int texcoord = (((c & 15) * VID_WIDTH_MULTIPLIER) << 3) | ((c >> 4) << 11); 51 | 52 | DMA_PUSH(4, 1); 53 | dma_buffer[dma_pos++] = 0x64000000 | color; 54 | dma_buffer[dma_pos++] = pos; 55 | dma_buffer[dma_pos++] = (384 << 22) | (0x35 << 16) | texcoord; 56 | dma_buffer[dma_pos++] = (8 << 16) | (widthmul & 0xFFFF); 57 | 58 | pos += 0x10001; 59 | 60 | DMA_PUSH(4, 1); 61 | dma_buffer[dma_pos++] = 0x66000000; 62 | dma_buffer[dma_pos++] = pos; 63 | dma_buffer[dma_pos++] = (384 << 22) | (0x35 << 16) | texcoord; 64 | dma_buffer[dma_pos++] = (8 << 16) | (widthmul & 0xFFFF); 65 | 66 | x += (width + 1) * VID_WIDTH_MULTIPLIER; 67 | } 68 | 69 | // Do texpage 70 | DMA_PUSH(1, 1); 71 | dma_buffer[dma_pos++] = 0x00000000; 72 | DMA_PUSH(1, 1); 73 | dma_buffer[dma_pos++] = 0xE100061E; 74 | 75 | } 76 | 77 | void draw_status_progress(int progress, int max) 78 | { 79 | int width = VID_WIDTH / 2; 80 | 81 | DMA_PUSH(3, 1); 82 | dma_buffer[dma_pos++] = 0x6020F020; 83 | dma_buffer[dma_pos++] = (10 << 16) | (-(width/2) & 0xFFFF); 84 | dma_buffer[dma_pos++] = ((2*VID_HEIGHT_MULTIPLIER) << 16) | ((width * progress / max) & 0xFFFF); 85 | 86 | DMA_PUSH(3, 1); 87 | dma_buffer[dma_pos++] = 0x60181818; 88 | dma_buffer[dma_pos++] = (10 << 16) | (-(width/2) & 0xFFFF); 89 | dma_buffer[dma_pos++] = ((2*VID_HEIGHT_MULTIPLIER) << 16) | ((width) & 0xFFFF); 90 | } 91 | 92 | void draw_text(int x, int y, int color, const char *format, ...) 93 | { 94 | char buffer[256]; 95 | 96 | va_list args; 97 | va_start(args, format); 98 | vsnprintf(buffer, sizeof(buffer), format, args); 99 | 100 | draw_text_buffer(x, y, color, buffer); 101 | 102 | va_end(args); 103 | } 104 | 105 | static void draw_block_icon_flat(int bx, int by, int bw, int bh, block_info_t *bi1) 106 | { 107 | int bwh = bw/2; 108 | int bhh = bh/2; 109 | 110 | // Flat texture 111 | DMA_PUSH(9, 1); 112 | dma_buffer[dma_pos++] = 0x2C808080; 113 | dma_buffer[dma_pos++] = ((by-bhh) << 16) | ((bx-bwh) & 0xFFFF); 114 | dma_buffer[dma_pos++] = (bi1->cl << 16) | (bi1->tc + (0x0000)); 115 | dma_buffer[dma_pos++] = ((by-bhh) << 16) | ((bx+bwh) & 0xFFFF); 116 | dma_buffer[dma_pos++] = (bi1->tp << 16) | (bi1->tc + (0x000F)); 117 | dma_buffer[dma_pos++] = ((by+bhh) << 16) | ((bx-bwh) & 0xFFFF); 118 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (0x0F00)); 119 | dma_buffer[dma_pos++] = ((by+bhh) << 16) | ((bx+bwh) & 0xFFFF); 120 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (0x0F0F)); 121 | } 122 | 123 | void draw_block_icon(int bx, int by, int bw, int bh, int block_id) 124 | { 125 | block_info_t *bi0 = &block_info[block_id][5]; 126 | block_info_t *bi1 = &block_info[block_id][2]; 127 | 128 | if (block_id == 6 || (block_id >= 37 && block_id <= 40)) { 129 | draw_block_icon_flat(bx, by, bw, bh, bi1); 130 | } else if (block_id > 0 && block_id < BLOCK_MAX) { 131 | int bh8 = 8 * bh / 32; 132 | int bw14 = 14 * bw / 32; 133 | int bh16 = 16 * bh / 32; 134 | 135 | // Isometric cube 136 | // Positions: 137 | // 138 | // left, centre, right 139 | // up, down 140 | // cu 141 | // lu ru 142 | // cc 143 | // ld rd 144 | // cd 145 | uint32_t lu = ((by-bh8) << 16) | ((bx-bw14) & 0xFFFF); 146 | uint32_t ld = ((by+bh8) << 16) | ((bx-bw14) & 0xFFFF); 147 | uint32_t cu = ((by-bh16) << 16) | ((bx) & 0xFFFF); 148 | uint32_t cc = ((by+0) << 16) | ((bx) & 0xFFFF); 149 | uint32_t cd = ((by+bh16) << 16) | ((bx) & 0xFFFF); 150 | uint32_t ru = ((by-bh8) << 16) | ((bx+bw14) & 0xFFFF); 151 | uint32_t rd = ((by+bh8) << 16) | ((bx+bw14) & 0xFFFF); 152 | uint16_t tc0 = 0x0000; 153 | uint16_t tc1 = 0x000F; 154 | uint16_t tc2 = 0x0F00; 155 | uint16_t tc3 = 0x0F0F; 156 | uint32_t cmd = ((block_id&(~1)) == 8) ? 0x2E000000 : 0x2C000000; 157 | 158 | if (block_id == 44) { 159 | lu += bh8<<16; 160 | cu += bh8<<16; 161 | ru += bh8<<16; 162 | cc += bh8<<16; 163 | tc0 |= 0x0008; 164 | tc2 |= 0x0008; 165 | } 166 | DMA_PUSH(9, 1); 167 | dma_buffer[dma_pos++] = cmd | block_lighting[5]; 168 | dma_buffer[dma_pos++] = lu; 169 | dma_buffer[dma_pos++] = (bi0->cl << 16) | (bi0->tc + (0x0000)); 170 | dma_buffer[dma_pos++] = cu; 171 | dma_buffer[dma_pos++] = (bi0->tp << 16) | (bi0->tc + (0x000F)); 172 | dma_buffer[dma_pos++] = cc; 173 | dma_buffer[dma_pos++] = (0 << 16) | (bi0->tc + (0x0F00)); 174 | dma_buffer[dma_pos++] = ru; 175 | dma_buffer[dma_pos++] = (0 << 16) | (bi0->tc + (0x0F0F)); 176 | 177 | DMA_PUSH(9, 1); 178 | dma_buffer[dma_pos++] = cmd | block_lighting[0]; 179 | dma_buffer[dma_pos++] = lu; 180 | dma_buffer[dma_pos++] = (bi1->cl << 16) | (bi1->tc + (tc0)); 181 | dma_buffer[dma_pos++] = cc; 182 | dma_buffer[dma_pos++] = (bi1->tp << 16) | (bi1->tc + (tc1)); 183 | dma_buffer[dma_pos++] = ld; 184 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (tc2)); 185 | dma_buffer[dma_pos++] = cd; 186 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (tc3)); 187 | 188 | DMA_PUSH(9, 1); 189 | dma_buffer[dma_pos++] = cmd | block_lighting[2]; 190 | dma_buffer[dma_pos++] = cc; 191 | dma_buffer[dma_pos++] = (bi1->cl << 16) | (bi1->tc + (tc0)); 192 | dma_buffer[dma_pos++] = ru; 193 | dma_buffer[dma_pos++] = (bi1->tp << 16) | (bi1->tc + (tc1)); 194 | dma_buffer[dma_pos++] = cd; 195 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (tc2)); 196 | dma_buffer[dma_pos++] = rd; 197 | dma_buffer[dma_pos++] = (0 << 16) | (bi1->tc + (tc3)); 198 | } 199 | } 200 | 201 | void draw_block_background(block_info_t *bi) 202 | { 203 | DMA_PUSH(3, 1); 204 | dma_buffer[dma_pos++] = 0x62000000; 205 | dma_buffer[dma_pos++] = (-(VID_HEIGHT/2) << 16) | (-(VID_WIDTH/2) & 0xFFFF); 206 | dma_buffer[dma_pos++] = ((VID_HEIGHT) << 16) | ((VID_WIDTH) & 0xFFFF); 207 | DMA_PUSH(3, 1); 208 | dma_buffer[dma_pos++] = 0x62000000; 209 | dma_buffer[dma_pos++] = (-(VID_HEIGHT/2) << 16) | (-(VID_WIDTH/2) & 0xFFFF); 210 | dma_buffer[dma_pos++] = ((VID_HEIGHT) << 16) | ((VID_WIDTH) & 0xFFFF); 211 | 212 | for (int y = 0; y < VID_HEIGHT; y += 16 * VID_HEIGHT_MULTIPLIER) { 213 | for (int x = 0; x < VID_WIDTH; x += 16 * VID_WIDTH_MULTIPLIER) { 214 | draw_block_icon_flat( 215 | x - (VID_WIDTH/2) + 8 * VID_WIDTH_MULTIPLIER, 216 | y - (VID_HEIGHT/2) + 8, 217 | 16 * VID_WIDTH_MULTIPLIER, 218 | 16 * VID_HEIGHT_MULTIPLIER, 219 | bi); 220 | } 221 | } 222 | } 223 | 224 | void draw_dirt_background(void) 225 | { 226 | draw_block_background(&block_info[3][5]); 227 | } 228 | 229 | void draw_status_window(int style, const char *format, ...) 230 | { 231 | char buffer[256]; 232 | 233 | va_list args; 234 | va_start(args, format); 235 | vsnprintf(buffer, sizeof(buffer), format, args); 236 | 237 | // Draw text 238 | int width = get_text_width_buffer(buffer); 239 | draw_text_buffer((VID_WIDTH - width) / 2, (VID_HEIGHT - 8) / 2, 0xFFFFFF, buffer); 240 | 241 | switch (style) { 242 | case 0: 243 | draw_dirt_background(); 244 | break; 245 | case 1: 246 | DMA_PUSH(3, 1); 247 | dma_buffer[dma_pos++] = 0x600A0A0A; 248 | dma_buffer[dma_pos++] = (-(VID_HEIGHT/2) << 16) | (-(VID_WIDTH/2) & 0xFFFF); 249 | dma_buffer[dma_pos++] = ((VID_HEIGHT) << 16) | ((VID_WIDTH) & 0xFFFF); 250 | break; 251 | } 252 | 253 | va_end(args); 254 | } 255 | 256 | void draw_block_sel_menu(int pos, uint8_t *slots, int slotcount) 257 | { 258 | // 34x34 slots 259 | int bg_w = ((25 * 9) + 18 + 4) * VID_WIDTH_MULTIPLIER; 260 | int bg_h = ((25 * ((slotcount+8)/9))) + 18 + 15; 261 | 262 | int y = 0; 263 | int x = 0; 264 | 265 | // blocks 266 | for (int id = 0; id < slotcount; id++) { 267 | int x_center = (-bg_w/2) + 2 + 9; 268 | int y_center = (-bg_h/2) + 12 + 9; 269 | int bid = slots[id]; 270 | 271 | x_center += (25 * VID_WIDTH_MULTIPLIER * x) + 16 * VID_WIDTH_MULTIPLIER; 272 | y_center += (25 * y) + 11; 273 | 274 | if (id == pos) { 275 | draw_block_icon(x_center, y_center, 32 * VID_WIDTH_MULTIPLIER, 32, bid); 276 | 277 | // background 278 | DMA_PUSH(3, 1); 279 | dma_buffer[dma_pos++] = 0x62FFFFFF; 280 | dma_buffer[dma_pos++] = ( 281 | (y_center - 16) << 16) | 282 | ((x_center - 16 * VID_WIDTH_MULTIPLIER) & 0xFFFF); 283 | dma_buffer[dma_pos++] = ((32) << 16) | ((32 * VID_WIDTH_MULTIPLIER) & 0xFFFF); 284 | } else { 285 | draw_block_icon(x_center, y_center, 16 * VID_WIDTH_MULTIPLIER, 16, bid); 286 | } 287 | 288 | if ((++x) == 9) { y++; x = 0; } 289 | } 290 | 291 | // text 292 | int text_w = get_text_width("Select block"); 293 | draw_text((VID_WIDTH - text_w) / 2, ((VID_HEIGHT - bg_h) / 2) + 4, 0xFFFFFF, "Select block"); 294 | 295 | // background 296 | DMA_PUSH(3, 1); 297 | dma_buffer[dma_pos++] = 0x62080808; 298 | dma_buffer[dma_pos++] = ((-bg_h/2) << 16) | ((-bg_w/2) & 0xFFFF); 299 | dma_buffer[dma_pos++] = ((bg_h) << 16) | ((bg_w) & 0xFFFF); 300 | } 301 | 302 | void draw_current_block(void) 303 | { 304 | int32_t cam_cx = cam_x >> 8; 305 | int32_t cam_cy = cam_y >> 8; 306 | int32_t cam_cz = cam_z >> 8; 307 | 308 | if (current_block[hotbar_pos] > 0) 309 | { 310 | int32_t sel_cx = -1; 311 | int32_t sel_cy = -1; 312 | int32_t sel_cz = -1; 313 | int32_t sel_valid = world_cast_ray( 314 | cam_x, cam_y, cam_z, 315 | mat_rt31, mat_rt32, mat_rt33, 316 | &sel_cx, &sel_cy, &sel_cz, 317 | 10, true); 318 | 319 | if(sel_valid >= 0) { 320 | int32_t fmask = 0x00; 321 | fmask |= (cam_cx < sel_cx ? 0x04 : cam_cx > sel_cx ? 0x08 : 0); 322 | fmask |= (cam_cy < sel_cy ? 0x10 : cam_cy > sel_cy ? 0x20 : 0); 323 | fmask |= (cam_cz < sel_cz ? 0x01 : cam_cz > sel_cz ? 0x02 : 0); 324 | int di = ABS(sel_cx - cam_cx) + ABS(sel_cy - cam_cy) + ABS(sel_cz - cam_cz); 325 | draw_block(sel_cx, sel_cy, sel_cz, di, current_block[hotbar_pos], fmask, true); 326 | } 327 | } 328 | } 329 | 330 | void draw_hotbar(void) 331 | { 332 | const int bw = 16 * VID_WIDTH_MULTIPLIER; 333 | const int bh = 16; 334 | const int by = 120 - 6 - TEXT_BORDER_Y; 335 | 336 | { 337 | int bx = -((bw*(HOTBAR_MAX-1))/2) + (hotbar_pos*bw); 338 | DMA_PUSH(3, 1); 339 | dma_buffer[dma_pos++] = 0x60C0C0C0; 340 | dma_buffer[dma_pos++] = ((by-(bh/2)-1) << 16) | ((bx-(bw/2)-1) & 0xFFFF); 341 | dma_buffer[dma_pos++] = ((2) << 16) | ((bw-2+(2*VID_WIDTH_MULTIPLIER)) & 0xFFFF); 342 | DMA_PUSH(3, 1); 343 | dma_buffer[dma_pos++] = 0x60C0C0C0; 344 | dma_buffer[dma_pos++] = ((by+(bh/2)-1) << 16) | ((bx-(bw/2)-1) & 0xFFFF); 345 | dma_buffer[dma_pos++] = ((2) << 16) | ((bw-2+(2*VID_WIDTH_MULTIPLIER)) & 0xFFFF); 346 | DMA_PUSH(3, 1); 347 | dma_buffer[dma_pos++] = 0x60C0C0C0; 348 | dma_buffer[dma_pos++] = ((by-(bh/2)-1) << 16) | ((bx-(bw/2)-2) & 0xFFFF); 349 | dma_buffer[dma_pos++] = ((bh+2) << 16) | ((2*VID_WIDTH_MULTIPLIER) & 0xFFFF); 350 | DMA_PUSH(3, 1); 351 | dma_buffer[dma_pos++] = 0x60C0C0C0; 352 | dma_buffer[dma_pos++] = ((by-(bh/2)-1) << 16) | ((bx+(bw/2)-2) & 0xFFFF); 353 | dma_buffer[dma_pos++] = ((bh+2) << 16) | ((2*VID_WIDTH_MULTIPLIER) & 0xFFFF); 354 | } 355 | 356 | for (int i = 0; i < HOTBAR_MAX; i++) { 357 | int bx = -((bw*(HOTBAR_MAX-1))/2) + (i*bw); 358 | draw_block_icon(bx, by, bw, bh, current_block[i]); 359 | } 360 | 361 | DMA_PUSH(3, 1); 362 | dma_buffer[dma_pos++] = 0x62000000; 363 | dma_buffer[dma_pos++] = ((by-bh/2) << 16) | ((0 - (bw*HOTBAR_MAX)/2) & 0xFFFF); 364 | dma_buffer[dma_pos++] = (bh << 16) | ((bw*HOTBAR_MAX) & 0xFFFF); 365 | } 366 | 367 | void draw_crosshair(void) 368 | { 369 | DMA_PUSH(3, 1); 370 | dma_buffer[dma_pos++] = 0x60FFFFFF; 371 | dma_buffer[dma_pos++] = (((-3) & 0xFFFF) << 16) | ((0) & 0xFFFF); 372 | dma_buffer[dma_pos++] = (7 << 16) | (1 * VID_WIDTH_MULTIPLIER); 373 | DMA_PUSH(3, 1); 374 | dma_buffer[dma_pos++] = 0x60FFFFFF; 375 | dma_buffer[dma_pos++] = (((0) & 0xFFFF) << 16) | ((-3 * VID_WIDTH_MULTIPLIER) & 0xFFFF); 376 | dma_buffer[dma_pos++] = (1 << 16) | (7 * VID_WIDTH_MULTIPLIER); 377 | } 378 | 379 | void draw_liquid_overlay(void) 380 | { 381 | int32_t cam_cx = cam_x >> 8; 382 | int32_t cam_cy = cam_y >> 8; 383 | int32_t cam_cz = cam_z >> 8; 384 | 385 | int32_t cam_cb = world_get_block(cam_cx, cam_cy, cam_cz); 386 | 387 | if ((cam_cb & (~1)) == 8) { 388 | for (int i = 0; i < 2; i++) { 389 | DMA_PUSH(3, 1); 390 | dma_buffer[dma_pos++] = 0x628D6F2E; 391 | dma_buffer[dma_pos++] = (((-(VID_HEIGHT/2))&0xFFFF) << 16) | ((-(VID_WIDTH/2))&0xFFFF); 392 | dma_buffer[dma_pos++] = (VID_HEIGHT << 16) | (VID_WIDTH << 0); 393 | } 394 | } else if ((cam_cb & (~1)) == 10) { 395 | for (int i = 0; i < 3; i++) { 396 | DMA_PUSH(3, 1); 397 | dma_buffer[dma_pos++] = 0x621460EE; 398 | dma_buffer[dma_pos++] = (((-(VID_HEIGHT/2))&0xFFFF) << 16) | ((-(VID_WIDTH/2))&0xFFFF); 399 | dma_buffer[dma_pos++] = (VID_HEIGHT << 16) | (VID_WIDTH << 0); 400 | } 401 | } 402 | } 403 | 404 | int gui_menu(int optcount, int optstartpos, ...) 405 | { 406 | char *strs[optcount]; 407 | int curr_opt = optstartpos % optcount; 408 | 409 | va_list args; 410 | va_start(args, optstartpos); 411 | for (int i = 0; i < optcount; i++) 412 | strs[i] = va_arg(args, char*); 413 | va_end(args); 414 | 415 | while (1) { 416 | gpu_dma_init(); 417 | frame_start(); 418 | 419 | int opt_height = 20; 420 | int opt_distance = opt_height + 8; 421 | if (optcount >= 9) { 422 | opt_height -= 2; 423 | opt_distance -= 4; 424 | } 425 | int opt_width = VID_WIDTH * 3 / 4; 426 | int opt_text_y = (opt_height - 8) / 2; 427 | int opt_allheight = (opt_distance * (optcount - 1)) + opt_height; 428 | int opt_x = (VID_WIDTH - opt_width) / 2; 429 | int opt_y = (VID_HEIGHT - opt_allheight) / 2; 430 | 431 | for (int i = 0; i < optcount; i++) 432 | { 433 | if (strs[i] == NULL) continue; 434 | 435 | int tw = get_text_width_buffer(strs[i]); 436 | int ty = opt_y + (opt_distance * i); 437 | draw_text((VID_WIDTH - tw) / 2, ty + opt_text_y, 0xFFFFFF, strs[i]); 438 | 439 | DMA_PUSH(3, 1); 440 | dma_buffer[dma_pos++] = i == curr_opt ? 0x62C0C0C0 : 0x62000000; 441 | dma_buffer[dma_pos++] = ((ty - (VID_HEIGHT/2)) << 16) | ((opt_x - (VID_WIDTH/2)) & 0xFFFF); 442 | dma_buffer[dma_pos++] = ((opt_height) << 16) | ((opt_width) & 0xFFFF); 443 | } 444 | 445 | draw_dirt_background(); 446 | gpu_dma_finish(); 447 | frame_flip(); 448 | joy_update(1, 1); 449 | 450 | if ((joy_pressed & PAD_DOWN) != 0) { 451 | curr_opt = (curr_opt + 1) % optcount; 452 | while (strs[curr_opt] == NULL) 453 | curr_opt = (curr_opt + 1) % optcount; 454 | } 455 | if ((joy_pressed & PAD_UP) != 0) { 456 | curr_opt = (curr_opt - 1); 457 | if (curr_opt < 0) curr_opt = optcount - 1; 458 | while (strs[curr_opt] == NULL) { 459 | curr_opt = (curr_opt - 1); 460 | if (curr_opt < 0) curr_opt = optcount - 1; 461 | } 462 | } 463 | if ((joy_pressed & (PAD_START | PAD_T | PAD_O)) != 0) { curr_opt = -1; break; } 464 | if ((joy_pressed & PAD_X) != 0) break; 465 | } 466 | 467 | return curr_opt; 468 | } 469 | 470 | void gui_terrible_text_viewer(const char* text) 471 | { 472 | // Leave this alone. It can be slow and terrible. 473 | // It's for fulfilling licensing obligations and nothing else. 474 | // Shoo! 475 | int text_pos = 0; 476 | int last_tp = -1; 477 | int line_height = 9; 478 | int border_width = VID_WIDTH * 7 / 8; 479 | int border_height = VID_HEIGHT * 7 / 8; 480 | int window_width = border_width - 8*VID_WIDTH_MULTIPLIER; 481 | int window_height = (border_height - 8); 482 | window_height = window_height - (window_height % line_height); 483 | int window_x = ((VID_WIDTH - window_width) / 2); 484 | int window_y = ((VID_HEIGHT - window_height) / 2); 485 | int border_x = ((-border_width) / 2); 486 | int border_y = ((-border_height) / 2); 487 | char buffer[512]; 488 | 489 | window_height -= 16; 490 | 491 | while (1) { 492 | if (last_tp != text_pos) { 493 | const char *tpos = text; 494 | int newlines = 0; 495 | while (newlines < text_pos && (*tpos)!=0) { 496 | if (*(tpos++) == 10) newlines++; 497 | } 498 | 499 | if ((*tpos) == 0) { 500 | text_pos = newlines - 1; 501 | continue; 502 | } 503 | 504 | gpu_dma_init(); 505 | frame_start(); 506 | 507 | draw_text_buffer(window_x, window_y + window_height + 8, 0x404040, "[press o to exit]"); 508 | 509 | int iy = 0; 510 | int ix = 0; 511 | int ib = 0; 512 | while (iy < window_height) { 513 | switch (*tpos) { 514 | case 13: continue; 515 | case 10: 516 | case 32: { 517 | buffer[ib] = 0; 518 | int w = get_text_width_buffer(buffer); 519 | if (w > window_width) { 520 | char *last_word = strrchr(buffer, ' '); 521 | last_word[0] = '\0'; 522 | draw_text_buffer(window_x + ix, window_y + iy, 0xFFFFFF, buffer); 523 | last_word[0] = ' '; 524 | int lwl = strlen(last_word + 1); 525 | memmove(buffer, last_word + 1, lwl); 526 | ib = lwl; 527 | buffer[ib++] = 32; 528 | iy += line_height; 529 | } else if ((*tpos) == 10) { 530 | draw_text_buffer(window_x + ix, window_y + iy, 0xFFFFFF, buffer); 531 | ib = 0; 532 | iy += line_height; 533 | } else { 534 | buffer[ib++] = 32; 535 | } 536 | } break; 537 | case 0: 538 | iy = window_height; 539 | break; 540 | default: 541 | buffer[ib++] = *tpos; 542 | break; 543 | } 544 | tpos++; 545 | } 546 | 547 | 548 | DMA_PUSH(3, 1); 549 | dma_buffer[dma_pos++] = 0x62000000; 550 | dma_buffer[dma_pos++] = (border_y << 16) | (border_x & 0xFFFF); 551 | dma_buffer[dma_pos++] = (border_height << 16) | (border_width & 0xFFFF); 552 | 553 | draw_dirt_background(); 554 | gpu_dma_finish(); 555 | frame_flip(); 556 | 557 | last_tp = text_pos; 558 | } else { 559 | wait_for_next_vblank(); 560 | } 561 | 562 | joy_update(1, 4); 563 | 564 | if ((joy_pressed & PAD_DOWN) != 0) text_pos++; 565 | if ((joy_pressed & PAD_UP) != 0) if (text_pos > 0) text_pos--; 566 | if ((joy_pressed & (PAD_T | PAD_O)) != 0) break; 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /src/joy.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | // approx. 1/4 a second 5 | #ifdef TV_PAL 6 | #define MAX_PRESS_TIME 12 7 | #else 8 | #define MAX_PRESS_TIME 15 9 | #endif 10 | 11 | int32_t joy_pressed; 12 | static uint8_t press_time[32]; 13 | static int jbp = 0; 14 | 15 | void joy_update(int ticks, int autorepeat_divisor) 16 | { 17 | int max_time = (MAX_PRESS_TIME / autorepeat_divisor); 18 | sawpads_do_read(); 19 | joy_pressed = 0; 20 | 21 | for (int i = 0; i < 32; i++) { 22 | int joy_mask = 1 << (i & 15); 23 | int mask = 1 << i; 24 | int pressed = (sawpads_controller[i >> 4].buttons & joy_mask) == 0; 25 | if (pressed) { 26 | press_time[i] = (press_time[i] + ticks); 27 | } else { 28 | press_time[i] = 0; 29 | } 30 | 31 | 32 | if (press_time[i] > max_time) { 33 | joy_pressed |= mask; 34 | while (press_time[i] > max_time) { 35 | press_time[i] -= max_time; 36 | } 37 | } else if (press_time[i] == ticks) joy_pressed |= mask; 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/meshes.h: -------------------------------------------------------------------------------- 1 | #define FACE_N 0x0000 2 | #define FACE_0 0x0080 3 | #define FACE_P 0x0100 4 | 5 | const mesh_data_t mesh_data_slab[] = { 6 | // -Z 7 | {FACE_N, FACE_N, FACE_N, 0x0F00, .face = 0,}, 8 | {FACE_P, FACE_N, FACE_N, 0x0F0F, .face = 0,}, 9 | {FACE_N, FACE_0, FACE_N, 0x0800, .face = 0,}, 10 | {FACE_P, FACE_0, FACE_N, 0x080F, .face = 0,}, 11 | 12 | // +Z 13 | {FACE_N, FACE_N, FACE_P, 0x0F0F, .face = 1,}, 14 | {FACE_N, FACE_0, FACE_P, 0x080F, .face = 1,}, 15 | {FACE_P, FACE_N, FACE_P, 0x0F00, .face = 1,}, 16 | {FACE_P, FACE_0, FACE_P, 0x0800, .face = 1,}, 17 | 18 | // -X 19 | {FACE_N, FACE_N, FACE_N, 0x0F0F, .face = 2,}, 20 | {FACE_N, FACE_0, FACE_N, 0x080F, .face = 2,}, 21 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 2,}, 22 | {FACE_N, FACE_0, FACE_P, 0x0800, .face = 2,}, 23 | 24 | // +X 25 | {FACE_P, FACE_N, FACE_N, 0x0F00, .face = 3,}, 26 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 3,}, 27 | {FACE_P, FACE_0, FACE_N, 0x0800, .face = 3,}, 28 | {FACE_P, FACE_0, FACE_P, 0x080F, .face = 3,}, 29 | 30 | // -Y 31 | {FACE_N, FACE_N, FACE_N, 0x0000, .face = 4,}, 32 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 4,}, 33 | {FACE_P, FACE_N, FACE_N, 0x000F, .face = 4,}, 34 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 4,}, 35 | 36 | // +Y 37 | {FACE_N, FACE_0, FACE_N, 0x0000, .face = 5,}, 38 | {FACE_P, FACE_0, FACE_N, 0x000F, .face = 5,}, 39 | {FACE_N, FACE_0, FACE_P, 0x0F00, .face = 5,}, 40 | {FACE_P, FACE_0, FACE_P, 0x0F0F, .face = 5,}, 41 | }; 42 | 43 | const mesh_data_t mesh_data_block_defaults[] = { 44 | // -Z 45 | {FACE_N, FACE_N, FACE_N, 0x0F00, .face = 0,}, 46 | {FACE_P, FACE_N, FACE_N, 0x0F0F, .face = 0,}, 47 | {FACE_N, FACE_P, FACE_N, 0x0000, .face = 0,}, 48 | {FACE_P, FACE_P, FACE_N, 0x000F, .face = 0,}, 49 | 50 | // +Z 51 | {FACE_N, FACE_N, FACE_P, 0x0F0F, .face = 1,}, 52 | {FACE_N, FACE_P, FACE_P, 0x000F, .face = 1,}, 53 | {FACE_P, FACE_N, FACE_P, 0x0F00, .face = 1,}, 54 | {FACE_P, FACE_P, FACE_P, 0x0000, .face = 1,}, 55 | 56 | // -X 57 | {FACE_N, FACE_N, FACE_N, 0x0F0F, .face = 2,}, 58 | {FACE_N, FACE_P, FACE_N, 0x000F, .face = 2,}, 59 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 2,}, 60 | {FACE_N, FACE_P, FACE_P, 0x0000, .face = 2,}, 61 | 62 | // +X 63 | {FACE_P, FACE_N, FACE_N, 0x0F00, .face = 3,}, 64 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 3,}, 65 | {FACE_P, FACE_P, FACE_N, 0x0000, .face = 3,}, 66 | {FACE_P, FACE_P, FACE_P, 0x000F, .face = 3,}, 67 | 68 | // -Y 69 | {FACE_N, FACE_N, FACE_N, 0x0000, .face = 4,}, 70 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 4,}, 71 | {FACE_P, FACE_N, FACE_N, 0x000F, .face = 4,}, 72 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 4,}, 73 | 74 | // +Y 75 | {FACE_N, FACE_P, FACE_N, 0x0000, .face = 5,}, 76 | {FACE_P, FACE_P, FACE_N, 0x000F, .face = 5,}, 77 | {FACE_N, FACE_P, FACE_P, 0x0F00, .face = 5,}, 78 | {FACE_P, FACE_P, FACE_P, 0x0F0F, .face = 5,}, 79 | }; 80 | 81 | const mesh_data_t mesh_data_plant[] = { 82 | // TODO 83 | // X-Z=0 front 84 | {FACE_N, FACE_N, FACE_N, 0x0F00, .face = 8,}, 85 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 8,}, 86 | {FACE_N, FACE_P, FACE_N, 0x0000, .face = 8,}, 87 | {FACE_P, FACE_P, FACE_P, 0x000F, .face = 8,}, 88 | 89 | // X-Z=0 back 90 | {FACE_P, FACE_P, FACE_P, 0x000F, .face = 8,}, 91 | {FACE_P, FACE_N, FACE_P, 0x0F0F, .face = 8,}, 92 | {FACE_N, FACE_P, FACE_N, 0x0000, .face = 8,}, 93 | {FACE_N, FACE_N, FACE_N, 0x0F00, .face = 8,}, 94 | 95 | // X+Z=0 front 96 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 8,}, 97 | {FACE_P, FACE_N, FACE_N, 0x0F0F, .face = 8,}, 98 | {FACE_N, FACE_P, FACE_P, 0x0000, .face = 8,}, 99 | {FACE_P, FACE_P, FACE_N, 0x000F, .face = 8,}, 100 | 101 | // X+Z=0 back 102 | {FACE_P, FACE_P, FACE_N, 0x000F, .face = 8,}, 103 | {FACE_P, FACE_N, FACE_N, 0x0F0F, .face = 8,}, 104 | {FACE_N, FACE_P, FACE_P, 0x0000, .face = 8,}, 105 | {FACE_N, FACE_N, FACE_P, 0x0F00, .face = 8,}, 106 | }; 107 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | static const char *opt_renderdist_txt[] = {"Render distance: Short", "Render distance: Normal", "Render distance: Far", "Render distance: Extreme"}; 4 | static const char *opt_genmodes_txt[] = {"Generator: Default", "Generator: Flat"}; 5 | static const char *opt_fovmodes_txt[] = {"FOV: Narrow", "FOV: Normal", "FOV: Wide"}; 6 | 7 | int gui_options_menu(options_t *options) { 8 | int last_option = 0; 9 | while (1) { 10 | #ifdef STANDALONE_EXE 11 | last_option = gui_menu( 12 | 6, last_option, 13 | options->pro_jumps ? "Movement: Quake Pro" : "Movement: Classic", 14 | options->move_dpad ? "Controls: D-Pad" : "Controls: Left Analog", 15 | opt_renderdist_txt[options->render_distance % 4], 16 | options->show_fps ? "Show FPS: On" : "Show FPS: Off", 17 | // options->fog_on ? "Fog: On" : "Fog: Off", 18 | // opt_fovmodes_txt[options->fov_mode % 3], 19 | NULL, 20 | "Done" 21 | ); 22 | #else 23 | last_option = gui_menu( 24 | 8, last_option, 25 | options->pro_jumps ? "Movement: Quake Pro" : "Movement: Classic", 26 | options->move_dpad ? "Controls: D-Pad" : "Controls: Left Analog", 27 | opt_renderdist_txt[options->render_distance % 4], 28 | options->show_fps ? "Show FPS: On" : "Show FPS: Off", 29 | // options->fog_on ? "Fog: On" : "Fog: Off", 30 | // opt_fovmodes_txt[options->fov_mode % 3], 31 | options->sound_on ? "Sound: On" : "Sound: Off", 32 | options->music_on ? "Music: On" : "Music: Off", 33 | NULL, 34 | "Done" 35 | ); 36 | #endif 37 | switch (last_option) { 38 | case 0: 39 | options->pro_jumps = !options->pro_jumps; 40 | break; 41 | case 1: 42 | options->move_dpad = !options->move_dpad; 43 | break; 44 | case 2: 45 | options->render_distance = (options->render_distance + 1) % 4; 46 | break; 47 | case 3: 48 | options->show_fps = !options->show_fps; 49 | break; 50 | // case 4: 51 | // options->fog_on = !options->fog_on; 52 | // break; 53 | // case 4: 54 | // options->fov_mode = (options->fov_mode + 1) % 3; 55 | break; 56 | #ifdef STANDALONE_EXE 57 | case 5: 58 | return 0; 59 | #else 60 | case 4: 61 | options->sound_on = !options->sound_on; 62 | break; 63 | case 5: 64 | options->music_on = !options->music_on; 65 | break; 66 | case 7: 67 | return 0; 68 | #endif 69 | default: 70 | return -1; 71 | } 72 | } 73 | } 74 | 75 | int gui_worldgen_menu(void) { 76 | int last_option = 0; 77 | int wgen_mode = 0; 78 | while (1) { 79 | last_option = gui_menu( 80 | 4, last_option, 81 | opt_genmodes_txt[wgen_mode], 82 | NULL, 83 | "Generate", 84 | "Return" 85 | ); 86 | switch (last_option) { 87 | case 0: 88 | wgen_mode = (wgen_mode + 1) % 2; 89 | break; 90 | case 2: 91 | return wgen_mode; 92 | default: 93 | return -1; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/save.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | #include 5 | #include "lz4.h" 6 | 7 | #define VERSION_MAJOR 1 8 | #define VERSION_MINOR 0 9 | #define HEADER_SIZE sizeof(level_header_t) 10 | 11 | extern uint8_t icon_raw[]; 12 | 13 | static int card_initialized = 0; 14 | static uint32_t card_sector_map[20]; 15 | 16 | static void init_card(save_progress_callback *pc) 17 | { 18 | uint8_t secbuf[128]; 19 | 20 | if (card_initialized == 0) 21 | { 22 | card_initialized = 1; 23 | } 24 | 25 | for (uint32_t i = 0; i < 20; i++) { 26 | wait_for_vblanks(VBLANKS_PER_CARD_WRITE); 27 | if (sawpads_read_card_sector(0, i, secbuf) >= 4) { 28 | card_sector_map[i] = ((uint32_t*) secbuf)[0]; 29 | } 30 | } 31 | } 32 | 33 | void write_sjis_name(uint8_t *buffer, const char *format, ...) 34 | { 35 | char text[32]; 36 | 37 | va_list args; 38 | va_start(args, format); 39 | vsnprintf(text, sizeof(text), format, args); 40 | va_end(args); 41 | 42 | int pos = 0; 43 | for (uint32_t i = 0; i < strlen(text); i++) { 44 | uint8_t c = text[i]; 45 | if (c >= '0' && c <= '9') { buffer[pos++] = 0x82; buffer[pos++] = 0x4F + c - '0'; } 46 | else if (c >= 'A' && c <= 'Z') { buffer[pos++] = 0x82; buffer[pos++] = 0x60 + c - 'A'; } 47 | else if (c >= 'a' && c <= 'z') { buffer[pos++] = 0x82; buffer[pos++] = 0x81 + c - 'a'; } 48 | else if (c == '(') { buffer[pos++] = 0x81; buffer[pos++] = 0x69; } 49 | else if (c == ')') { buffer[pos++] = 0x81; buffer[pos++] = 0x6A; } 50 | else /* space */ { buffer[pos++] = 0x81; buffer[pos++] = 0x40; } 51 | } 52 | } 53 | 54 | #define READ16(i) (secbuf[(i)] | (secbuf[(i)+1]<<8)) 55 | #define READ32(i) (secbuf[(i)] | (secbuf[(i)+1]<<8) | (secbuf[(i)+2]<<16) | (secbuf[(i)+3]<<24)) 56 | #define WRITE16(i, v) { secbuf[i] = ((v) & 0xFF); secbuf[i+1] = ((v) >> 8); } 57 | #define WRITE32(i, v) { secbuf[i] = ((v) & 0xFF); secbuf[i+1] = (((v) >> 8) & 0xFF); secbuf[i+2] = (((v) >> 16) & 0xFF); secbuf[i+3] = ((v) >> 24); } 58 | 59 | static uint8_t get_card_checksum(uint8_t *secbuf) 60 | { 61 | uint8_t checksum = 0x00; 62 | for(size_t i = 0; i < 0x7F; i++) { 63 | checksum ^= secbuf[i]; 64 | } 65 | return checksum; 66 | } 67 | 68 | static void checksum_card_frame(uint8_t *secbuf) 69 | { 70 | secbuf[0x7F] = get_card_checksum(secbuf); 71 | } 72 | 73 | static int checksum_card_match(uint8_t *secbuf) 74 | { 75 | return secbuf[0x7F] == get_card_checksum(secbuf); 76 | } 77 | 78 | const char *save_get_error_string(int value) 79 | { 80 | switch (value) { 81 | case SAVE_ERROR_COMPRESSION: 82 | return "Compression failed!"; 83 | case SAVE_ERROR_OUT_OF_SPACE: 84 | return "Out of memory card space!"; 85 | case SAVE_ERROR_NOT_FOUND: 86 | return "Save not found!"; 87 | case SAVE_ERROR_INVALID_ARGUMENTS: 88 | return "Invalid arguments!"; 89 | case SAVE_ERROR_CARD: 90 | return "Card error!"; 91 | case SAVE_ERROR_CARD_FATAL: 92 | return "Fatal card error!"; 93 | case SAVE_ERROR_CORRUPT_DATA: 94 | return "Save corrupted!"; 95 | case SAVE_ERROR_MAP_TOO_LARGE: 96 | return "Map too large!"; 97 | case SAVE_ERROR_OUT_OF_MEMORY: 98 | return "Out of memory!"; 99 | case SAVE_ERROR_UNSUPPORTED_DATA: 100 | return "Unsupported save data!"; 101 | default: 102 | return ""; 103 | } 104 | } 105 | 106 | static uint16_t sawpads_adjust_card_address(uint16_t address) { 107 | for (uint32_t i = 0; i < 20; i++) { 108 | if (card_sector_map[i] == address) { 109 | return 36 + i; 110 | } 111 | } 112 | 113 | return address; 114 | } 115 | 116 | static int32_t fromage_read_card_safe(uint16_t address, uint8_t *buffer) { 117 | wait_for_vblanks(VBLANKS_PER_CARD_WRITE); 118 | return sawpads_read_card_sector(0, sawpads_adjust_card_address(address), buffer); 119 | } 120 | 121 | static int32_t fromage_write_card_safe(uint16_t address, uint8_t *buffer) { 122 | wait_for_vblanks(VBLANKS_PER_CARD_WRITE); 123 | return sawpads_write_card_sector(0, sawpads_adjust_card_address(address), buffer); 124 | } 125 | 126 | int load_level(int save_id, level_info *info, uint8_t *target, int32_t target_size, save_progress_callback *pc) 127 | { 128 | uint8_t secbuf[128]; 129 | int start_block = -1; 130 | int start_sector = 3; 131 | int16_t block_nexts[15]; 132 | char filename[16]; 133 | uint8_t *buffer; 134 | int sectors_read = 0; 135 | 136 | if (save_id < 0) return SAVE_ERROR_INVALID_ARGUMENTS; 137 | snprintf(filename, sizeof(filename), REGION_SAVE_FILENAME, save_id); 138 | 139 | init_card(pc); 140 | 141 | // find start block and nexts 142 | for (int i = 1; i < 16; i++) { 143 | if (fromage_read_card_safe(i, secbuf) <= 0) return SAVE_ERROR_CARD; 144 | 145 | if ((secbuf[0] & 0x5C) == 0x50) { 146 | if (start_block < 0 && strcmp(secbuf + 10, filename) == 0) { 147 | start_block = i - 1; 148 | } 149 | block_nexts[i - 1] = (int16_t) (secbuf[8] | (secbuf[9] << 8)); 150 | } 151 | } 152 | 153 | if (start_block < 0) return SAVE_ERROR_NOT_FOUND; 154 | 155 | // read header and parse it 156 | if (fromage_read_card_safe((start_block + 1) * 64 + 2, secbuf) <= 0) return SAVE_ERROR_CARD; 157 | 158 | if (secbuf[0] != VERSION_MAJOR || secbuf[1] != VERSION_MINOR) return SAVE_ERROR_UNSUPPORTED_DATA; 159 | 160 | int16_t sector_count = READ16(0x08); 161 | if (sector_count < 0) return SAVE_ERROR_CORRUPT_DATA; 162 | 163 | int cmp_data_size = ((sector_count - 1) * 128) + ((secbuf[0x0A] & 0x7F) + 1); 164 | 165 | info->xsize = READ16(0x02); 166 | info->ysize = READ16(0x04); 167 | info->zsize = READ16(0x06); 168 | info->cam_x = READ32(0x10); 169 | info->cam_y = READ32(0x14); 170 | info->cam_z = READ32(0x18); 171 | info->cam_rx = READ16(0x1C); 172 | info->cam_ry = READ16(0x1E); 173 | 174 | uint8_t opt1 = secbuf[0x0B]; 175 | info->options.pro_jumps = (opt1 & 0x01) != 0; 176 | info->options.move_dpad = (opt1 & 0x02) != 0; 177 | /* if (secbuf[0x0C] < 12) { 178 | info->options.render_distance = 0; 179 | } else { 180 | info->options.render_distance = ((secbuf[0x0C] + 3 - 12) >> 2); 181 | } */ 182 | 183 | int target_data_size = info->xsize * info->ysize * info->zsize; 184 | if (target_data_size > target_size) return SAVE_ERROR_MAP_TOO_LARGE; 185 | 186 | info->hotbar_pos = secbuf[0x20] & 0x0F; 187 | for (int i = 0; i < HOTBAR_MAX; i++) 188 | info->hotbar_blocks[i] = secbuf[0x21 + i]; 189 | 190 | buffer = malloc(sector_count * 128); 191 | if (buffer == NULL) return SAVE_ERROR_OUT_OF_MEMORY; 192 | 193 | // read data 194 | while (start_block >= 0) { 195 | while (start_sector < 64) { 196 | if (pc != NULL) pc(sectors_read, sector_count); 197 | if (sectors_read >= sector_count) break; 198 | 199 | if (fromage_read_card_safe((start_block + 1) * 64 + start_sector, buffer + (sectors_read * 128)) <= 0) return SAVE_ERROR_CARD; 200 | start_sector++; sectors_read++; 201 | } 202 | 203 | if (sectors_read >= sector_count) break; 204 | start_block = block_nexts[start_block]; 205 | start_sector = 0; 206 | } 207 | 208 | // decompress data 209 | int data_size = LZ4_decompress_safe(buffer, target, cmp_data_size, target_data_size); 210 | free(buffer); 211 | if (data_size <= 0) return cmp_data_size; 212 | 213 | // that's all, folks! 214 | return 0; 215 | } 216 | 217 | int save_level(int save_id, level_info *info, const uint8_t *data, save_progress_callback *pc) 218 | { 219 | uint8_t secbuf[128]; 220 | uint8_t block_ids[15]; 221 | int16_t block_nexts[15]; 222 | uint8_t block_delete[15]; 223 | char filename[16]; 224 | 225 | if (save_id < 0 || save_id > 9) return SAVE_ERROR_INVALID_ARGUMENTS; 226 | snprintf(filename, sizeof(filename), REGION_SAVE_FILENAME, save_id); 227 | 228 | int level_size = info->xsize * info->ysize * info->zsize; 229 | int level_cmp_size = LZ4_compressBound(level_size); 230 | char *level_cmp_data = malloc(level_cmp_size); 231 | if (level_cmp_data == NULL) return SAVE_ERROR_OUT_OF_MEMORY; 232 | 233 | level_cmp_size = LZ4_compress_default(data, level_cmp_data, level_size, level_cmp_size); 234 | if (level_cmp_size <= 0) return SAVE_ERROR_COMPRESSION; 235 | 236 | level_cmp_data = realloc(level_cmp_data, level_cmp_size); 237 | if (level_cmp_data == NULL) return SAVE_ERROR_OUT_OF_MEMORY; 238 | 239 | init_card(pc); 240 | 241 | int sectors_required = ((level_cmp_size + 127) / 128) + 3 /* header + title + icon frames */; 242 | int blocks_required = (sectors_required + 63) / 64; 243 | if (blocks_required > 15) { 244 | free(level_cmp_data); 245 | return SAVE_ERROR_MAP_TOO_LARGE; 246 | } 247 | 248 | int progress_max = sectors_required + 3 + 1; 249 | if (pc != NULL) pc(0, progress_max); 250 | 251 | int blocks_found = 0; 252 | 253 | // find free blocks 254 | // also write down the list of connected sectors, as well as 255 | // any sectors to be deleted 256 | for (int i = 1; i < 16; i++) { 257 | if (fromage_read_card_safe(i, secbuf) <= 0) { 258 | free(level_cmp_data); 259 | return SAVE_ERROR_CARD; 260 | } 261 | block_delete[i - 1] = 0; 262 | 263 | if ((secbuf[0] & 0xAC) == 0xA0) { 264 | block_ids[blocks_found++] = i; 265 | block_nexts[i - 1] = -1; 266 | } else if ((secbuf[0] & 0x5C) == 0x50) { 267 | if (strcmp(secbuf + 10, filename) == 0) { 268 | block_delete[i - 1] = 1; 269 | } 270 | block_nexts[i - 1] = (int16_t) (secbuf[8] | (secbuf[9] << 8)); 271 | } 272 | } 273 | 274 | // delete blocks, if any 275 | for (int i = 0; i < 15; i++) { 276 | if (block_delete[i] == 1) { 277 | int j = i; 278 | while (j >= 0) { 279 | if (fromage_read_card_safe(j + 1, secbuf) <= 0) { 280 | free(level_cmp_data); 281 | return SAVE_ERROR_CARD_FATAL; 282 | } 283 | secbuf[0] = (secbuf[0] & 0x0F) | 0xA0; 284 | checksum_card_frame(secbuf); 285 | if (fromage_write_card_safe(j + 1, secbuf) <= 0) { 286 | free(level_cmp_data); 287 | return SAVE_ERROR_CARD_FATAL; 288 | } 289 | block_ids[blocks_found++] = j; 290 | j = block_nexts[j]; 291 | } 292 | } 293 | } 294 | 295 | if (blocks_found < blocks_required) { 296 | free(level_cmp_data); 297 | return SAVE_ERROR_OUT_OF_SPACE; 298 | } 299 | 300 | // allocate blocks 301 | memset(secbuf, 0, 128); 302 | for (int i = 0; i < blocks_required; i++) { 303 | secbuf[0] = (i == 0) ? 0x51 : ((i == (blocks_required - 1)) ? 0x53 : 0x52); 304 | if (i == (blocks_required - 1)) { 305 | secbuf[8] = secbuf[9] = 0xFF; 306 | } else { 307 | secbuf[8] = block_ids[i + 1] - 1; 308 | secbuf[9] = 0; 309 | } 310 | 311 | if (i == 0) { 312 | secbuf[5] = (blocks_required * 0x2000) >> 8; 313 | secbuf[6] = (blocks_required * 0x2000) >> 16; 314 | strcpy(secbuf + 0x0a, filename); 315 | } else { 316 | memset(secbuf + 0x04, 0, 0x04); 317 | memset(secbuf + 0x0a, 0, 0x20 - 0x0a); 318 | } 319 | 320 | checksum_card_frame(secbuf); 321 | if (fromage_write_card_safe(block_ids[i], secbuf) <= 0) { 322 | free(level_cmp_data); 323 | return i == 0 ? -3 : -5; 324 | } 325 | } 326 | if (pc != NULL) pc(1, progress_max); 327 | 328 | // write title frame 329 | memset(secbuf, 0, 128); 330 | secbuf[0] = 'S'; secbuf[1] = 'C'; 331 | secbuf[2] = 0x11; 332 | secbuf[3] = 0x01; 333 | write_sjis_name(secbuf + 4, "Fromage Level (Slot %d)", save_id); 334 | memcpy(secbuf + 0x60, icon_raw, 0x20); 335 | 336 | if (fromage_write_card_safe(block_ids[0] * 64, secbuf) <= 0) { 337 | free(level_cmp_data); 338 | return SAVE_ERROR_CARD_FATAL; 339 | } 340 | if (pc != NULL) pc(2, progress_max); 341 | 342 | { 343 | // write icon frame 344 | memcpy(secbuf, icon_raw + 0x20, 0x80); 345 | 346 | // overlay icon slot id (clut index 15 = white) 347 | int fx = ((save_id + '0') & 0xF) * 4 * VID_WIDTH_MULTIPLIER; 348 | int fy = ((save_id + '0') >> 4) * 8; 349 | 350 | for (int iy = 0; iy < 8; iy++) { 351 | for (int ix = 0; ix < 4; ix++) { 352 | #if VID_WIDTH_MULTIPLIER > 1 353 | uint8_t v = (font_raw[(FONT_CHARS/2) + ((fy+iy) * 128) + (fx+ix*2)] * 0xF) & 0xF; 354 | v |= (font_raw[(FONT_CHARS/2) + ((fy+iy) * 128) + (fx+ix*2+1)] * 0xF) & 0xF0; 355 | #else 356 | uint8_t v = font_raw[(FONT_CHARS/2) + ((fy+iy) * 64) + (fx+ix)] * 0xF; 357 | #endif 358 | secbuf[(iy + 8) * 8 + (ix)] |= v; 359 | } 360 | } 361 | } 362 | 363 | if (fromage_write_card_safe(block_ids[0] * 64 + 1, secbuf) <= 0) { 364 | free(level_cmp_data); 365 | return SAVE_ERROR_CARD_FATAL; 366 | } 367 | 368 | if (pc != NULL) pc(3, progress_max); 369 | 370 | // write fromage header frame 371 | memset(secbuf, 0, 128); 372 | secbuf[0x00] = VERSION_MAJOR; 373 | secbuf[0x01] = VERSION_MINOR; 374 | WRITE16(0x02, info->xsize); 375 | WRITE16(0x04, info->ysize); 376 | WRITE16(0x06, info->zsize); 377 | WRITE16(0x08, sectors_required - 3); 378 | secbuf[0x0A] = ((level_cmp_size + 127) & 0x7F); 379 | secbuf[0x0B] = info->options.pro_jumps ? 0x01 : 0x00; 380 | secbuf[0x0B] |= info->options.move_dpad ? 0x02 : 0x00; 381 | // secbuf[0x0C] = info->options.render_distance * 4 + 12; 382 | WRITE32(0x10, info->cam_x); 383 | WRITE32(0x14, info->cam_y); 384 | WRITE32(0x18, info->cam_z); 385 | WRITE16(0x1C, info->cam_rx); 386 | WRITE16(0x1E, info->cam_ry); 387 | secbuf[0x20] = info->hotbar_pos & 0x0F; 388 | for (int i = 0; i < HOTBAR_MAX; i++) 389 | secbuf[0x21 + i] = info->hotbar_blocks[i]; 390 | 391 | if (fromage_write_card_safe(block_ids[0] * 64 + 2, secbuf) <= 0) { 392 | free(level_cmp_data); 393 | return SAVE_ERROR_CARD_FATAL; 394 | } 395 | 396 | // write fromage data frames 397 | for (int i = 3; i < sectors_required; i++) { 398 | if (pc != NULL) pc(i + 4, progress_max); 399 | uint8_t *ptr = level_cmp_data + (128 * (i - 3)); 400 | if (fromage_write_card_safe(block_ids[i >> 6] * 64 + (i & 0x3F), ptr) <= 0) { 401 | free(level_cmp_data); 402 | return SAVE_ERROR_CARD_FATAL; 403 | } 404 | } 405 | 406 | // done! 407 | free(level_cmp_data); 408 | return blocks_required; 409 | } 410 | -------------------------------------------------------------------------------- /src/sound.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | #define SOUND_ID_COUNT 16 5 | 6 | static uint16_t ids[SOUND_ID_COUNT]; 7 | static uint8_t sounds_loaded = 0; 8 | 9 | void sound_init(void) { 10 | file_record_t *file = cdrom_get_file("SOUNDS.LZ4"); 11 | uint8_t *cmpbuf = malloc(file->size); 12 | cdrom_read_record(file, cmpbuf); 13 | 14 | memcpy(ids, cmpbuf, SOUND_ID_COUNT * sizeof(uint16_t)); 15 | int cmpbufsize = file->size - SOUND_ID_COUNT*2 - 4; 16 | int bufsize = READ32LE(cmpbuf, SOUND_ID_COUNT*2); 17 | uint8_t *buf = lz4_alloc_and_unpack(cmpbuf + SOUND_ID_COUNT*2 + 4, cmpbufsize, bufsize); 18 | 19 | orelei_init_spu(); 20 | orelei_sram_write_blocking(0x1000, buf, bufsize); 21 | 22 | free(buf); 23 | free(cmpbuf); 24 | 25 | sounds_loaded = 1; 26 | } 27 | 28 | static int snote = 0; 29 | static int32_t sound_rand = 1; 30 | 31 | void sound_play(int id, int vol_left, int vol_right) { 32 | if (!sounds_loaded) return; 33 | int base_freq = 2048; 34 | int pitch_diff = (RAND(sound_rand) & 0xFF) - 0x80; 35 | orelei_play_note(snote, 0x1000 + (ids[id]<<3), 0x9FC083FF, vol_left, vol_right, base_freq + pitch_diff); 36 | orelei_commit_key_changes(); 37 | snote = (snote + 1) & 15; 38 | } 39 | 40 | #define SOUND_GRASS 0 41 | #define SOUND_GRAVEL 1 42 | #define SOUND_STONE 2 43 | #define SOUND_WOOD 3 44 | 45 | static uint8_t sound_ids[] = { 46 | SOUND_GRASS, 47 | SOUND_STONE, SOUND_GRASS, SOUND_GRASS, SOUND_STONE, SOUND_WOOD, // 1-5 48 | SOUND_GRASS, SOUND_STONE, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, // 6-10 49 | SOUND_GRASS, SOUND_GRAVEL, SOUND_GRAVEL, SOUND_STONE, SOUND_STONE, // 11-15 50 | SOUND_STONE, SOUND_WOOD, SOUND_GRASS, SOUND_STONE, SOUND_STONE, // 16-20 51 | SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, // 21-25 52 | SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, // 26-30 53 | SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, // 31-35 54 | SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, SOUND_GRASS, // 36-40 55 | SOUND_STONE, SOUND_STONE, SOUND_STONE, SOUND_STONE, SOUND_STONE, // 41-45 56 | SOUND_STONE, SOUND_WOOD, SOUND_STONE, SOUND_STONE // 46-49 57 | }; 58 | 59 | int sound_get_id(int32_t bid) { 60 | int a = RAND(sound_rand) & 3; 61 | int b = sound_ids[bid] & 3; 62 | return (b<<2) + a; 63 | } 64 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "lz4.h" 3 | 4 | uint8_t *lz4_alloc_and_unpack(uint8_t *buf, int cmpsize, int size) { 5 | uint8_t *out = malloc(size); 6 | if (out == NULL) return NULL; 7 | int outsize = LZ4_decompress_safe(buf, out, cmpsize, size); 8 | if (outsize <= 0) { free(out); return NULL; } 9 | return out; 10 | } 11 | -------------------------------------------------------------------------------- /src/world.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #define WORLD_FALLING_CLASSIC 4 | 5 | struct update_entry { 6 | uint8_t cx, cy, cz; 7 | uint32_t time; 8 | struct update_entry* next; 9 | }; 10 | typedef struct update_entry update_entry_t; 11 | 12 | static uint8_t level_opaque_height[LEVEL_LZ][LEVEL_LX]; 13 | static uint8_t level_faces[LEVEL_LY][LEVEL_LZ][LEVEL_LX]; 14 | static uint16_t level_has_vis_blocks[LEVEL_LY>>2][LEVEL_LZ>>2][LEVEL_LX>>2]; 15 | static uint32_t level_time = 0; 16 | static uint32_t target_level_time = 0; 17 | static uint32_t tick_seed = 1; 18 | 19 | #define UPDATE_LIST_RING_SIZE 8 20 | static update_entry_t* update_list[UPDATE_LIST_RING_SIZE]; 21 | 22 | static inline void world_update_block_cache(int32_t cx, int32_t cy, int32_t cz); 23 | 24 | uint8_t world_get_top_opaque(int32_t cx, int32_t cz) 25 | { 26 | return level_opaque_height[cz][cx]; 27 | } 28 | 29 | uint32_t world_get_vis_blocks_unsafe(int32_t cx, int32_t cy, int32_t cz) 30 | { 31 | return level_has_vis_blocks[cy>>2][cz>>2][cx>>2]; 32 | } 33 | 34 | inline int32_t world_get_render_faces_unsafe(int32_t cx, int32_t cy, int32_t cz) 35 | { 36 | return level_faces[cy][cz][cx]; 37 | } 38 | 39 | inline int32_t world_get_block_unsafe(int32_t cx, int32_t cy, int32_t cz) 40 | { 41 | return fsys_level[cy][cz][cx]; 42 | } 43 | 44 | inline int32_t world_get_block(int32_t cx, int32_t cy, int32_t cz) 45 | { 46 | if(cx < 0 || cx >= LEVEL_LX) { return 0; } 47 | if(cy < 0 || cy >= LEVEL_LY) { return 0; } 48 | if(cz < 0 || cz >= LEVEL_LZ) { return 0; } 49 | return fsys_level[cy][cz][cx]; 50 | } 51 | 52 | // FLAGS: 53 | // 1 - cause block update to neighbors and self 54 | void world_set_block(int32_t cx, int32_t cy, int32_t cz, uint8_t b, uint8_t flags) 55 | { 56 | if (cx >= 0 && cy >= 0 && cz >= 0 && cx < LEVEL_LX && cy < LEVEL_LY && cz < LEVEL_LZ) { 57 | uint8_t old_b = fsys_level[cy][cz][cx]; 58 | fsys_level[cy][cz][cx] = b; 59 | world_update_block_cache(cx, cy, cz); 60 | if (cy > 0) world_update_block_cache(cx, cy-1, cz); 61 | if (cy < LEVEL_LY-1) world_update_block_cache(cx, cy+1, cz); 62 | if (cx > 0) world_update_block_cache(cx-1, cy, cz); 63 | if (cz > 0) world_update_block_cache(cx, cy, cz-1); 64 | if (cx < LEVEL_LX-1) world_update_block_cache(cx+1, cy, cz); 65 | if (cz < LEVEL_LZ-1) world_update_block_cache(cx, cy, cz+1); 66 | if ((flags & 1) != 0) { 67 | if (old_b == 19) { // sponge 68 | for (int dy = -3; dy <= 3; dy++) 69 | for (int dz = -3; dz <= 3; dz++) 70 | for (int dx = -3; dx <= 3; dx++) 71 | if ((world_get_block(cx+dx, cy+dy, cz+dz) & (~1)) == 8) { 72 | world_schedule_block_update(cx+dx, cy+dy, cz+dz, 1); 73 | } 74 | } 75 | 76 | world_schedule_block_update(cx, cy, cz, 1); 77 | world_schedule_block_update(cx-1, cy, cz, 1); 78 | world_schedule_block_update(cx+1, cy, cz, 1); 79 | world_schedule_block_update(cx, cy-1, cz, 1); 80 | world_schedule_block_update(cx, cy+1, cz, 1); 81 | world_schedule_block_update(cx, cy, cz-1, 1); 82 | world_schedule_block_update(cx, cy, cz+1, 1); 83 | } 84 | } 85 | } 86 | 87 | uint32_t world_is_raycast_hit(int32_t b); 88 | 89 | int32_t world_cast_ray(int32_t px, int32_t py, int32_t pz, int32_t vx, int32_t vy, int32_t vz, int32_t *ocx, int32_t *ocy, int32_t *ocz, int32_t max_steps, bool use_block_before_hit) 90 | { 91 | // Get cell 92 | int32_t cx = px>>8; 93 | int32_t cy = py>>8; 94 | int32_t cz = pz>>8; 95 | 96 | // Get direction 97 | int32_t gx = (vx < 0 ? -1 : 1); 98 | int32_t gy = (vy < 0 ? -1 : 1); 99 | int32_t gz = (vz < 0 ? -1 : 1); 100 | 101 | // Get absolute velocity 102 | int32_t avx = (vx < 0 ? -vx : vx); 103 | int32_t avy = (vy < 0 ? -vy : vy); 104 | int32_t avz = (vz < 0 ? -vz : vz); 105 | 106 | // Avoid div by zero 107 | if(avx == 0) { avx = 1; } 108 | if(avy == 0) { avy = 1; } 109 | if(avz == 0) { avz = 1; } 110 | 111 | // Get distance to cell boundary 112 | int32_t dx = (vx < 0 ? (px&0xFF) : 0x100-(px&0xFF)); 113 | int32_t dy = (vy < 0 ? (py&0xFF) : 0x100-(py&0xFF)); 114 | int32_t dz = (vz < 0 ? (pz&0xFF) : 0x100-(pz&0xFF)); 115 | 116 | // Get time to cell boundary 117 | int32_t tx = ((dx<<16)/avx); 118 | int32_t ty = ((dy<<16)/avy); 119 | int32_t tz = ((dz<<16)/avz); 120 | int32_t tbx = ((0x100<<16)/avx); 121 | int32_t tby = ((0x100<<16)/avy); 122 | int32_t tbz = ((0x100<<16)/avz); 123 | 124 | // Start tracing 125 | for(int32_t i = 0; i < max_steps; i++) { 126 | int32_t lcx = cx; 127 | int32_t lcy = cy; 128 | int32_t lcz = cz; 129 | int32_t face_hit = -1; 130 | 131 | // Find nearest boundary 132 | if(tx < ty && tx < tz) { 133 | // X 134 | cx += gx; 135 | ty -= tx; 136 | tz -= tx; 137 | tx = tbx; 138 | face_hit = gx < 0 ? FACE_XP : FACE_XN; 139 | } else if(ty < tz) { 140 | // Y 141 | tx -= ty; 142 | cy += gy; 143 | tz -= ty; 144 | ty = tby; 145 | face_hit = gy < 0 ? FACE_YP : FACE_YN; 146 | } else { 147 | // Z 148 | tx -= tz; 149 | ty -= tz; 150 | cz += gz; 151 | tz = tbz; 152 | face_hit = gz < 0 ? FACE_ZP : FACE_ZN; 153 | } 154 | 155 | // Check if our cell is good 156 | if(world_is_raycast_hit(world_get_block(cx, cy, cz))) { 157 | // We hit something 158 | if(use_block_before_hit) { 159 | *ocx = lcx; 160 | *ocy = lcy; 161 | *ocz = lcz; 162 | } else { 163 | *ocx = cx; 164 | *ocy = cy; 165 | *ocz = cz; 166 | } 167 | 168 | if (*ocx < 0 || *ocy < 0 || *ocz < 0 || *ocx >= LEVEL_LX || *ocy >= LEVEL_LY || *ocz >= LEVEL_LZ) 169 | return -1; 170 | 171 | // And return 172 | return face_hit; 173 | } 174 | 175 | } 176 | 177 | // We hit nothing 178 | return -1; 179 | } 180 | 181 | inline uint32_t world_is_translucent(int32_t b) { 182 | return b == 0 || b == 6 || b == 18 || b == 20 || (b >= 37 && b <= 40) || b == 44; 183 | } 184 | 185 | inline uint32_t world_is_translucent_render(int32_t b) { 186 | // TODO: We could probably handle lava a bit more economically? 187 | return world_is_translucent(b) || (b >= 8 && b <= 11); 188 | } 189 | 190 | inline uint32_t world_is_walkable(int32_t b) { 191 | return b == 0 || b == 6 || (b >= 8 && b <= 11) || (b >= 37 && b <= 40); 192 | } 193 | 194 | inline uint32_t world_is_raycast_hit(int32_t b) { 195 | return b != 0 && !(b >= 8 && b <= 11); 196 | } 197 | 198 | int32_t world_is_colliding(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2) { 199 | if (x2 < 0 || y2 < 0 || z2 < 0 || x1 >= LEVEL_LX || z1 >= LEVEL_LZ) 200 | return true; 201 | if (y1 >= LEVEL_LY) 202 | return false; 203 | 204 | for (int y = y1; y <= y2; y++) 205 | for (int z = z1; z <= z2; z++) 206 | for (int x = x1; x <= x2; x++) 207 | if (!world_is_walkable(world_get_block(x, y, z))) 208 | return true; 209 | return false; 210 | } 211 | 212 | int32_t world_is_colliding_fixed(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2) { 213 | if (x1 < 0 || y1 < 0 || z1 < 0 || x2 >= LEVEL_LX<<8 || z2 >= LEVEL_LZ<<8) 214 | return true; 215 | if (y1 >= LEVEL_LY<<8) 216 | return false; 217 | 218 | for (int y = (y1>>8); y <= (y2>>8); y++) 219 | for (int z = (z1>>8); z <= (z2>>8); z++) 220 | for (int x = (x1>>8); x <= (x2>>8); x++) 221 | { 222 | int block_id = world_get_block(x, y, z); 223 | if (block_id == 44) 224 | { 225 | if (y1 < ((y << 8) | 0x80)) 226 | return true; 227 | } 228 | else if (!world_is_walkable(block_id)) 229 | return true; 230 | } 231 | 232 | return false; 233 | } 234 | 235 | static inline uint32_t equal_render(int32_t b, int32_t nb) { 236 | if ((b >= 8) && (b <= 11)) return (b&(~1))==(nb&(~1)); 237 | else return b == nb; 238 | } 239 | 240 | static inline uint32_t should_render(int32_t b, int32_t nx, int32_t ny, int32_t nz, int32_t face) { 241 | int32_t nb = world_get_block_unsafe(nx, ny, nz); 242 | switch (b) { 243 | case 6: 244 | case 37: 245 | case 38: 246 | case 39: 247 | case 40: // plants 248 | return 1; 249 | case 8: 250 | case 9: // water 251 | case 10: 252 | case 11: // lava 253 | return (b&(~1)) != (nb&(~1)) && world_is_translucent_render(nb); 254 | case 18: // leaves 255 | case 20: // glass 256 | return b != nb && world_is_translucent_render(nb); 257 | case 44: // single slab 258 | if (face == FACE_YP) return 1; 259 | else return world_is_translucent_render(nb); 260 | default: // solid blocks 261 | switch (nb) { 262 | case 44: 263 | if (face != FACE_YP) return 1; 264 | return world_is_translucent_render(nb); 265 | default: 266 | return world_is_translucent_render(nb); 267 | } 268 | break; 269 | } 270 | } 271 | 272 | static inline uint32_t calc_fmask(int32_t cx, int32_t cy, int32_t cz, int32_t b) 273 | { 274 | if(b == 0) { 275 | return 0; 276 | } 277 | 278 | uint32_t fmask = 0; 279 | 280 | if(cz > 0 && should_render(b, cx, cy, cz-1, FACE_ZN)) fmask |= 0x01; 281 | if(cx > 0 && should_render(b, cx-1, cy, cz, FACE_XN)) fmask |= 0x04; 282 | if(cy > 0 && should_render(b, cx, cy-1, cz, FACE_YN)) fmask |= 0x10; 283 | if(cz < LEVEL_LZ-1 && should_render(b, cx, cy, cz+1, FACE_ZP)) fmask |= 0x02; 284 | if(cx < LEVEL_LX-1 && should_render(b, cx+1, cy, cz, FACE_XP)) fmask |= 0x08; 285 | if(cy >= LEVEL_LY-1 || should_render(b, cx, cy+1, cz, FACE_YP)) fmask |= 0x20; 286 | 287 | return fmask; 288 | } 289 | 290 | static inline void world_update_block_cache(int32_t cx, int32_t cy, int32_t cz) { 291 | int32_t b = world_get_block_unsafe(cx, cy, cz); 292 | 293 | // update hasblock 294 | /* if (b != 0) { 295 | level_has_block[cy][cz][cx >> 6] &= ~(1 << (cx & 0x3F)); 296 | } else { 297 | level_has_block[cy][cz][cx >> 6] |= (1 << (cx & 0x3F)); 298 | } */ 299 | 300 | // update fmask 301 | level_faces[cy][cz][cx] = calc_fmask(cx, cy, cz, b); 302 | 303 | // update heightmap 304 | uint32_t translucent = world_is_translucent(b); 305 | if (!translucent && level_opaque_height[cz][cx] < cy) { 306 | level_opaque_height[cz][cx] = cy; 307 | } else if (translucent && level_opaque_height[cz][cx] >= cy) { 308 | int py = level_opaque_height[cz][cx]; 309 | level_opaque_height[cz][cx] = 0; 310 | for (; py > 0; py--) { 311 | if (!world_is_translucent(world_get_block_unsafe(cx, py, cz))) { 312 | level_opaque_height[cz][cx] = py; 313 | break; 314 | } 315 | } 316 | } 317 | 318 | // update partial octree 319 | uint32_t visbit = 1; 320 | visbit <<= (cz&3); 321 | visbit <<= (cy&3)<<2; 322 | if(((uint32_t *)level_faces[cy][cz])[cx>>2] != 0) { 323 | level_has_vis_blocks[cy>>2][cz>>2][cx>>2] |= visbit; 324 | } else { 325 | level_has_vis_blocks[cy>>2][cz>>2][cx>>2] &= ~visbit; 326 | } 327 | } 328 | 329 | static bool is_liquid(int32_t b) { 330 | return b >= 8 && b <= 11; 331 | } 332 | 333 | static void world_liquid_try_expand(int32_t cx, int32_t cy, int32_t cz, int32_t dir, int32_t db, uint32_t delay, bool sponges) 334 | { 335 | (void)dir; 336 | if (cx < 0 || cy < 0 || cz < 0 || cx >= LEVEL_LX || cy >= LEVEL_LY || cz >= LEVEL_LZ) 337 | return; 338 | 339 | int32_t b = world_get_block_unsafe(cx, cy, cz); 340 | if (is_liquid(b)) { 341 | b &= ~1; 342 | db &= ~1; 343 | if (db == 10 && b == 8) { // Lava -> Water = Stone 344 | world_set_block(cx, cy, cz, 1, 0); 345 | } else if (db == 8 && b == 10) { // Water -> Lava = Obsidian 346 | world_set_block(cx, cy, cz, 49, 0); 347 | } 348 | } else if ((b&(~1)) != (db&(~1)) && world_is_walkable(b)) { 349 | if (sponges) { 350 | int min_x = cx - 2; 351 | int min_y = cy - 2; 352 | int min_z = cz - 2; 353 | int max_x = cx + 2; 354 | int max_y = cy + 2; 355 | int max_z = cz + 2; 356 | 357 | if (min_x < 0) min_x = 0; 358 | else if (max_x >= LEVEL_LX) max_x = LEVEL_LX - 1; 359 | 360 | if (min_y < 0) min_y = 0; 361 | else if (max_y >= LEVEL_LY) max_y = LEVEL_LY - 1; 362 | 363 | if (min_z < 0) min_z = 0; 364 | else if (max_z >= LEVEL_LZ) max_z = LEVEL_LZ - 1; 365 | 366 | for (int dy = min_y; dy <= max_y; dy++) 367 | for (int dz = min_z; dz <= max_z; dz++) 368 | for (int dx = min_x; dx <= max_x; dx++) 369 | if (world_get_block_unsafe(dx, dy, dz) == 19) 370 | return; 371 | } 372 | world_set_block(cx, cy, cz, db, 0); 373 | world_schedule_block_update(cx, cy, cz, delay); 374 | } 375 | } 376 | 377 | static void world_falling_try(int32_t cx, int32_t cy, int32_t cz, int32_t db) { 378 | #ifdef WORLD_FALLING_CLASSIC 379 | int dy = cy; 380 | while (world_is_walkable(world_get_block(cx, --dy, cz))) {} 381 | if (++dy != cy) { 382 | world_set_block(cx, cy, cz, 0, 1); 383 | world_set_block(cx, dy, cz, db, 1); 384 | } 385 | #else 386 | int32_t b = world_get_block(cx, cy-1, cz); 387 | if (world_is_walkable(b)) { 388 | world_set_block(cx, cy, cz, 0, 1); 389 | world_set_block(cx, cy-1, cz, db, 1); 390 | } 391 | #endif 392 | } 393 | 394 | static void world_block_check(int32_t cx, int32_t cy, int32_t cz) { 395 | int32_t b = world_get_block_unsafe(cx, cy, cz); 396 | switch (b) { 397 | case 2: // grass 398 | if (world_get_top_opaque(cx, cz) > cy) 399 | world_set_block(cx, cy, cz, 3, 0); 400 | break; 401 | case 3: // dirt 402 | if (world_get_top_opaque(cx, cz) <= cy) { 403 | for (int dy = cy-1; dy <= cy+1; dy++) 404 | for (int dz = cz-1; dz <= cz+1; dz++) 405 | for (int dx = cx-1; dx <= cx+1; dx++) 406 | if (world_get_block(dx, dy, dz) == 2) { 407 | // ^ also convienently checks world position validity 408 | if (world_get_top_opaque(dx, dz) <= dy) 409 | world_set_block(cx, cy, cz, 2, 0); 410 | } 411 | } 412 | break; 413 | case 6: // sapling 414 | break; 415 | case 37: 416 | case 38: 417 | if (cy == 0 || (world_get_block_unsafe(cx, cy-1, cz)&(~1)) != 2 || world_get_top_opaque(cx, cz) > cy) 418 | world_set_block(cx, cy, cz, 0, 0); 419 | break; 420 | case 39: 421 | case 40: 422 | if (cy == 0 || (world_get_block_unsafe(cx, cy-1, cz)) != 1 || world_get_top_opaque(cx, cz) <= cy) 423 | world_set_block(cx, cy, cz, 0, 0); 424 | break; 425 | } 426 | } 427 | 428 | static void world_block_update(int32_t cx, int32_t cy, int32_t cz) { 429 | int32_t b = world_get_block_unsafe(cx, cy, cz); 430 | switch (b) { 431 | case 8: // water 432 | case 9: 433 | world_liquid_try_expand(cx, cy-1, cz, 4, 8, 4, true); 434 | world_liquid_try_expand(cx-1, cy, cz, 2, 8, 4, true); 435 | world_liquid_try_expand(cx+1, cy, cz, 3, 8, 4, true); 436 | world_liquid_try_expand(cx, cy, cz-1, 0, 8, 4, true); 437 | world_liquid_try_expand(cx, cy, cz+1, 1, 8, 4, true); 438 | break; 439 | case 10: // lava 440 | case 11: 441 | world_liquid_try_expand(cx, cy-1, cz, 4, 10, 16, false); 442 | world_liquid_try_expand(cx-1, cy, cz, 2, 10, 16, false); 443 | world_liquid_try_expand(cx+1, cy, cz, 3, 10, 16, false); 444 | world_liquid_try_expand(cx, cy, cz-1, 0, 10, 16, false); 445 | world_liquid_try_expand(cx, cy, cz+1, 1, 10, 16, false); 446 | break; 447 | case 12: // sand 448 | case 13: // gravel 449 | world_falling_try(cx, cy, cz, b); 450 | break; 451 | case 19: // sponge 452 | for (int dy = -2; dy <= 2; dy++) 453 | for (int dz = -2; dz <= 2; dz++) 454 | for (int dx = -2; dx <= 2; dx++) 455 | if ((world_get_block(cx+dx, cy+dy, cz+dz) & (~1)) == 8) { 456 | world_set_block(cx+dx, cy+dy, cz+dz, 0, 1); 457 | } 458 | break; 459 | default: 460 | break; 461 | } 462 | } 463 | 464 | static inline void world_tick() { 465 | if ((level_time & 3) == 0) { 466 | // TODO: consider using LFSR 467 | for (int i = 0; i < 64; i++) 468 | { 469 | int r = RAND(tick_seed); 470 | int ry = r % LEVEL_LY; 471 | int rz = (r / LEVEL_LY) % LEVEL_LZ; 472 | int rx = ((r / LEVEL_LY) / LEVEL_LZ) % LEVEL_LX; 473 | 474 | world_block_check(rx, ry, rz); 475 | } 476 | 477 | uint32_t update_time = (level_time >> 2); 478 | int ring_pos = (update_time % UPDATE_LIST_RING_SIZE); 479 | while (ring_pos < 0) ring_pos += UPDATE_LIST_RING_SIZE; 480 | 481 | update_entry_t* prev = NULL; 482 | update_entry_t* curr = update_list[ring_pos]; 483 | while (curr != NULL) { 484 | if (update_time >= curr->time) { 485 | int32_t cx = curr->cx; 486 | int32_t cy = curr->cy; 487 | int32_t cz = curr->cz; 488 | 489 | update_entry_t* f_curr = curr; 490 | if (prev != NULL) { 491 | prev->next = curr->next; 492 | world_block_update(cx, cy, cz); 493 | curr = prev->next; 494 | } else { 495 | update_list[ring_pos] = curr->next; 496 | world_block_update(cx, cy, cz); 497 | curr = update_list[ring_pos]; 498 | } 499 | free(f_curr); 500 | } else { 501 | prev = curr; 502 | curr = curr->next; 503 | } 504 | } 505 | } 506 | } 507 | 508 | void world_init() { 509 | for (int cy = 0; cy < LEVEL_LY; cy++) 510 | for (int cz = 0; cz < LEVEL_LZ; cz++) 511 | for (int cx = 0; cx < LEVEL_LX; cx++) 512 | world_update_block_cache(cx, cy, cz); 513 | } 514 | 515 | void world_update(uint32_t ticks, uint32_t *vbl_counter) { 516 | target_level_time = ticks; 517 | uint32_t in_vbls = (*vbl_counter); 518 | 519 | while (level_time < target_level_time) { 520 | world_tick(); 521 | level_time++; 522 | 523 | if (((*vbl_counter) - in_vbls) >= 2) break; 524 | } 525 | } 526 | 527 | void world_schedule_block_update(int32_t cx, int32_t cy, int32_t cz, uint32_t delay) { 528 | if (cx < 0 || cy < 0 || cz < 0 || cx >= LEVEL_LX || cy >= LEVEL_LY || cz >= LEVEL_LZ) 529 | return; 530 | 531 | update_entry_t* entry = (update_entry_t*) malloc(sizeof(update_entry_t)); 532 | if (entry == NULL) 533 | return; // ah well 534 | 535 | entry->cx = (uint8_t) cx; 536 | entry->cy = (uint8_t) cy; 537 | entry->cz = (uint8_t) cz; 538 | entry->time = ((level_time + 3) >> 2) + delay; 539 | 540 | int ring_pos = (entry->time % UPDATE_LIST_RING_SIZE); 541 | while (ring_pos < 0) ring_pos += UPDATE_LIST_RING_SIZE; 542 | 543 | entry->next = update_list[ring_pos]; 544 | update_list[ring_pos] = entry; 545 | } 546 | -------------------------------------------------------------------------------- /src/worldgen.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #define ELEVATION_MUL 102 // 0.025 << 12 4 | #define ROUGHNESS_MUL 205 // 0.05 << 12 5 | #define DETAIL_MUL 614 // 0.15 << 12 6 | 7 | static int fade(int t) { 8 | int t1 = (t)*6-(15<<12); 9 | int t2 = ((t*t1)>>12)+(10<<12); 10 | int t3 = (t*t2)>>12; 11 | int t4 = (t*t3)>>12; 12 | int t5 = (t*t4)>>12; 13 | return t5; 14 | } 15 | 16 | /* #define fade(t) ( 17 | (t)*(t)*(t)*( 18 | (t)*( 19 | (t)*6-(15<<12) 20 | )+(10<<12) 21 | ) 22 | ) */ 23 | 24 | #define lerp(t,a,b) ( (a) + ( ((t)*((b)-(a))) >> 12) ) 25 | 26 | static int grad(uint8_t hash, int x, int y, int z) 27 | { 28 | switch(hash & 0xF) 29 | { 30 | case 0: return x + y; 31 | case 1: return -x + y; 32 | case 2: return x + -y; 33 | case 3: return -x + -y; 34 | case 4: return x + z; 35 | case 5: return -x + z; 36 | case 6: return x + -z; 37 | case 7: return -x + -z; 38 | case 8: return y + z; 39 | case 9: return -y + z; 40 | case 10: return y + -z; 41 | case 11: return -y + -z; 42 | case 12: return y + x; 43 | case 13: return -y + z; 44 | case 14: return y + -x; 45 | case 15: return -y + -z; 46 | default: while(1) {}; break; 47 | } 48 | } 49 | 50 | static int perlin(uint8_t *perm, int x, int y, int z) 51 | { 52 | int xi = (x >> 12) & 0xFF; 53 | int yi = (y >> 12) & 0xFF; 54 | int zi = (z >> 12) & 0xFF; 55 | int xf = x & 0xFFF; 56 | int yf = y & 0xFFF; 57 | int zf = z & 0xFFF; 58 | int u = fade(xf); 59 | int v = fade(yf); 60 | int w = fade(zf); 61 | 62 | int a = perm[xi]+yi; 63 | int aa = perm[a&0xFF]+zi; 64 | int ab = perm[(a+1)&0xFF]+zi; 65 | int b = perm[(xi+1)&0xFF]+yi; 66 | int ba = perm[b&0xFF]+zi; 67 | int bb = perm[(b+1)&0xFF]+zi; 68 | 69 | uint8_t aaa = perm[aa&0xFF]; 70 | uint8_t aba = perm[ab&0xFF]; 71 | uint8_t baa = perm[ba&0xFF]; 72 | uint8_t bba = perm[bb&0xFF]; 73 | uint8_t aab = perm[(aa+1)&0xFF]; 74 | uint8_t abb = perm[(ab+1)&0xFF]; 75 | uint8_t bab = perm[(ba+1)&0xFF]; 76 | uint8_t bbb = perm[(bb+1)&0xFF]; 77 | 78 | return lerp(w, lerp(v, lerp(u, grad(aaa, xf , yf , zf ), 79 | grad(baa, xf-(1<<12), yf , zf )), 80 | lerp(u, grad(aba, xf , yf-(1<<12), zf ), 81 | grad(bba, xf-(1<<12), yf-(1<<12), zf ))), 82 | lerp(v, lerp(u, grad(aab, xf , yf , zf-(1<<12) ), 83 | grad(bab, xf-(1<<12), yf , zf-(1<<12) )), 84 | lerp(u, grad(abb, xf , yf-(1<<12), zf-(1<<12) ), 85 | grad(bbb, xf-(1<<12), yf-(1<<12), zf-(1<<12) )))); 86 | } 87 | 88 | #define SET(x,y,z,b) if (x>=0&&y>=0&&z>=0&&x=0&&y>=0&&z>=0&&x 0; i--) 133 | { 134 | uint8_t j = seed & 0xFF; 135 | uint8_t t = perm[j]; perm[j] = perm[i]; perm[i] = t; 136 | RAND(seed); 137 | } 138 | 139 | for (int z = 0; z < lz; z++) 140 | for (int x = 0; x < lx; x++) 141 | { 142 | if (pc != NULL) pc(z*lx+x,lz*lx); 143 | 144 | int elevation = perlin(perm, x * ELEVATION_MUL, z * ELEVATION_MUL, 0 << 12); 145 | int roughness = perlin(perm, x * ROUGHNESS_MUL, z * ROUGHNESS_MUL, 10000 << 12); 146 | int detail = perlin(perm, x * DETAIL_MUL, z * DETAIL_MUL, 20000 << 12) >> 1; 147 | int height = (int) ((elevation + ((roughness * detail) >> 12)) * (ly/4) + ((ly/2) << 12) + (1 << 11)); 148 | height >>= 12; 149 | if (height > ly-1) height = ly-1; 150 | else if (height < 1) height = 1; 151 | 152 | heights[z*lx + x] = height; 153 | SET(x,0,z,10); 154 | 155 | if (height > ly/2) { 156 | for (int y = 1; y < height-4; y++) 157 | map[(y * lz + z) * lx + x] = 1; 158 | for (int y = height-4; y < height-1; y++) 159 | map[(y * lz + z) * lx + x] = 3; 160 | map[((height - 1) * lz + z) * lx + x] = 2; 161 | } else if (height == ly/2) { 162 | for (int y = 1; y < height-3; y++) 163 | map[(y * lz + z) * lx + x] = 1; 164 | for (int y = height-3; y < height; y++) 165 | map[(y * lz + z) * lx + x] = 12; 166 | } else { 167 | for (int y = 1; y < height; y++) 168 | map[(y * lz + z) * lx + x] = 1; 169 | for (int y = height; y < ly/2; y++) 170 | map[((y) * lz + z) * lx + x] = 8; 171 | } 172 | } 173 | 174 | if (wc != NULL) wc("Soiling.."); 175 | imax = lx*lz/64; 176 | for (int i = 0; i < imax; i++) 177 | { 178 | if (pc != NULL) pc(i,imax); 179 | 180 | int ore_type = 14; 181 | switch (seed & 0x7) { 182 | case 0: break; // gold 183 | case 1: case 2: case 3: ore_type += 1; break; // iron 184 | default: ore_type += 2; break; // coal 185 | } 186 | RAND(seed); 187 | int cx = seed % lx; 188 | int cz = (seed / lx) % lz; 189 | int cy = (seed / lx / lz) % (ly/2); 190 | RAND(seed); 191 | int cluster_xs = (seed & 1); 192 | int cluster_ys = ((seed >> 1) & 1); 193 | int cluster_zs = ((seed >> 2) & 1); 194 | int cluster_xe = ((seed >> 3) & 1); 195 | int cluster_ye = ((seed >> 4) & 1); 196 | int cluster_ze = ((seed >> 5) & 1); 197 | RAND(seed); 198 | for (int dx=cx-cluster_xs; dx<=cx+cluster_xe; dx++) 199 | for (int dy=cy-cluster_ys; dy<=cy+cluster_ye; dy++) 200 | for (int dz=cz-cluster_zs; dz<=cz+cluster_ze; dz++) 201 | if (GET(dx,dy,dz) == 1) SET(dx,dy,dz,ore_type); 202 | } 203 | 204 | #if 0 205 | if (wc != NULL) wc("Carving.."); 206 | imax = 10*(lx*lz)/(64*64); 207 | for (int i = 0; i < imax; i++) 208 | { 209 | if (pc != NULL) pc(i,imax); 210 | double tx = seed % lx; 211 | double tz = (seed / lx) % lz; 212 | double ty = (seed / lx / lz) % (ly*2/3); 213 | RAND(seed); 214 | double perlin_x = ((seed & 0xFFFF) / 256.0); 215 | double perlin_z = ((seed >> 16) / 256.0); 216 | double perlin_y = 0.0; 217 | RAND(seed); 218 | int perlin_length = (seed & 0x1F) + 8; 219 | for (int i = 0; i < perlin_length; i++) { 220 | int cx = (int) (tx + 0.5); 221 | int cy = (int) (ty + 0.5); 222 | int cz = (int) (tz + 0.5); 223 | int depth = 1; 224 | 225 | for (int dx=cx-depth; dx<=cx+depth; dx++) 226 | for (int dy=cy-depth; dy<=cy+depth; dy++) 227 | for (int dz=cz-depth; dz<=cz+depth; dz++) 228 | SET(dx,dy,dz,0); 229 | 230 | double noise_val_1 = perlin(perm, perlin_x, perlin_y, perlin_z) * 3.14159253565 * 2; 231 | double noise_val_2 = perlin(perm, perlin_x, perlin_y, perlin_z + 1000.0) * 3.14159253565 * 2; 232 | double noise_val_3 = perlin(perm, perlin_x, perlin_y, perlin_z + 2000.0) * 3.14159253565 * 2; 233 | tx += sin(noise_val_1)/1.25; 234 | ty += cos(noise_val_1)/1.25; 235 | 236 | ty += sin(noise_val_2)/1.25; 237 | tz += cos(noise_val_2)/1.25; 238 | 239 | tz += sin(noise_val_3)/1.25; 240 | tx += cos(noise_val_3)/1.25; 241 | 242 | perlin_y += 0.05; 243 | } 244 | } 245 | #endif 246 | 247 | if (wc != NULL) wc("Growing.."); 248 | imax = lx*lz/256; 249 | for (int i = 0; i < imax; i++) 250 | { 251 | if (pc != NULL) pc(i,imax); 252 | int tx = seed % lx; 253 | int tz = (seed / lx) % lz; 254 | RAND(seed); 255 | if (heights[tz*lx+tx] > ly/2 && heights[tz*lx+tx] < ly-5) 256 | { 257 | // plant tree 258 | int y_base = heights[tz*lx+tx]; 259 | if (GET(tx,y_base,tz) != 0) continue; 260 | 261 | int log_height = 3 + (seed % 3); 262 | RAND(seed); 263 | 264 | for (int i = 0; i < log_height; i++) 265 | SET(tx,i+y_base,tz,17); 266 | 267 | SET(tx,y_base+log_height,tz,18); 268 | SET(tx,y_base+log_height+1,tz,18); 269 | 270 | for (int i = -2; i <= 1; i++) { 271 | int sb = (i+2)*8; 272 | SET(tx,y_base+log_height+i,tz-1,18); 273 | SET(tx-1,y_base+log_height+i,tz,18); 274 | SET(tx,y_base+log_height+i,tz+1,18); 275 | SET(tx+1,y_base+log_height+i,tz,18); 276 | 277 | if (i < 1 || (i == 1 && (seed & (1< ly/2) 322 | { 323 | // plant plant 324 | int y_base = heights[tz*lx+tx]; 325 | if (GET(tx,y_base,tz) != 0) continue; 326 | 327 | SET(tx, y_base, tz, (seed&1) + 37); 328 | RAND(seed); 329 | } 330 | } 331 | 332 | if (pc != NULL) pc(1,1); 333 | } 334 | 335 | void world_generate(int mode, uint8_t *map, int32_t lx, int32_t ly, int32_t lz, uint32_t seed, worldgen_stage_callback *wc, save_progress_callback *pc) 336 | { 337 | switch (mode) 338 | { 339 | case 1: world_generate_flat(map, lx, ly, lz, wc); break; 340 | default: world_generate_default(map, lx, ly, lz, seed, wc, pc); break; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /system.cnf: -------------------------------------------------------------------------------- 1 | BOOT = cdrom:\BOOT.EXE;1 2 | TCB = 4 3 | EVENT = 10 4 | STACK = 801ff000 5 | 6 | -------------------------------------------------------------------------------- /tools/32to15.py: -------------------------------------------------------------------------------- 1 | import sys, struct 2 | #import high_quality_rips 3 | 4 | indat = open(sys.argv[1], "rb").read()[18:][:256*256*4] 5 | fp = open(sys.argv[2], "wb") 6 | for i in xrange(256*256): 7 | p = [ord(c) for c in indat[i*4+0:i*4+4]] 8 | if p[3] >= 2: # >= 128: 9 | # Opaque 10 | r = max(0, min(31, p[2]>>3)) 11 | g = max(0, min(31, p[1]>>3)) 12 | b = max(0, min(31, p[0]>>3)) 13 | v = 0x8000|(r<<0)|(g<<5)|(b<<10) 14 | else: 15 | # Transparent 16 | v = 0x0000 17 | fp.write(struct.pack(" None 6 | in_fname = sys.argv[1] 7 | label_name = os.path.basename(in_fname).replace(".", "_") 8 | assert label_name != "" 9 | with open(in_fname, "rb") as infp: 10 | raw_data = infp.read() 11 | data = [ord(raw_data[i:i+1]) for i in range(len(raw_data))] 12 | outfp = sys.stdout 13 | outfp.write("uint8_t %s[] = {\n" % (label_name,)) 14 | for i in range(0, len(data), 16): 15 | b = data[i:i+16] 16 | if i > 0: 17 | outfp.write(",") 18 | outfp.write(",".join("0x%02X" % (v,) for v in b) + "\n") 19 | outfp.write("};") 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /tools/bin2s.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | 4 | 5 | def main(): # type: () -> None 6 | in_fname = sys.argv[1] 7 | label_name = os.path.basename(in_fname).replace(".", "_") 8 | assert label_name != "" 9 | with open(in_fname, "rb") as infp: 10 | raw_data = infp.read() 11 | data = [ord(raw_data[i:i+1]) for i in range(len(raw_data))] 12 | 13 | outfp = sys.stdout 14 | outfp.write(".global %s\n" % (label_name,)) 15 | outfp.write("%s:\n" % (label_name,)) 16 | for i in range(0, len(data), 16): 17 | b = data[i:i+16] 18 | outfp.write("\t.byte " + ",".join("0x%02X" % (v,) for v in b) + "\n") 19 | outfp.write(".global %s__end\n" % (label_name,)) 20 | outfp.write("%s__end:\n" % (label_name,)) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /tools/lz4pack.c: -------------------------------------------------------------------------------- 1 | /* 2 | lz4pack - a tiny packer for the lz4 block format (as opposed to the frame format) 3 | used for certain fromage assets 4 | Copyright (c) 2019 asie 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "lz4.h" 12 | 13 | int main(int argc, char** argv) { 14 | if (argc < 3) { 15 | fprintf(stderr, "Must specify input and output filenames!\n"); 16 | return 1; 17 | } 18 | 19 | int offset = 0; 20 | int prepend_size = 0; 21 | 22 | int c; 23 | while ((c = getopt(argc, argv, "po:")) != -1) { 24 | switch (c) { 25 | case 'p': prepend_size = 1; break; 26 | case 'o': offset = atoi(optarg); break; 27 | } 28 | } 29 | 30 | FILE *fin = fopen(argv[optind + 0], "rb"); 31 | if (fin == NULL) { 32 | fprintf(stderr, "Could not open input file!\n"); 33 | return 1; 34 | } 35 | 36 | FILE *fout = fopen(argv[optind + 1], "wb"); 37 | if (fout == NULL) { 38 | fprintf(stderr, "Could not open output file!\n"); 39 | return 1; 40 | } 41 | 42 | // read whole file in 43 | fseek(fin, 0, SEEK_END); 44 | int size = ftell(fin); 45 | fseek(fin, 0, SEEK_SET); 46 | int read = 0; 47 | 48 | uint8_t *buf = malloc(size); 49 | if (buf == NULL) { return 1; } 50 | 51 | while (read < size && !feof(fin)) { 52 | read += fread(buf + read, 1, size - read, fin); 53 | } 54 | fclose(fin); 55 | 56 | if (offset > 0) { 57 | fwrite(buf, 1, offset, fout); 58 | } 59 | 60 | if (prepend_size != 0) { 61 | int s = size - offset; 62 | fputc(s & 0xFF, fout); 63 | fputc((s >> 8) & 0xFF, fout); 64 | fputc((s >> 16) & 0xFF, fout); 65 | fputc((s >> 24) & 0xFF, fout); 66 | } 67 | 68 | int cmp_size = LZ4_compressBound(size - offset); 69 | char *cmp_data = malloc(cmp_size); 70 | if (cmp_data == NULL) return 1; 71 | 72 | cmp_size = LZ4_compress_default(buf + offset, cmp_data, size - offset, cmp_size); 73 | if (cmp_size <= 0) return 1; 74 | 75 | cmp_data = realloc(cmp_data, cmp_size); 76 | if (cmp_data == NULL) return 1; 77 | 78 | fwrite(cmp_data, 1, cmp_size, fout); 79 | fclose(fout); 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /tools/mkatlas.py: -------------------------------------------------------------------------------- 1 | import sys, struct 2 | import numpy as np 3 | import scipy.cluster.vq as vq 4 | from PIL import Image, ImageOps, ImageColor 5 | 6 | im = Image.open(sys.argv[1]) 7 | fp = open(sys.argv[2], "wb") 8 | clut = [None] * 256 9 | single_pixel_colors = [None] * 256 10 | imgdata = [None] * (256*256) 11 | imgwidth = 256 12 | mipmap_levels = 4 13 | 14 | def draw_4bit(im, ix, iy, iw, ih, tx, ty): 15 | # generate palette 16 | img = [None] * (mipmap_levels+1) 17 | img_data = [None] * (mipmap_levels+1) 18 | img_translucent = [None] * (mipmap_levels+1) 19 | 20 | img[0] = im.crop((ix, iy, ix+iw, iy+ih)).convert("RGBA") 21 | img_data[0] = np.zeros((iw * ih, 3)) 22 | img_translucent[0] = np.zeros((iw * ih)) 23 | for irm in range(1,mipmap_levels+1): 24 | img[irm] = img[0].resize((iw>>irm, ih>>irm), Image.LANCZOS) 25 | img_data[irm] = np.zeros(((iw>>irm) * (ih>>irm), 3)) 26 | img_translucent[irm] = np.zeros(((iw>>irm) * (ih>>irm))) 27 | has_translucent = False 28 | for irm in range(0,mipmap_levels+1): 29 | for iry in range(ih>>irm): 30 | for irx in range(iw>>irm): 31 | img_pixel = img[irm].getpixel((irx, iry)) 32 | img_data[irm][iry * (iw>>irm) + irx] = [ 33 | int(img_pixel[0] * 31 / 255.0), 34 | int(img_pixel[1] * 31 / 255.0), 35 | int(img_pixel[2] * 31 / 255.0) 36 | ] 37 | if img_pixel[3] <= 1: 38 | img_translucent[irm][iry * (iw>>irm) + irx] = 1 39 | has_translucent = True 40 | centroids,_ = vq.kmeans(img_data[0], 15 if has_translucent else 16) 41 | palette = [0x0000] * 16 42 | for pl in range(len(centroids)): 43 | r = max(0, min(31, int(centroids[pl][0] + 0.5))) 44 | g = max(0, min(31, int(centroids[pl][1] + 0.5))) 45 | b = max(0, min(31, int(centroids[pl][2] + 0.5))) 46 | palette[pl] = 0x8000|(r<<0)|(g<<5)|(b<<10) 47 | # TODO: floyd-steinberg 48 | for irm in range(0,mipmap_levels+1): 49 | indexes,_ = vq.vq(img_data[irm],centroids) 50 | for iry in range(ih>>irm): 51 | for irx in range(iw>>irm): 52 | iridx = iry * (iw>>irm) + irx 53 | impc = 15 54 | if img_translucent[irm][iridx] == 0: 55 | impc = indexes[iridx] 56 | imgdata[((ty>>irm)+iry)*imgwidth + ((tx>>irm)+irx)] = impc 57 | single_pixel = img[mipmap_levels].getpixel((0, 0)) 58 | single_pixel_color = 0 59 | single_pixel_color |= int(single_pixel[2]) << 16 60 | single_pixel_color |= int(single_pixel[1]) << 8 61 | single_pixel_color |= int(single_pixel[0]) 62 | return palette, single_pixel_color 63 | 64 | # imgp = img.convert(mode='P', palette=Image.ADAPTIVE, colors=16) 65 | # imgpalr = imgp.getpalette() 66 | # palette = [None] * 16 67 | # for col in range(16): 68 | # palette[col] = [imgpalr[col * 3], imgpalr[col * 3 + 1], imgpalr[col * 3 + 2], 255] 69 | # for iry in range(ih): 70 | # for irx in range(iw): 71 | # impc = imgp.getpixel((irx, iry)) 72 | # impp = img.getpixel((irx, iry)) 73 | # if len(impp) > 3: 74 | # palette[impc][3] = impp[3] 75 | # imgdata[(ty+iry)*imgwidth + tx+irx] = impc 76 | # return palette 77 | 78 | def write_palette(fp, palette): 79 | for ip in range(16): 80 | fp.write(struct.pack("= 5: 8 | imwater = Image.open(sys.argv[3]) 9 | imlava = Image.open(sys.argv[4]) 10 | fp = open(sys.argv[2], "wb") 11 | clut = [None] * 256 12 | imgdata = [None] * (256*256) 13 | imgwidth = 256 14 | 15 | def draw_4bit(im, ix, iy, iw, ih, tx, ty): 16 | img = im.crop((ix, iy, ix+iw, iy+ih)).convert("RGBA") 17 | imgp = img.convert(mode='P', palette=Image.ADAPTIVE, colors=16) 18 | imgpalr = imgp.getpalette() 19 | palette = [None] * 16 20 | for col in range(16): 21 | palette[col] = [imgpalr[col * 3], imgpalr[col * 3 + 1], imgpalr[col * 3 + 2], 255] 22 | for iry in range(ih): 23 | for irx in range(iw): 24 | impc = imgp.getpixel((irx, iry)) 25 | impp = img.getpixel((irx, iry)) 26 | if len(impp) > 3: 27 | palette[impc][3] = impp[3] 28 | imgdata[(ty+iry)*imgwidth + tx+irx] = impc 29 | return palette 30 | 31 | def write_palette(fp, palette): 32 | for ip in range(16): 33 | v = 0x0000 34 | if palette[ip][3] >= 2: 35 | # Opaque 36 | r = max(0, min(31, palette[ip][0]>>3)) 37 | g = max(0, min(31, palette[ip][1]>>3)) 38 | b = max(0, min(31, palette[ip][2]>>3)) 39 | v = 0x8000|(r<<0)|(g<<5)|(b<<10) 40 | fp.write(struct.pack(" 0: 15 | return ix+1 16 | return 1 17 | 18 | for fch in range(0, 128, 2): 19 | offx = (fch & 15) * 8 20 | offy = (fch >> 4) * 8 21 | fp.write(struct.pack(" 0): 38 | v |= 1 39 | impc = im.getpixel((ix+1, iy)) 40 | if (impc[alpha_idx] > 0): 41 | v |= 16 42 | if sys.argv[3] == "2x": 43 | fp.write(struct.pack("> 4) * 17)) 45 | else: 46 | fp.write(struct.pack(" 3: 23 | palette[impc][3] = impp[3] 24 | imgdata[(ty+iry)*imgwidth + tx+irx] = impc 25 | return palette 26 | 27 | def write_palette(fp, palette): 28 | for ip in range(16): 29 | v = 0x0000 30 | if palette[ip][3] >= 2: 31 | # Opaque 32 | r = max(0, min(31, palette[ip][0]>>3)) 33 | g = max(0, min(31, palette[ip][1]>>3)) 34 | b = max(0, min(31, palette[ip][2]>>3)) 35 | v = 0x8000|(r<<0)|(g<<5)|(b<<10) 36 | fp.write(struct.pack("