check in existing work

This commit is contained in:
water
2020-08-22 22:30:12 -04:00
parent 4fe75d3cc0
commit acf086a3d2
160 changed files with 32609 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
---
BasedOnStyle: Chromium
ColumnLimit: 100
SortIncludes: false
+4
View File
@@ -0,0 +1,4 @@
# for clion
cmake-build-debug/*
.idea/*
build/*
+3
View File
@@ -0,0 +1,3 @@
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
+32
View File
@@ -0,0 +1,32 @@
# Top Level CMakeLists.txt
cmake_minimum_required(VERSION 3.0) # todo - this was picked randomly
project(jak)
set(CMAKE_CXX_STANDARD 11)
# optimization level can be set here. Note that game/ overwrites this for building game C++ code.
set(CMAKE_CXX_FLAGS "-O0 -ggdb -Wall \
-Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wmissing-include-dirs -Woverloaded-virtual \
-Wredundant-decls -Wshadow -Wsign-promo ")
# includes relative to top level jak-project folder
include_directories(./)
# build asset packer/unpacker
add_subdirectory(asset_tool)
# build decompiler
add_subdirectory(decompiler)
# build the game code in C++
add_subdirectory(game)
# build the compiler
add_subdirectory(goalc)
# build the gtest libraries
add_subdirectory(third-party/googletest)
# build tests
add_subdirectory(test)
+92
View File
@@ -1,3 +1,92 @@
Project Structure
----------------------
Requirements:
- `cmake` for build system
- `clang-format` for formatting code (there is already a `.clang-format` provided)
- `gtest` for testing. (Run `git submodule update --init --recursive` to check out the repository)
- `nasm` for assembling x86. There isn't much x86 assembly so if there's a better way to do this for windows, we can change.
- Third party libraries (`nlohmann/json`, `minilzo`, and `linenoise`) are provided in the `third-party` folder
Layout:
- `goalc` is the GOAL compiler
- `gs` contains GOOS code for parts of GOOS implemented in GOOS
- `gc` contains GOAL code for parts of GOAL implemented in GOAL (must generate no machine code, just defining macros)
- `decompiler` is the decompiler
- `data` will contain big assets and the output of the GOAL compiler (not checked in to git)
- `out` will contain the finished game (not checked into git)
- `resources` will contain data which is checked into git
- `game` will contain the game source code
- `common` will contain all data/type shared between different applications.
- `doc` will contain documentation (markdown format?)
- `iso_data` is where the files from the DVD go
- `third-party` will contain code we didn't write. Google Test is a git submodule in this folder.
- `tests` will contain all tests
- `asset_tool` will contain the asset packer/unpacker
Design:
(if anybody has better ideas, feel free to suggest improvements! This is just a rough plan for now)
- All C++ code should build from the top-level `cmake`.
- All C++ applications (GOAL compiler, asset extractor, asset packer, runtime, test) should have a script in the top level which launches them.
- All file paths should be relative to the `jak` folder.
- The planned workflow for building a game:
- `git submodule update --init --recursive` : check out gtest
- `mkdir build; cd build` : create build folder for C++
- `cmake ..; make -j` : build C++ code
- `cd ..`
- `./test.sh` : run gtests
- `./asset_extractor.sh ./iso_data` : extract assets from game
- `./build_engine.sh` : run GOAL compiler to build all game code
- `./build_game.sh` : run the asset packer to build the game
- `./run_game.sh` : run the game
- Workflow for development:
- `./gc.sh` : run the compiler in interactive mode
- `./gs.sh` : run a goos interpreter in interactive mode
- `./decomp.sh ./iso_data` : run the decompiler
Current state:
- GOAL compiler just implements the GOOS Scheme Macro Language. Running `./gc.sh` just loads the GOOS library (`goalc/gs/goos-lib.gs`) and then goes into an interactive mode. Use `(exit)` to exit.
- `./test.sh` runs tests for some game C++ code, for GOOS, for the reader, for the listener connection, and for some early emitter stuff.
- The runtime boots in `fakeiso` mode which will load some dummy files. Then the C Kernel (`game/kernel`) will load the `KERNEL.CGO` and `GAME.CGO` files, which are from the "proof of concept" GOAL compiler. If you run `./gk.sh`, you should see it load stuff, then print:
```
calling play!
~~ HACK ~~ : fake play has been called
InitListenerConnect
InitCheckListener
kernel: machine started
```
where the `~~ HACK ~~` message is from code in `KERNEL.CGO`.
Code Guidelines:
- Avoid warnings
- Use asserts over throwing exceptions in game code (throwing exceptions from C code called by GOAL code is sketchy)
TODOS:
- Build on Windows!
- Networking
- File paths
- Timer
- CMake?
- Assembly
- Windows calling convention for assembly stuff
- pthreads (can probably replace with `std::thread`, I don't remember why I used `pthread`s)
- performance stats for `SystemThread` (probably just get rid of these performance stats completely)
- `mmap`ing executable memory
- line input library (appears windows compatible?)
- Clean up use of namespaces
- Clean up the print message when `gk` starts.
- Finish commenting runtime stuff
- Runtime document
- GOOS document
- Listener protocol document
- GOAL Compiler IR
- GOAL Compiler Skeleton
In Progress:
- GOAL emitter / emitter testing setup
Project Description
-----------------------
@@ -29,6 +118,7 @@ Some statistics:
The rough timeline is to finish sometime in 2022. If it looks like this is impossible, the project will be abandoned. But I have already spent about 4 months preparing to start this and seems doable. I also have some background in compilers, and familiarity with PS2 (worked on DobieStation PS2 emulator) / MIPS in general (wrote a PS1 emulator). I think the trick will be making good automated tools - the approach taken for SM64 and other N64 decompilations is way too labor-intensive to work.
GOAL Decompiler
------------------
The decompiler is in progress, at
@@ -190,3 +280,5 @@ Packs together all assets/compiled code/runtime into a format that can be played
It's important that the asset extraction/packing can be automated so we can avoid distributing the assets, which are large and probably not supposed to be distributed.
View File
+20
View File
@@ -0,0 +1,20 @@
/*!
* @file common_types.h
* Common Integer Types.
*/
#ifndef JAK1_COMMON_TYPES_H
#define JAK1_COMMON_TYPES_H
#include <cstdint>
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using s8 = int8_t;
using s16 = int16_t;
using s32 = int32_t;
using s64 = int64_t;
#endif // JAK1_COMMON_TYPES_H
+37
View File
@@ -0,0 +1,37 @@
/*!
* @file link_types.h
* Types used in the linking data, shared between the object file generator and the kernel's linker.
*/
#ifndef JAK1_LINK_TYPES_H
#define JAK1_LINK_TYPES_H
enum LinkKind {
LINK_TABLE_END = 0, //! no more linking data
LINK_SYMBOL_OFFSET = 1, //! link a symbol (pointer to symbol table entry)
LINK_TYPE_PTR = 2, //! link a pointer to a type.
LINK_DISTANCE_TO_OTHER_SEG_64 = 3, //! link to another segment
LINK_DISTANCE_TO_OTHER_SEG_32 = 4, //! link to another segment
};
enum SegmentTypes { MAIN_SEGMENT = 0, DEBUG_SEGMENT = 1, TOP_LEVEL_SEGMENT = 2 };
constexpr int N_SEG = 3;
/*!
* Data at the front of the DGO.
*/
struct DgoHeader {
u32 object_count;
char name[60];
};
/*!
* Data at the front of each OBJ.
*/
struct ObjectHeader {
u32 size;
char name[60];
};
#endif // JAK1_LINK_TYPES_H
+59
View File
@@ -0,0 +1,59 @@
/*!
* @file listener_common.h
* Common types shared between the compiler and the runtime for the listener connection.
*/
#ifndef JAK1_LISTENER_COMMON_H
#define JAK1_LISTENER_COMMON_H
#include "common/common_types.h"
/*!
* Header of a DECI2 protocol message
* TODO - there are other copies of this somewhere
*/
struct Deci2Header {
u16 len; //! size of data following header
u16 rsvd; //! zero, used internally by runtime.
u16 proto; //! protocol identification number
u8 src; //! identification code of sender
u8 dst; //! identification code of recipient
};
/*!
* Type of message sent to compiler
*/
enum class ListenerMessageKind : u16 {
MSG_ACK = 0, //! Acknowledge a compiler message
MSG_OUTPUT = 1, //! Send output buffer data
MSG_PRINT = 2, //! Send print buffer data
MSG_INVALID = 24
};
/*!
* Type of message sent from compiler
*/
enum ListenerToTargetMsgKind {
LTT_MSG_POKE = 1, //! "Poke" the game and have it flush buffers
LTT_MSG_INSEPCT = 5, //! Inspect an object
LTT_MSG_PRINT = 6, //! Print an object
LTT_MSG_PRINT_SYMBOLS = 7, //! Print all symbols
LTT_MSG_RESET = 8, //! Reset the game
LTT_MSG_CODE = 9 //! Send code to patch into the game
};
/*!
* The full header of a listener message, including the Deci2Header
* TODO - there are other copies of this somewhere
*/
struct ListenerMessageHeader {
Deci2Header deci2_header; //! The header used for DECI2 communication
ListenerMessageKind msg_kind; //! GOAL Listener message kind
u16 u6; //! Unknown
u32 msg_size; //! Size of data after this header
u64 u8; //! Unknown
};
constexpr int DECI2_PORT = 8112; // TODO - is this a good choise?
#endif // JAK1_LISTENER_COMMON_H
+80
View File
@@ -0,0 +1,80 @@
/*!
* @file symbols.h
* The location of fixed symbols in the GOAL symbol table.
*/
#ifndef JAK1_SYMBOLS_H
#define JAK1_SYMBOLS_H
constexpr int FIX_SYM_EMPTY_CAR = -0xc;
constexpr int FIX_SYM_EMPTY_PAIR = -0xa;
constexpr int FIX_SYM_EMPTY_CDR = -0x8;
constexpr int FIX_SYM_FALSE = 0x0; // GOAL boolean #f (note that this is equal to the $s7 register)
constexpr int FIX_SYM_TRUE = 0x8; // GOAL boolean #t
// types
constexpr int FIX_SYM_FUNCTION_TYPE = 0x10; // GOAL type of function
constexpr int FIX_SYM_BASIC_TYPE = 0x18; // GOAL structure type with type tag
constexpr int FIX_SYM_STRING_TYPE = 0x20; // GOAL string type (gstring)
constexpr int FIX_SYM_SYMBOL_TYPE = 0x28; // GOAL symbol type
constexpr int FIX_SYM_TYPE_TYPE = 0x30; // GOAL type of type
constexpr int FIX_SYM_OBJECT_TYPE = 0x38; // GOAL parent type of all types
constexpr int FIX_SYM_LINK_BLOCK = 0x40; // GOAL type of link-block (used by linker, but seems to be unused by GOAL)
constexpr int FIX_SYM_INTEGER_TYPE = 0x48; // GOAL integer parent type, assumes unboxed
constexpr int FIX_SYM_SINTEGER_TYPE = 0x50; // GOAL signed integer parent type, assumes unboxed
constexpr int FIX_SYM_UINTEGER_TYPE = 0x58; // GOAL unsinged integer parent type, assumes unboxed
constexpr int FIX_SYM_BINTEGER_TYPE = 0x60; // GOAL "boxed integer" type
constexpr int FIX_SYM_INT8_TYPE = 0x68; // GOAL 8-bit signed integer
constexpr int FIX_SYM_INT16_TYPE = 0x70; // ...
constexpr int FIX_SYM_INT32_TYPE = 0x78; // ...
constexpr int FIX_SYM_INT64_TYPE = 0x80; // ...
constexpr int FIX_SYM_INT128_TYPE = 0x88; // GOAL 128-bit integer type, behaves strangely
constexpr int FIX_SYM_UINT8_TYPE = 0x90; // GOAL 8-bit unsigned integer
constexpr int FIX_SYM_UINT16_TYPE = 0x98; // ...
constexpr int FIX_SYM_UINT32_TYPE = 0xA0; // ...
constexpr int FIX_SYM_UINT64_TYPE = 0xA8; // ...
constexpr int FIX_SYM_UINT128_TYPE = 0xB0; // ...
constexpr int FIX_SYM_FLOAT_TYPE = 0xB8; // GOAL 32-bit floating point type
constexpr int FIX_SYM_PROCESS_TREE_TYPE = 0xC0; // GOAL process-tree type. Used in the gkernel
constexpr int FIX_SYM_PROCESS_TYPE = 0xC8; // GOAL process type
constexpr int FIX_SYM_THREAD_TYPE = 0xD0; // GOAL thread type
constexpr int FIX_SYM_STRUCTURE_TYPE = 0xD8; // GOAL structure type. Any type with fields
constexpr int FIX_SYM_PAIR_TYPE = 0xE0; // GOAL pair type
constexpr int FIX_SYM_POINTER_TYPE = 0xE8; // GOAL pointer type (32-bit)
constexpr int FIX_SYM_NUMBER_TYPE = 0xF0; // GOAL number type (parent of integer/float types)
constexpr int FIX_SYM_ARRAY_TYPE = 0xF8; // GOAL array type
constexpr int FIX_SYM_VU_FUNCTION_TYPE = 0x100; // GOAL vu-function type
constexpr int FIX_SYM_CONNECTABLE_TYPE = 0x108; // GOAL connectable
constexpr int FIX_SYM_STACK_FRAME_TYPE = 0x110; // GOAL stack-frame
constexpr int FIX_SYM_FILE_STREAM_TYPE = 0x118; // GOAL file-stream
constexpr int FIX_SYM_KHEAP = 0x120; // GOAL kheap
// GOAL functions
constexpr int FIX_SYM_NOTHING_FUNC = 0x128; // GOAL nothing-func (does nothing)
constexpr int FIX_SYM_DEL_BASIC_FUNC = 0x130; // GOAL delete-basic function
// GOAL allocation symbols (?)
constexpr int FIX_SYM_STATIC = 0x138; // GOAL 'static
constexpr int FIX_SYM_GLOBAL_HEAP = 0x140; // GOAL 'global
constexpr int FIX_SYM_DEBUG_HEAP = 0x148; // GOAL 'debug
constexpr int FIX_SYM_LOADING_LEVEL = 0x150; // ??
constexpr int FIX_SYM_LOADING_PACKAGE = 0x158; // ??
constexpr int FIX_SYM_PROCESS_LEVEL_HEAP = 0x160; // ??
constexpr int FIX_SYM_STACK = 0x168; // GOAL 'stack
constexpr int FIX_SYM_SCRATCH = 0x170; // GOAL 'scratch
// GOAL random stuff
constexpr int FIX_SYM_SCRATCH_TOP = 0x178; // GOAL *scratch-top*
constexpr int FIX_SYM_ZERO_FUNC = 0x180; // GOAL zero-func (returns 0x0 in $v0 register)
constexpr int FIX_SYM_ASIZE_OF_BASIC_FUNC = 0x188; // GOAL asize-of-basic function
constexpr int FIX_SYM_COPY_BASIC_FUNC = 0x190; // GOAL copy-basic function
constexpr int FIX_SYM_LEVEL = 0x198; // ??
constexpr int FIX_SYM_ART_GROUP = 0x1a0; // ??
constexpr int FIX_SYM_TX_PAGE_DIR = 0x1a8; // ??
constexpr int FIX_SYM_TX_PAGE = 0x1b0; // ??
constexpr int FIX_SYM_SOUND = 0x1b8; // ??
constexpr int FIX_SYM_DGO = 0x1c0; // ??
constexpr int FIX_SYM_TOP_LEVEL = 0x1c8; // ??
constexpr int FIX_FIXED_SYM_END_OFFSET = 0x1d0;
#endif // JAK1_SYMBOLS_H
+21
View File
@@ -0,0 +1,21 @@
/*!
* @file versions.h
* Version numbers for GOAL Language, Kernel, etc...
*/
#ifndef JAK1_VERSIONS_H
#define JAK1_VERSIONS_H
#include "common/common_types.h"
namespace versions {
// language version
constexpr s32 GOAL_VERSION_MAJOR = 2;
constexpr s32 GOAL_VERSION_MINOR = 6;
}
// GOAL kernel version
constexpr int KERNEL_VERSION_MAJOR = 2;
constexpr int KERNEL_VERSION_MINOR = 0;
#endif // JAK1_VERSIONS_H
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
View File
+84
View File
@@ -0,0 +1,84 @@
# Reader
GOOS and GOAL both use the same reader, which converts text files to S-Expressions and allows these s-expressions to be mapped back to a line in a source file for error messages. This docuemnt explains the syntax of the reader. Note that these rules do not explain the syntax of the language (for instance, GOAL has a much more complicated system of integers and many more restrictions), but rather the rules of how your program source must look.
## Integer Input
Integers handled by the reader are 64-bits. Any overflow is considered an error. An integer can be specified as a decimal, like `0` or `-12345`; in hex, like `#xbeef`; or in binary, like `#b101001`. All three representations can be used anywhere an integer is used. Hex numbers do not care about the case of the characters. Decimal numbers are signed, and wrapping from a large positive number to a negative number will generate an error. The valid input range for decimals is `INT64_MIN` to `INT64_MAX`. Hex and binary are unsigned and do not support negative signs, but allow large positive numbers to wrap to negative. Their input range is `0` to `UINT64_MAX`. For example, `-1` can be entered as `-1` or `#xffffffffffffffff`, but not as `UINT64_MAX` in decimal.
## Floating Point Input
Floating point values handled by the reader are implemented with `double`. Weird numbers (denormals, NaN, infinity) are invalid and not handled by the reader directly. A number _must_ have a decimal point to be interpreted as floating point. Otherwise, it will be an integer. Leading/trailing zeros are optional.
## Character Input
Characters are used to represent characters that are part of text. The character `c` is represented by `#\c`. This representation is used for all ASCII characters between `!` and `~`. There are three special characters which have a non-standard representation:
- Space : `#\\s`
- New Line: `#\\n`
- Tab: `#\\t`
All other characters are invalid.
## String
A string is a sequence of characters, surrounding by double quotes. The ASCII characters from ` ` to `~` excluding `"` can be entered directly. Strings have the following escape codes:
- `\\` : insert a backslash
- `\n` : insert a new line
- `\t` : insert a tab
- `\"` : insert a double quote
## Comments
The reader supports line comments with `;` and multi-line comments with `#| |#`. For example
```
(print "hi") ; prints hi
#|
this is a multi-line comment!
(print "hi") <- this is commented out.
|#
```
## Array
The reader supports arrays with the following syntax:
```
; array of 1, 2, 3, 4
#(1 2 3 4)
```
Arrays can be nested with lists, pairs, and other arrays.
## Pair
The reader supports pairs with the following syntax:
```
; pair of a, b
(a . b)
```
Pairs can be nested with lists, pairs, and arrays.
## List
The reader supports lists. Lists are just an easier way of constructing a linked list of pairs, terminated with the empty list. The empty list is a special list written like `()`.
```
; list of 1, 2, 3
(1 2 3)
; actually the same as
(1 . (2 . (3 . ())))
```
## Symbol
A symbol is a sequence of characters containing no whitespace, and not matching any other data type. (Note: this is not a very good definition). Typically symbols are lower case, and words are separated by a `-`. Examples:
```
this-is-a-symbol
; you can have weird symbols too:
#f
#t
-
*
+
__WEIRDLY-NamedSymbol ; this is weird, but OK.
```
## Reader Macros
The reader has some default macros which are common in Scheme/LISP:
- `'x` will be replaced with `(quote x)`
- `` `x`` will be replaced with `(quasiquote x)`
- `,x` will be replaced with `(unquote x)`
- `,@` will be replaced with `(unquote-splicing x)`
+58
View File
@@ -0,0 +1,58 @@
# We define our own compilation flags here.
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-O0 -ggdb -Wall \
-Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wmissing-include-dirs -Woverloaded-virtual \
-Wredundant-decls -Wshadow -Wsign-promo ")
enable_language(ASM_NASM)
set(RUNTIME_SOURCE
main.cpp
runtime.cpp
system/SystemThread.cpp
system/IOP_Kernel.cpp
system/iop_thread.cpp
system/Deci2Server.cpp
sce/libcdvd_ee.cpp
sce/libscf.cpp
sce/deci2.cpp
sce/sif_ee.cpp
sce/iop.cpp
sce/stubs.cpp
kernel/asm_funcs.nasm
kernel/fileio.cpp
kernel/kboot.cpp
kernel/kdgo.cpp
kernel/kdsnetm.cpp
kernel/klink.cpp
kernel/klisten.cpp
kernel/kmachine.cpp
kernel/kmalloc.cpp
kernel/kmemcard.cpp
kernel/kprint.cpp
kernel/kscheme.cpp
kernel/ksocket.cpp
kernel/ksound.cpp
overlord/dma.cpp
overlord/fake_iso.cpp
overlord/iso.cpp
overlord/iso_api.cpp
overlord/iso_cd.cpp
overlord/iso_queue.cpp
overlord/isocommon.cpp
overlord/overlord.cpp
overlord/ramdisk.cpp
overlord/sbank.cpp
overlord/soundcommon.cpp
overlord/srpc.cpp
overlord/ssound.cpp
overlord/stream.cpp)
# the runtime should be built without any static/dynamic libraries.
add_executable(gk ${RUNTIME_SOURCE})
# we also build a runtime library for testing. This version is likely unable to call GOAL code correctly, but
# can be used to test other things.
add_library(runtime ${RUNTIME_SOURCE})
target_link_libraries(gk pthread)
+31
View File
@@ -0,0 +1,31 @@
/*!
* @file dgo_rpc_types.h
* Types used for the DGO Remote Procedure Call between the EE and the IOP
*/
#ifndef JAK1_DGO_RPC_TYPES_H
#define JAK1_DGO_RPC_TYPES_H
#include "common/common_types.h"
constexpr int DGO_RPC_ID = 0xdeb4;
constexpr int DGO_RPC_CHANNEL = 3;
constexpr int DGO_RPC_LOAD_FNO = 0;
constexpr int DGO_RPC_LOAD_NEXT_FNO = 1;
constexpr int DGO_RPC_CANCEL_FNO = 2;
constexpr int DGO_RPC_RESULT_INIT = 666;
constexpr int DGO_RPC_RESULT_ABORTED = 3;
constexpr int DGO_RPC_RESULT_MORE = 2;
constexpr int DGO_RPC_RESULT_ERROR = 1;
constexpr int DGO_RPC_RESULT_DONE = 0;
struct RPC_Dgo_Cmd {
uint16_t rsvd;
uint16_t result;
uint32_t buffer1;
uint32_t buffer2;
uint32_t buffer_heap_top;
char name[16];
};
#endif // JAK1_DGO_RPC_TYPES_H
+12
View File
@@ -0,0 +1,12 @@
/*!
* @file loader_rpc_types.h
* Types used for the Loader Remote Procedure Call between the EE and the IOP
*/
#ifndef JAK1_LOADER_RPC_TYPES_H
#define JAK1_LOADER_RPC_TYPES_H
constexpr int LOADER_RPC_ID = 0xdeb2;
constexpr int LOADER_RPC_CHANNEL = 1;
#endif // JAK1_LOADER_RPC_TYPES_H
+13
View File
@@ -0,0 +1,13 @@
/*!
* @file play_rpc_types.h
* Types used for the play Remote Procedure Call between the EE and the IOP.
* Note that PLAY and PLAYER are different.
*/
#ifndef JAK1_PLAY_RPC_TYPES_H
#define JAK1_PLAY_RPC_TYPES_H
constexpr int PLAY_RPC_ID = 0xdeb6;
constexpr int PLAY_RPC_CHANNEL = 5;
#endif // JAK1_PLAY_RPC_TYPES_H
+13
View File
@@ -0,0 +1,13 @@
/*!
* @file player_rpc_types.h
* Types used for the player Remote Procedure Call between the EE and the IOP.
* Note that PLAY and PLAYER are different.
*/
#ifndef JAK1_PLAYER_RPC_TYPES_H
#define JAK1_PLAYER_RPC_TYPES_H
constexpr int PLAYER_RPC_ID = 0xdeb1;
constexpr int PLAYER_RPC_CHANNEL = 0;
#endif // JAK1_PLAYER_RPC_TYPES_H
+25
View File
@@ -0,0 +1,25 @@
/*!
* @file ramdisk_rpc_types.h
* Types used for the RamDisk Remote Procedure Call between the EE and the IOP
*/
#ifndef JAK1_RAMDISK_RPC_TYPES_H
#define JAK1_RAMDISK_RPC_TYPES_H
#include "common/common_types.h"
constexpr int RAMDISK_RPC_ID = 0xdeb3;
constexpr int RAMDISK_RPC_CHANNEL = 2;
constexpr int RAMDISK_GET_DATA_FNO = 0;
constexpr int RAMDISK_RESET_AND_LOAD_FNO = 1;
constexpr int RAMDISK_BYPASS_LOAD_FILE = 4;
struct RPC_Ramdisk_LoadCmd {
char pad[4];
uint32_t file_id_or_ee_addr;
uint32_t offset_into_file;
uint32_t size;
char name[16]; // guess on length?
};
#endif // JAK1_RAMDISK_RPC_TYPES_H
+10
View File
@@ -0,0 +1,10 @@
; Fake ISO file - used to map files in jak-project/ to files available for loading from OVERLORD.
; Each entry should consist of an ISO name, followed by a file name
; note that tweakval, vagdir, screen1 have dummy data for now.
KERNEL.CGO resources/KERNEL.CGO
GAME.CGO resources/GAME.CGO
TEST.CGO resources/TEST.CGO
TWEAKVAL.MUS resources/TWEAKVAL.MUS
VAGDIR.AYB resources/VAGDIR.AYB
SCREEN1.USA resources/SCREEN1.USA
+96
View File
@@ -0,0 +1,96 @@
/*!
* @file Ptr.h
* Representation of a GOAL pointer which can be converted to/from a C pointer.
*/
#ifndef JAK_PTR_H
#define JAK_PTR_H
#include <stdexcept>
#include "game/runtime.h"
#include "common/common_types.h"
/*!
* GOAL pointer to a T. Represented as a 32-bit unsigned offset from g_ee_main_mem.
* A NULL pointer has an offset of 0.
*
* This doesn't have to be very efficient, as this implementation is only used in the C Kernel.
* The GOAL implementation is much more efficient.
*
* Consider putting size checks on these?
*/
template <typename T>
struct Ptr {
u32 offset;
/*!
* Default pointer is NULL.
*/
Ptr() { offset = 0; }
/*!
* Pointer from manual offset.
*/
explicit Ptr(u32 v) { offset = v; }
/*!
* Dereference a pointer. Will throw if you do this on a null pointer.
*/
T* operator->() {
if (offset) {
return (T*)(g_ee_main_mem + offset);
} else {
throw std::runtime_error("Ptr null dereference!");
}
}
/*!
* Dereference a pointer. Will throw if you do this on a null pointer.
*/
T& operator*() {
if (offset) {
return *(T*)(g_ee_main_mem + offset);
} else {
throw std::runtime_error("Ptr null dereference!");
}
}
// pointer math
Ptr operator+(s32 diff) { return Ptr(offset + diff); }
s32 operator-(Ptr<T> x) { return offset - x.offset; }
Ptr operator-(s32 diff) { return Ptr(offset - diff); }
bool operator==(const Ptr<T>& x) { return offset == x.offset; }
/*!
* Convert to a C pointer.
*/
T* c() {
if (!offset) {
return nullptr;
}
return (T*)(g_ee_main_mem + offset);
}
template <typename T2>
Ptr<T2> cast() {
return Ptr<T2>(offset);
}
};
template <typename T>
Ptr<T> make_ptr(T* x) {
if (!x) {
return Ptr<T>(0);
}
return Ptr<T>((u8*)x - g_ee_main_mem);
}
template <typename T>
Ptr<u8> make_u8_ptr(T* x) {
if (!x) {
return Ptr<u8>(0);
}
return Ptr<u8>((u8*)x - g_ee_main_mem);
}
#endif // JAK_PTR_H
+98
View File
@@ -0,0 +1,98 @@
;;;;;;;;;;;;;;;;;;;;
;; asm_funcs.nasm ;;
;;;;;;;;;;;;;;;;;;;;
;; GOAL Runtime assembly functions. These exist only in the x86 version of GOAL.
;; declaration of the extern "C" function format_impl
extern format_impl
SECTION .TEXT
;; This _format function which will be exported to the GOAL symbol table at runtime start as "_format"
;; This function accepts 8 GOAL arguments and puts them on the stack, then calls format_impl and passes
;; a pointer to this array of GOAL arguments as the argument. The reason for this is that GOAL and
;; the standard System V ABI used in Linux are different for 8 argument function calls.
global _format
_format:
; GOAL will call with regs RDI, RSI, RDX, RCX, R8, R9, R10, R11
; to make sure the stack frame is aligned
sub rsp, 8
; push all registers and create the register array on the stack
push r11
push r10
push r9
push r8
push rcx
push rdx
push rsi
push rdi
; set the first argument register to the stack argument array
mov rdi, rsp
; call C function to do format, result will go in RAX
call format_impl
; restore
; (note - this could probably just be add rsp 72, we don't care about the value of these register)
pop rdi
pop rsi
pop rdx
pop rcx
pop r8
pop r9
pop r10
pop r11
add rsp, 8
ret
;; NOTE: calling format has a _lot_ of indirection...
;; symbol table lookup to find the GOAL "format" symbol value
;; run the GOAL-to-C trampoline (on GOAL heap) to jump to this _format
;; run this wrapper to call the real format_impl
;; The _call_goal_asm function is used to call a GOAL function from C.
;; It supports up to 3 arguments and a return value.
;; This should be called with the arguments:
;; - first goal arg
;; - second goal arg
;; - third goal arg
;; - address of function to call
;; - address of the symbol table
;; - GOAL memory space offset
global _call_goal_asm
_call_goal_asm:
;; x86 saved registers we need to modify for GOAL should be saved
push r13
push r14
push r15
;; RDI - first arg
;; RSI - second arg
;; RDX - third arg
;; RCX - function pointer (goes in r13)
;; R8 - st (goes in r14)
;; R9 - off (goes in r15)
;; set GOAL function pointer
mov r13, rcx
;; offset
mov r15, r8
;; symbol table
mov r14, r9
;; call GOAL by function pointer
call r13
;; retore x86 registers.
pop r15
pop r14
pop r13
ret
+500
View File
@@ -0,0 +1,500 @@
/*!
* @file fileio.cpp
* GOAL Low-Level File I/O and String Utilities
* DONE!
*/
#include <cassert>
#include <cstring>
#include <cstdio>
#include "game/sce/stubs.h"
#include "fileio.h"
#include "kprint.h"
namespace {
// buffer for file paths. This might be static char buffer[512]. Maybe 633 is the line number?
char buffer_633[512];
} // namespace
void fileio_init_globals() {
memset(buffer_633, 0, 512);
}
using namespace ee;
/*!
* Return pointer to null terminator of string.
* const is for losers.
* DONE, EXACT
*/
char* strend(char* str) {
while (*str)
str++;
return str;
}
/*!
* An implementation of Huffman decoding.
* In this limited decoder, your data must have lower two bits equal to zero.
* @param loc_ptr pointer to pointer to data to read (will be modified to point to next word)
* @return decoded word
* UNUSED, EXACT
*/
u32 ReadHufWord(u8** loc_ptr) {
u8* loc = *loc_ptr; // pointer to data to read
u32 value = *(u32*)loc; // read word
u8* next_loc = loc + 1; // next data to read
u32 length = value & 3; // length of word is stored in lower two bits.
switch (length) {
case 0: // already all set.
break;
case 1:
value = (value & 0xfc) | (loc[1] << 8);
next_loc = loc + 2;
break;
case 2:
value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10);
next_loc = loc + 3;
break;
case 3:
value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10) | (loc[3] << 0x18);
next_loc = loc + 4;
break;
default:
assert(false);
}
// update location pointer
*loc_ptr = next_loc;
return value;
}
/*!
* Copy a string from src to dst. The null terminator is copied too.
* This is identical to normal strcpy.
* DONE, EXACT
*/
void kstrcpy(char* dst, const char* src) {
char* dst_ptr = dst;
const char* src_ptr = src;
while (*src_ptr != 0) {
*dst_ptr = *src_ptr;
src_ptr++;
dst_ptr++;
}
*dst_ptr = 0;
}
/*!
* Copy a string from src to dst, making all letters upper case.
* The null terminator is copied too.
* DONE, EXACT
*/
void kstrcpyup(char* dst, const char* src) {
while (*src) {
char c = *src;
if (c >= 'a' && c <= 'z') { // A-Z,a-z
c -= 0x20;
}
*dst = c;
dst++;
src++;
}
*dst = 0;
}
/*!
* Concatenate two strings. Src is added to dest.
* The new string is null terminated. No bounds checking is done.
* DONE, EXACT
*/
void kstrcat(char* dest, const char* src) {
// seek to end of first string
while (*dest) {
dest++;
}
// copy second string
while (*src) {
*dest = *src;
src++;
dest++;
}
// null terminate
*dest = 0;
}
/*!
* Concatenate two strings with a maximum length for the resulting string
* The maximum length should be larger than the length of the original string.
* The resulting string will be truncated when it reaches the given length.
* The null terminator is added, but doesn't count toward the length.
* DONE, EXACT
*/
void kstrncat(char* dest, const char* src, s32 count) {
// seek to null terminator of first string, count length
s32 i = 0;
while (*dest) {
dest++;
i++;
}
// append second string, not exceeding length
while (*src && (i < count)) {
*dest = *src;
src++;
dest++;
i++;
}
// null terminate
*dest = 0;
}
/*!
* Insert the pad char at the beginning of a string, count times.
* DONE, EXACT
*/
char* kstrinsert(char* str, char pad, s32 count) {
// shift string+null terminator to the right.
s32 len = strlen(str);
while (len > -1) {
str[len + count] = str[len];
len--;
}
// pad
len = 0;
while (len < count) {
str[len++] = pad;
}
return str;
}
/*!
* Get filename from path.
* This function is renamed to basename_goal so it doesn't conflict with "basename" that is
* already defined on my computer.
* For example:
* a/b/c.e will return c.e
* a\b\c.e will return c.e
* asdf.asdf will return asdf.asdf
* DONE, EXACT
*/
char* basename_goal(char* s) {
char* input = s;
char* pt = s;
// seek to end
for (;;) {
char c = *pt;
if (c) {
pt++;
} else {
break;
}
}
// back up...
for (;;) {
if (pt < input) {
return input;
}
pt--;
char c = *pt;
// until we hit a slash.
if (c == '\\' || c == '/') { // slashes
return pt + 1; // and return one past
}
}
}
/*!
* Turn file name into file's path.
* DONE, EXACT
*/
char* DecodeFileName(const char* name) {
char* result;
// names starting with $ are special:
if (name[0] == '$') {
if (!strncmp(name, "$TEXTURE/", 9)) {
result = MakeFileName(TX_PAGE_FILE_TYPE, name + 9, 0);
} else if (!strncmp(name, "$ART_GROUP/", 0xb)) {
result = MakeFileName(ART_GROUP_FILE_TYPE, name + 0xb, 0);
} else if (!strncmp(name, "$LEVEL/", 7)) {
int len = (int)strlen(name);
if (name[len - 4] == '.') {
result = MakeFileName(LEVEL_WITH_EXTENSION_FILE_TYPE, name + 7, 0);
} else {
// level files can omit a file type if desired
result = MakeFileName(LEVEL_FILE_TYPE, name + 7, 0);
}
} else if (!strncmp(name, "$DATA/", 6)) {
result = MakeFileName(DATA_FILE_TYPE, name + 6, 0);
} else if (!strncmp(name, "$CODE/", 6)) {
result = MakeFileName(CODE_FILE_TYPE, name + 6, 0);
} else if (!strncmp(name, "$RES/", 5)) {
result = MakeFileName(RES_FILE_TYPE, name + 5, 0);
} else {
printf("[ERROR] DecodeFileName: UNKNOWN FILE NAME %s\n", name);
result = nullptr;
}
} else {
// if no special prefix is given, assume $CODE
result = MakeFileName(CODE_FILE_TYPE, name, 0);
}
return result;
}
/*!
* Build a file name based on type.
* @param type: the file type.
* @param name: the file name
* @param new_string: if true, allocate a new global string for file name.
* will otherwise use a static buffer.
* DONE, Had unused int, char*, and MakeFileNameInfo params.
*/
char* MakeFileName(int type, const char* name, int new_string) {
// start with network filesystem
kstrcpy(buffer_633, "host:");
char* buf = strend(buffer_633);
// prefix to build directory
char prefix[64];
kstrcpy(prefix, FOLDER_PREFIX);
// build file name
if (type == LISTENER_TO_KERNEL_FILE_TYPE) {
kstrcpy(buf,
"kernel/LISTENERTOKERNEL"); // unused (I guess this is an old method to transfer data?)
} else if (type == KERNEL_TO_LISTENER_FILE_TYPE) {
kstrcpy(buf,
"kernel/KERNELTOLISTENER"); // unused (I guess this is an old method to transfer data?)
} else if (type == CODE_FILE_TYPE) {
sprintf(buf, "game/obj/%s.o", name); // game object file (CODE)
} else if (type == GAMEPAD_FILE_TYPE) {
sprintf(buffer_633, "pad:0"); // I guess the gamepad could be opened like a file at some point?
} else if (type == LISTENER_TO_KERNEL_LOCK_FILE_TYPE) {
kstrcpy(buf, "kernel/LISTENERTOKERNEL_LOCK"); // unused (likely used for LISTENERTOKERNEL?)
} else if (type == KERNEL_TO_LISTENER_LOCK_FILE_TYPE) {
kstrcpy(buf, "kernel/KERNELTOLISTENER_LOCK"); // unused (likley used for KERNELTOLISTENER?)
} else if (type == IOP_MODULE_FILE_TYPE) { // IOP module, overwrite the whole thing.
// this is unused, even by the remaining code to load IOP modules from the network.
// note this uses host0, which I believe is the PS2 TOOL's built in Linux SBC.
sprintf(buffer_633, "host0:/usr/local/sce/iop/modules/%s.irx", name);
} else if (type == DATA_FILE_TYPE) {
// GOAL object file, but containing data instead of code.
// likely packed by a tool that isn't the GOAL compiler.
sprintf(buf, "%sdata/%s.go", prefix, name);
} else if (type == TX_PAGE_FILE_TYPE) {
// Texture Page
// part of level files, so it has a version number.
sprintf(buf, "%sdata/texture-page%d/%s.go", prefix, TX_PAGE_VERSION, name);
} else if (type == JA_FILE_TYPE) {
// Art JA (joint animation? no idea)
// part of level files, so it has a version number
sprintf(buf, "%sdd_next/artdata%d/%s-ja.go", prefix, ART_FILE_VERSION, name);
} else if (type == JG_FILE_TYPE) {
// Art JG (joint group? no idea)
// part of level files, so it has a version number
sprintf(buf, "%sdd_next/artdata%d/%s-jg.go", prefix, ART_FILE_VERSION, name);
} else if (type == MA_FILE_TYPE) {
// Art MA (??)
// part of level files, so it has a version number
sprintf(buf, "%sdd_next/artdata%d/%s-ma.go", prefix, ART_FILE_VERSION, name);
} else if (type == MG_FILE_TYPE) {
// Art MG (??)
// part of level files, so it has a version number
sprintf(buf, "%sdd_next/artdata%d/%s-mg.go", prefix, ART_FILE_VERSION, name);
} else if (type == TG_FILE_TYPE) {
// unused, DATA TG file
sprintf(buf, "%sdata/%s-tg.go", prefix, name);
} else if (type == LEVEL_FILE_TYPE) {
// Level main file.
// part of level files, so it has a version number (a high one, 30!)
sprintf(buf, "%sdata/level%d/%s-bt.go", prefix, LEVEL_FILE_VERSION, name);
} else if (type == ART_GROUP_FILE_TYPE) {
// Level art group file.
// part of level files, so it has a version number
sprintf(buf, "%sdata/art-group%d/%s-ag.go", prefix, ART_FILE_VERSION, name);
} else if (type == VS_FILE_TYPE) {
// Level vs file, unused, unknown
// possibly early visibility file?
sprintf(buf, "%sdata/level%d/%s-vs.go", prefix, LEVEL_FILE_VERSION, name);
} else if (type == TX_FILE_TYPE) {
// Resource? TX file? some sort of texture?
sprintf(buf, "%sdata/res%d/%s-tx.go", prefix, RES_FILE_VERSION, name);
} else if (type == VS_BIN_FILE_TYPE) {
// level VS bin
// perhaps another format of early visibility data
sprintf(buf, "%sdata/level%d/%s-vs.bin", prefix, LEVEL_FILE_VERSION, name);
} else if (type == DGO_TXT_FILE_TYPE) {
// Text file in the DGO directory?
// Could have contained a list of files inside the DGO.
sprintf(buf, "%sdata/dgo%d/%s.txt", prefix, DGO_FILE_VERSION, name);
} else if (type == LEVEL_WITH_EXTENSION_FILE_TYPE) {
// Level file, but with an extension already on it.
sprintf(buf, "%sdata/level%d/%s", prefix, LEVEL_FILE_VERSION, name);
} else if (type == DATA_DGO_FILE_TYPE) {
// data DGO file (unused, all DGO/CGOs loaded through IOP)
sprintf(buf, "%sdata/dgo%d/%s.dgo", prefix, DGO_FILE_VERSION, name);
} else if (type == GAME_DGO_FILE_TYPE) {
// game DGO file (unused, all DGO/CGOs loaded through IOP)
sprintf(buf, "game/dgo%d/%s.dgo", DGO_FILE_VERSION, name);
} else if (type == DATA_CGO_FILE_TYPE) {
// data CGO file (unused, all DGO/CGOs loaded through IOP)
sprintf(buf, "%sdata/dgo%d/%s.cgo", prefix, DGO_FILE_VERSION, name);
} else if (type == GAME_CGO_FILE_TYPE) {
// game CGO file (unused, all DGO/CGOs loaded through IOP)
sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name);
} else if (type == CNT_FILE_TYPE) {
// game cnt file (continue point?)
sprintf(buf, "%sdata/res%d/game-cnt.go", prefix, RES_FILE_VERSION);
} else if (type == RES_FILE_TYPE) {
// RES go file?
sprintf(buf, "%sdata/res%d/%s.go", prefix, RES_FILE_VERSION, name);
} else if (type == REFPLANT_FILE_TYPE) {
// REFPLANT? no idea
static char nextDir[] = "/";
sprintf(buf, "%sconfig_data/refplant/%s", nextDir, name);
} else {
printf("UNKNOWN FILE TYPE %d\n", type);
}
char* result;
if (!new_string) {
// return pointer to static filename buffer
result = buffer_633;
} else {
// or create a new string on the global heap.
int l = (int)strlen(buffer_633);
result = (char*)kmalloc(kglobalheap, l + 1, 0, "filename").c();
kstrcpy(result, buffer_633);
}
return result;
}
/*!
* Does the file exist? No. It doesn't.
* @return 0 always, even if the file exists.
* DONE, EXACT, UNUSED
*/
u32 FileExists(const char* name) {
(void)name;
return 0;
}
/*!
* Does nothing. Likely is supposed to delete a file.
* @param name
* DONE, EXACT, UNUSED
*/
void FileDelete(const char* name) {
(void)name;
}
/*!
* Does nothing. Likely is supposed to copy a file.
* @param a
* @param b
* DONE, EXACT, UNUSED
*/
void FileCopy(const char* a, const char* b) {
(void)a;
(void)b;
}
/*!
* Determine the file length in bytes.
* DONE, EXACT
*/
s32 FileLength(char* filename) {
s32 fd = sceOpen(filename, SCE_RDONLY);
if (fd < 0) {
MsgErr("dkernel: file length !open \'%s\' (%d)\n", filename, fd);
sceClose(fd);
return 0xfffffffb;
} else {
s32 rv = sceLseek(fd, 0, SCE_SEEK_END);
sceClose(fd);
return rv;
}
}
/*!
* Load a file into memory
* @param name : file name
* @param heap : heap to allocate into, if memory is null
* @param memory : memory to load into. If null, allocates on the given kheap (with 64 extra bytes)
* @param malloc_flags : flags for the kmalloc
* @param size_out : file size is written here, if it's not null
* @return pointer to file data
* DONE, EXACT
*/
Ptr<u8> FileLoad(char* name, Ptr<kheapinfo> heap, Ptr<u8> memory, u32 malloc_flags, s32* size_out) {
s32 fd = sceOpen(name, SCE_RDONLY);
if (fd < 0) {
MsgErr("dkernel: file read !open \'%s\' (%d)\n", name, fd);
sceClose(fd);
return Ptr<u8>(0xfffffffb);
}
// determine size
s32 initial_pos = sceLseek(fd, 0, SCE_SEEK_CUR);
s32 size = sceLseek(fd, 0, SCE_SEEK_END);
sceLseek(fd, initial_pos, SCE_SEEK_SET);
if (size > 0) {
if (memory.offset == 0) {
memory = kmalloc(heap, size + 0x40, malloc_flags, name);
}
if (memory.offset == 0) {
MsgErr("dkernel: mem full for file read: '%s' (%d bytes)\n", name, size);
return Ptr<u8>(0xfffffffd);
}
s32 read_amount = sceRead(fd, memory.c(), size);
if (read_amount == size) {
sceClose(fd);
if (size_out)
*size_out = size;
return memory;
} else {
MsgErr("dkernel: can't read full file (%d of %d): '%s'\n", read_amount, size, name);
sceClose(fd);
return Ptr<u8>(0xfffffffb);
}
} else {
return Ptr<u8>(0);
}
}
/*!
* Write a file.
* DONE, EXACT
*/
s32 FileSave(char* name, u8* data, s32 size) {
s32 fd = sceOpen(name, SCE_WRONLY | SCE_TRUNC | SCE_CREAT);
if (fd < 0) {
MsgErr("dkernel: file write !open '%s'\n", name);
sceClose(fd);
return 0xfffffffa;
}
if (size != 0) {
s32 written = sceWrite(fd, data, size);
if (written != size) {
MsgErr("dkernel: can't write full file '%s'\n", name);
sceClose(fd);
return 0xfffffffa;
}
}
sceClose(fd);
return 0;
}
+71
View File
@@ -0,0 +1,71 @@
/*!
* @file fileio.h
* GOAL Low-Level File I/O and String Utilities
*/
#ifndef RUNTIME_FILEIO_H
#define RUNTIME_FILEIO_H
#include "common/common_types.h"
#include "Ptr.h"
#include "kmalloc.h"
// GOAL File Types
enum GoalFileType {
LISTENER_TO_KERNEL_FILE_TYPE = 1,
KERNEL_TO_LISTENER_FILE_TYPE = 2,
CODE_FILE_TYPE = 3,
GAMEPAD_FILE_TYPE = 4,
LISTENER_TO_KERNEL_LOCK_FILE_TYPE = 5,
KERNEL_TO_LISTENER_LOCK_FILE_TYPE = 6,
IOP_MODULE_FILE_TYPE = 8,
DATA_FILE_TYPE = 0x20,
TX_PAGE_FILE_TYPE = 0x21,
JA_FILE_TYPE = 0x22,
JG_FILE_TYPE = 0x23,
MA_FILE_TYPE = 0x24,
MG_FILE_TYPE = 0x25,
TG_FILE_TYPE = 0x26,
LEVEL_FILE_TYPE = 0x27,
ART_GROUP_FILE_TYPE = 0x30,
VS_FILE_TYPE = 0x31,
TX_FILE_TYPE = 0x32,
VS_BIN_FILE_TYPE = 0x33,
DGO_TXT_FILE_TYPE = 0x34,
LEVEL_WITH_EXTENSION_FILE_TYPE = 0x35,
DATA_DGO_FILE_TYPE = 0x36,
GAME_DGO_FILE_TYPE = 0x37,
DATA_CGO_FILE_TYPE = 0x38,
GAME_CGO_FILE_TYPE = 0x39,
CNT_FILE_TYPE = 0x3a,
RES_FILE_TYPE = 0x3b,
REFPLANT_FILE_TYPE = 0x301,
};
constexpr char FOLDER_PREFIX[] = "";
constexpr u32 ART_FILE_VERSION = 6;
constexpr u32 LEVEL_FILE_VERSION = 30;
constexpr u32 DGO_FILE_VERSION = 1;
constexpr u32 RES_FILE_VERSION = 1;
constexpr u32 TX_PAGE_VERSION = 7;
char* strend(char* str);
u32 ReadHufWord(u8** loc_ptr);
void kstrcpy(char* dst, const char* src);
void kstrcpyup(char* dst, const char* src);
void kstrcat(char* dest, const char* src);
void kstrncat(char* dest, const char* src, s32 count);
char* kstrinsert(char* str, char pad, s32 count);
char* basename_goal(char* s);
char* DecodeFileName(const char* name);
char* MakeFileName(int type, const char* name, int new_string);
u32 FileExists(const char* name);
void FileDelete(const char* name);
void FileCopy(const char* a, const char* b);
s32 FileLength(char* filename);
Ptr<u8> FileLoad(char* name, Ptr<kheapinfo> heap, Ptr<u8> memory, u32 malloc_flags, s32* size_out);
s32 FileSave(char* name, u8* data, s32 size);
void fileio_init_globals();
#endif // RUNTIME_FILEIO_H
+148
View File
@@ -0,0 +1,148 @@
/*!
* @file kboot.cpp
* GOAL Boot. Contains the "main" function to launch GOAL runtime
* DONE!
*/
#include <unistd.h>
#include <cstring>
#include "common/common_types.h"
#include "game/sce/libscf.h"
#include "kboot.h"
#include "kmachine.h"
#include "kscheme.h"
#include "ksocket.h"
#include "klisten.h"
using namespace ee;
// Level to load on boot
char DebugBootLevel[64];
// Pass to GOAL kernel on boot
char DebugBootMessage[64];
// game configuration
MasterConfig masterConfig;
// Set to 1 to kill GOAL kernel
u32 MasterExit;
// Set to 1 to enable debug heap
u32 MasterDebug;
// Set to 1 to load debug code
u32 DebugSegment;
// Set to 1 to load game engine after boot automatically
u32 DiskBoot;
void kboot_init_globals() {
strcpy(DebugBootLevel, "#f"); // no specified level
strcpy(DebugBootMessage, "play"); // play mode, the default retail mode
MasterExit = 0;
MasterDebug = 1;
DebugSegment = 1;
DiskBoot = 0;
memset(&masterConfig, 0, sizeof(MasterConfig));
}
/*!
* Launch the GOAL Kernel (EE).
* DONE!
* See InitParms for launch argument details.
* @param argc : argument count
* @param argv : argument list
* @return 0 on success, otherwise failure.
*
* CHANGES:
* Added InitParms call to handle command line arguments
* Removed hard-coded debug mode disable
* Renamed from `main` to `goal_main`
* Add call to sceDeci2Reset when GOAL shuts down.
*/
s32 goal_main(int argc, const char* const* argv) {
// Initialize global variables based on command line parameters
// This call is not present in the retail version of the game
// but the function is, and it likely goes here.
InitParms(argc, argv);
// Initialize CRC32 table for string hashing
init_crc();
// NTSC V1, NTSC v2, PAL CD Demo, PAL Retail
// Set up game configurations
masterConfig.aspect = (u16)sceScfGetAspect();
masterConfig.language = (u16)sceScfGetLanguage();
masterConfig.inactive_timeout = 0;
masterConfig.timeout = 0;
masterConfig.volume = 100;
// Set up language configuration
if (masterConfig.language == SCE_SPANISH_LANGUAGE) {
masterConfig.language = (u16)Language::Spanish;
} else if (masterConfig.language == SCE_FRENCH_LANGUAGE) {
masterConfig.language = (u16)Language::French;
} else if (masterConfig.language == SCE_GERMAN_LANGUAGE) {
masterConfig.language = (u16)Language::German;
} else if (masterConfig.language == SCE_ITALIAN_LANGUAGE) {
masterConfig.language = (u16)Language::Italian;
} else {
// pick english by default, if language is not supported.
masterConfig.language = (u16)Language::English;
}
// Set up aspect ratio override in demo
if (!strcmp(DebugBootMessage, "demo") || !strcmp(DebugBootMessage, "demo-shared")) {
masterConfig.aspect = SCE_ASPECT_FULL;
}
// In retail game, disable debugging modes, and force on DiskBoot
// MasterDebug = 0;
// DiskBoot = 1;
// DebugSegment = 0;
// Launch GOAL!
if (InitMachine() >= 0) { // init kernel
KernelCheckAndDispatch(); // run kernel
ShutdownMachine(); // kernel died, we should too.
}
return 0;
}
/*!
* Main loop to dispatch the GOAL kernel.
*/
void KernelCheckAndDispatch() {
while (!MasterExit) {
// try to get a message from the listener, and process it if needed
Ptr<char> new_message = WaitForMessageAndAck();
if (new_message.offset) {
ProcessListenerMessage(new_message);
}
// remember the old listener function
auto old_listener = ListenerFunction->value;
// dispatch the kernel
//(**kernel_dispatcher)();
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
ClearPending();
// if the listener function changed, it means the kernel ran it, so we should notify compiler.
if (MasterDebug && ListenerFunction->value != old_listener) {
SendAck();
}
usleep(1000); // todo - remove this
}
}
/*!
* Stop running the GOAL Kernel.
* DONE, EXACT
*/
void KernelShutdown() {
MasterExit = 1; // GOAL Kernel Dispatch loop will stop now.
}
+76
View File
@@ -0,0 +1,76 @@
/*!
* @file kboot.h
* GOAL Boot. Contains the "main" function to launch GOAL runtime.
*/
#ifndef RUNTIME_KBOOT_H
#define RUNTIME_KBOOT_H
#include "common/common_types.h"
//! Supported languages.
enum class Language {
English = 0,
French = 1,
German = 2,
Spanish = 3,
Italian = 4,
Japanese = 5,
UK_English = 6,
// uk english?
};
struct MasterConfig {
u16 language; //! GOAL language 0
u16 aspect; //! SCE_ASPECT 2
u16 disable_game; // 4
u16 inactive_timeout; // todo 6
u16 timeout; // todo 8
u16 volume; // todo 12
};
// Level to load on boot
extern char DebugBootLevel[64];
// Pass to GOAL kernel on boot
extern char DebugBootMessage[64];
// Set to 1 to kill GOAL kernel
extern u32 MasterExit;
// Set to 1 to enable debug heap
extern u32 MasterDebug;
// Set to 1 to load debug code
extern u32 DebugSegment;
// Set to 1 to load game engine after boot automatically
extern u32 DiskBoot;
extern MasterConfig masterConfig;
/*!
* Initialize global variables for kboot
*/
void kboot_init_globals();
/*!
* Launch the GOAL Kernel (EE).
* See InitParms for launch argument details.
* @param argc : argument count
* @param argv : argument list
* @return 0 on success, otherwise failure.
*/
s32 goal_main(int argc, const char* const* argv);
/*!
* Run the GOAL Kernel.
*/
void KernelCheckAndDispatch();
/*!
* Stop running the GOAL Kernel.
*/
void KernelShutdown();
#endif // RUNTIME_KBOOT_H
+360
View File
@@ -0,0 +1,360 @@
/*!
* @file kdgo.cpp
* Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading.
* DONE!
*/
#include <cstring>
#include "kdgo.h"
#include "kprint.h"
#include "kmalloc.h"
#include "fileio.h"
#include "klink.h"
#include "game/sce/sif_ee.h"
#include "game/common/dgo_rpc_types.h"
#include "game/common/player_rpc_types.h"
#include "game/common/ramdisk_rpc_types.h"
#include "game/common/loader_rpc_types.h"
#include "game/common/play_rpc_types.h"
using namespace ee;
sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call.
u16 x[8]; //! stupid temporary for storing a message
u32 sShowStallMsg; //! setting to show a "stalled on iop" message
u32 sMsgNum; //! Toggle for double buffered message sending.
RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
void kdgo_init_globals() {
memset(cd, 0, sizeof(cd));
memset(x, 0, sizeof(x));
sShowStallMsg = 1;
sLastMsg = nullptr;
memset(sMsg, 0, sizeof(sMsg));
}
/*!
* Call the given RPC with the given function number and buffers.
*/
s32 RpcCall(s32 rpcChannel,
u32 fno,
bool async,
void* sendBuff,
s32 sendSize,
void* recvBuff,
s32 recvSize) {
return sceSifCallRpc(&cd[rpcChannel], fno, async, sendBuff, sendSize, recvBuff, recvSize, nullptr,
nullptr);
}
/*!
* GOAL Wrapper for RpcCall.
*/
u64 RpcCall_wrapper(s32 rpcChannel,
u32 fno,
u32 async,
u64 send_buff,
s32 send_size,
u64 recv_buff,
s32 recv_size) {
return sceSifCallRpc(&cd[rpcChannel], fno, async, Ptr<u8>(send_buff).c(), send_size,
Ptr<u8>(recv_buff).c(), recv_size, nullptr, nullptr);
}
/*!
* Check if the given RPC is busy, by channel.
*/
u32 RpcBusy(s32 channel) {
return sceSifCheckStatRpc(&cd[channel].rpcd);
}
/*!
* Wait for an RPC to not be busy. Prints a stall message if sShowStallMsg is true and we have
* to wait on the IOP. Stalling here is bad because it means the rest of the game can't run.
*/
void RpcSync(s32 channel) {
if (RpcBusy(channel)) {
if (sShowStallMsg) {
Msg(6, "STALL: [kernel] waiting for IOP on RPC port #%d\n", channel);
}
while (RpcBusy(channel)) {
// an attempt to avoid spamming SIF?
u32 i = 0;
while (i < 1000) {
i++;
}
}
}
}
/*!
* Setup an RPC.
*/
u32 RpcBind(s32 channel, s32 id) {
while (true) {
if (sceSifBindRpc(&cd[channel], id, 1) < 0) {
MsgErr("Error: RpcBind failed on port #%d [%4.4X]\n", channel, id);
return 1;
}
Msg(6, "kernel: RPC port #%d started [%4.4X]\n", channel, id);
// FlushCache(0);
// this was not optimized out in Jak 1, but is _almost_ optimized out in Jak 2 and later.
u32 i = 0;
while (i < 10000) {
i++;
}
if (cd[channel].serve) {
break;
}
Msg(6, "kernel: RPC port #%d not responding.\n", channel);
// it might seem like looping here is a bad idea (unclear if sceSifBindRpc can be called
// multiple times!) but this actually happens sometimes, at least on development hardware!
// (also, it's not clear that the "serve" field having data in it really means anything - maybe
// the sceSifBindRpc doesn't wait for the connection to be fully set up? This seems likely
// because they had to put that little delay in there before checking.)
}
return 0;
}
/*!
* Setup all RPCs
*/
u32 InitRPC() {
if (!RpcBind(PLAYER_RPC_CHANNEL, PLAYER_RPC_ID) && !RpcBind(LOADER_RPC_CHANNEL, LOADER_RPC_ID) &&
!RpcBind(RAMDISK_RPC_CHANNEL, RAMDISK_RPC_ID) && !RpcBind(DGO_RPC_CHANNEL, DGO_RPC_ID) &&
!RpcBind(4, 0xdeb5) && !RpcBind(PLAY_RPC_CHANNEL, PLAY_RPC_ID)) {
return 0;
}
printf("Entering endless loop ... please wait\n");
for (;;) {
}
}
/*!
* Send a message to the IOP to stop it.
*/
void StopIOP() {
x[2] = 0x14; // todo - this type and message
RpcSync(PLAYER_RPC_CHANNEL);
RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0);
printf("IOP shut down\n");
// sceDmaSync(0x10009000, 0, 0);
printf("DMA shut down\n");
}
/*!
* Send message to IOP to start loading a new DGO file
* Uses a double-buffered message buffer
* @param name: the name of the DGO file
* @param buffer1 : one of the two file loading buffers
* @param buffer2 : the other of the two file loading buffers
* @param currentHeap : the current heap (for loading directly into the heap).
*
* DONE,
* MODIFIED : Added print statement to indicate when DGO load starts.
*/
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
u8 msgID = sMsgNum;
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
sMsg[msgID].buffer2 = buffer2.offset;
// also give a heap pointer so it can load the last object file directly into the heap to save the
// precious time.
sMsg[msgID].buffer_heap_top = currentHeap.offset;
// file name
strcpy(sMsg[msgID].name, name);
printf("[Begin Loading DGO RPC] %s, 0x%x, 0x%x, 0x%x\n", name, buffer1.offset, buffer2.offset,
currentHeap.offset);
// this RPC will return once we have loaded the first object file.
// but we call async, so we don't block here.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
sizeof(RPC_Dgo_Cmd));
sLastMsg = mess;
}
/*!
* Get the next object in the DGO. Will block until something is loaded.
* @param lastObjectFlag: will get set to 1 if this is the last object.
*
* DONE,
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
*/
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
*lastObjectFlag = 1;
// Wait for RPC function to respond. This will happen once the first object file is loaded.
RpcSync(DGO_RPC_CHANNEL);
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
// no pending message.
sLastMsg = nullptr;
} else {
// I don't see how this case can happen unless there's a bug. The game does check for this and
// nothing in this case. (maybe from GOAL this can happen?)
printf("last message not set!\n");
}
return buffer;
}
/*!
* Instruct the IOP to continue loading the next object.
* Only should be called once it is safe to overwrite the previous.
* @param heapPtr : pointer to heap so the IOP could try to load directly into a heap if it wants.
* This should be updated after each object file load to make sure the IOP knows the exact location
* of the end of the GOAL heap data.
* DONE,
* EXACT
*/
void ContinueLoadingDGO(Ptr<u8> heapPtr) {
u32 msgID = sMsgNum;
RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1;
sendBuff->result = DGO_RPC_RESULT_INIT;
sMsg[msgID].buffer1 = 0;
sMsg[msgID].buffer2 = 0;
sMsg[msgID].buffer_heap_top = heapPtr.offset;
// the IOP will wait for this RpcCall to continue the DGO state machine.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_NEXT_FNO, true, sendBuff, sizeof(RPC_Dgo_Cmd), sendBuff,
sizeof(RPC_Dgo_Cmd));
// this async RPC call will complete when the next object is fully loaded.
sLastMsg = sendBuff;
}
/*!
* Load the TEST.DGO file.
* Presumably used for debugging DGO loads.
* We don't have the TEST.DGO file, so this isn't very useful.
*
* DONE,
* EXACT,
* UNUSED
*/
void LoadDGOTest() {
u32 lastObject = 0;
// backup show stall message and set it to false
// EE will be loading DGO in a loop, so it will always be stalling
// no need to print it.
u32 lastShowStall = sShowStallMsg;
sShowStallMsg = 0;
// pick somewhat arbitrary memory to load the DGO into
BeginLoadingDGO("TEST.DGO", Ptr<u8>(0x4800000), Ptr<u8>(0x4c00000), Ptr<u8>(0x4000000));
while (true) {
// keep trying to load.
Ptr<u8> dest_buffer(0);
do {
dest_buffer = GetNextDGO(&lastObject);
} while (!dest_buffer.offset);
// print the name of the object we loaded, its destination, and its size.
Msg(6, "Loaded %s at %8.8X length %d\n", (dest_buffer + 4).cast<char>().c(), dest_buffer.offset,
*(dest_buffer.cast<u32>()));
if (lastObject) {
break;
}
// okay to load the next one
ContinueLoadingDGO(Ptr<u8>(0x4000000));
}
sShowStallMsg = lastShowStall;
}
/*!
* Load and link a DGO file.
* This does not use the mutli-threaded linker and will block until the entire file is done.
*/
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) {
auto name = Ptr<char>(name_gstr + 4).c();
auto heap = Ptr<kheapinfo>(heap_info);
load_and_link_dgo_from_c(name, heap, flag, buffer_size);
}
/*!
* Load and link a DGO file.
* This does not use the mutli-threaded linker and will block until the entire file is done.e
*/
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize) {
printf("[Load and Link DGO From C] %s\n", name);
u32 oldShowStall = sShowStallMsg;
// remember where the heap top point is so we can clear temporary allocations
auto oldHeapTop = heap->top;
// allocate temporary buffers from top of the given heap
// align 64 for IOP DMA
// note: both buffers named dgo-buffer-2
auto buffer2 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
// build filename. If no extension is given, default to CGO.
char fileName[16];
kstrcpyup(fileName, name);
if (fileName[strlen(fileName) - 4] != '.') {
strcat(fileName, ".CGO");
}
// no stall messages, as this is a blocking load and when spending 100% CPU time on linking,
// the linker can beat the DVD drive.
sShowStallMsg = 0;
// start load on IOP.
BeginLoadingDGO(
fileName, buffer1, buffer2,
Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0)); // 64-byte aligned for IOP DMA
u32 lastObjectLoaded = 0;
while (!lastObjectLoaded) {
// check to see if next object is loaded (I believe it always is?)
auto dgoObj = GetNextDGO(&lastObjectLoaded);
if (!dgoObj.offset) {
continue;
}
// if we're on the last object, it is loaded at cheap->current. So we can safely reset the two
// dgo-buffer allocations. We do this _before_ we link! This way, the last file loaded has more
// heap available, which is important when we need to use the entire memory.
if (lastObjectLoaded) {
heap->top = oldHeapTop;
}
// determine the size and name of the object we got
auto obj = dgoObj + 0x40; // seek past dgo object header
u32 objSize = *(dgoObj.cast<u32>()); // size from object's link block
char objName[64];
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
printf("[link and exec] %s %d\n", objName, lastObjectLoaded);
link_and_exec(obj, objName, objSize, heap, linkFlag); // link now!
// inform IOP we are done
if (!lastObjectLoaded) {
ContinueLoadingDGO(Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0));
}
}
sShowStallMsg = oldShowStall;
}
+30
View File
@@ -0,0 +1,30 @@
/*!
* @file kdgo.h
* Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading.
* DONE!
*/
#ifndef JAK_V2_KDGO_H
#define JAK_V2_KDGO_H
#include "common/common_types.h"
#include "Ptr.h"
#include "kmalloc.h"
void kdgo_init_globals();
u32 InitRPC();
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void StopIOP();
u64 RpcCall_wrapper(s32 rpcChannel,
u32 fno,
u32 async,
u64 send_buff,
s32 send_size,
u64 recv_buff,
s32 recv_size);
u32 RpcBusy(s32 channel);
void LoadDGOTest();
#endif // JAK_V2_KDGO_H
+222
View File
@@ -0,0 +1,222 @@
/*!
* @file kdsnetm.cpp
* Low-level DECI2 wrapper for ksocket
* DONE!
*/
#include <cstring>
#include <cstdio>
#include <cassert>
#include "game/sce/deci2.h"
#include "game/system/deci_common.h" // todo, reorganize to avoid this include
#include "kdsnetm.h"
#include "kprint.h"
using namespace ee;
/*!
* Current state of the GOAL Protocol
*/
GoalProtoBlock protoBlock;
/*!
* Initialize global variables for kdsnetm
*/
void kdsnetm_init_globals() {
protoBlock.reset();
}
/*!
* Register GOAL DECI2 Protocol Driver with DECI2 service
* DONE, EXACT
*/
void InitGoalProto() {
protoBlock.socket = sceDeci2Open(DECI2_PROTOCOL, &protoBlock, GoalProtoHandler);
if (protoBlock.socket < 0) {
MsgErr("gproto: open proto error\n");
} else {
protoBlock.send_buffer = nullptr;
protoBlock.receive_buffer = MessBufArea.cast<GoalMessageHeader>().c();
protoBlock.send_status = -1;
protoBlock.last_receive_size = -1;
protoBlock.receive_progress = 0;
protoBlock.deci2count.offset = 0;
Msg(6, "gproto: proto open at socket %d\n", protoBlock.socket);
}
}
/*!
* Close the DECI2 Protocol Driver
* DONE, EXACT
*/
void ShutdownGoalProto() {
if (protoBlock.socket > 0) {
sceDeci2Close(protoBlock.socket);
}
}
/*!
* Handle a DECI2 Protocol Event for the GOAL Proto.
* Called by the DECI2 Protocol driver
* DONE, added print statements on errors for debugging, EI and SYNC at the end were removed
*/
void GoalProtoHandler(int event, int param, void* opt) {
// verify we got the correct opt pointer. It's not clear why the opt pointer is used
// like this?
GoalProtoBlock* pb = (GoalProtoBlock*)opt;
if (&protoBlock != pb) {
Msg(6, "gproto: BAD OPT POINTER PASSED IN!!!!\n"); // this print statement is in the game.
pb = &protoBlock;
}
// increment deci2count, if it's set up
if (pb->deci2count.offset) {
*pb->deci2count = *pb->deci2count + 1;
}
// remember what event this is
pb->most_recent_event = event;
pb->most_recent_param = param;
switch (event) {
// get some data - param is the size
case DECI2_READ:
// sanity check the size
if (pb->receive_progress + param <= (int)DEBUG_MESSAGE_BUFFER_SIZE) {
// actually get data from DECI2
s32 received =
sceDeci2ExRecv(pb->socket, ((u8*)pb->receive_buffer) + pb->receive_progress, param);
if (received < 0) {
// receive failure
pb->last_receive_size = -1;
protoBlock.receive_progress = 0; // why use protoBlock instead of pb here?
printf("gproto: read error with sceDeci2ExRecv\n");
} else {
pb->receive_progress += received;
}
} else {
// size was too large
pb->last_receive_size = -1;
protoBlock.receive_progress = 0; // why use protoBlock here?
printf("gproto: read error, message too large!\n");
}
break;
// read is finished!
case DECI2_READDONE:
// set last_receive_size to indicate that there is a pending message in the buffer.
pb->last_receive_size = pb->receive_progress;
pb->receive_progress = 0;
break;
// send some data
case DECI2_WRITE: {
// note that we should not attempt to send more than 0xffff bytes at a time, or this will be
// wrong. This is correctly checked for prints, but not for outputs.
assert(pb->send_remaining < 0xffff);
// why and it with 0xffff? Seems like saturation would be better. Either way some data
// will be lost, so I guess it doesn't matter.
s32 sent = sceDeci2ExSend(pb->socket, (void*)pb->send_ptr, pb->send_remaining & 0xffff);
if (sent < 0) {
// if we got an error, put it in send status, signaling a send error (negative)
pb->send_status = sent;
} else {
// otherwise don't touch send status, leave it positive to indicate we're still sending
pb->send_ptr += sent;
pb->send_remaining -= sent;
}
} break;
// done sending!
case DECI2_WRITEDONE:
if (pb->send_remaining <= 0) {
// if we've send everything we want, set status to zero to indicate success
pb->send_status = 0;
} else {
// otherwise, set send status to a negative number (the negative absolute value of
// remaining)
s32 a = pb->send_remaining;
if (a < 0) {
a = -a;
}
pb->send_status = -a;
}
break;
case DECI2_CHSTATUS:
break;
// other events are undefined, so we just error.
default:
pb->last_receive_size = -1;
break;
}
}
/*!
* Low level DECI2 send
* Will block until send is complete.
* DONE, original version used an uncached address and had a FlushCache call, which were both
* removed
*/
s32 SendFromBufferD(s32 msg_kind, u64 p2, char* data, s32 size) {
// wait for send to finish or error first...
while (protoBlock.send_status > 0) {
// on actual PS2, the kernel will run this in another thread.
LIBRARY_sceDeci2_run_sends();
}
// retry at most 10 times until we complete without an error.
for (s32 i = 0; i < 10; i++) {
// or'd with 0x20000000 to get noncache version
GoalMessageHeader* header = (GoalMessageHeader*)(data - sizeof(GoalMessageHeader));
protoBlock.send_remaining = size + sizeof(GoalMessageHeader);
protoBlock.send_buffer = header;
protoBlock.send_ptr = (u8*)header;
protoBlock.send_status = size + sizeof(GoalMessageHeader);
// FlushCache(0);
// set DECI2 message header
header->deci2_hdr.len = protoBlock.send_remaining;
header->deci2_hdr.rsvd = 0;
header->deci2_hdr.proto = DECI2_PROTOCOL;
header->deci2_hdr.src = 'E'; // from EE
header->deci2_hdr.dst = 'H'; // to HOST
// set GOAL message header
header->msg_kind = (u16)msg_kind;
header->u6 = 0;
header->msg_size = size;
header->msg_id = p2;
// start send!
auto rv = sceDeci2ReqSend(protoBlock.socket, header->deci2_hdr.dst);
if (rv < 0) {
printf("1sceDeci2ReqSend fail, reason code = %08x\n", rv);
return 0xfffffffa;
}
// wait for send to complete or error.
while (protoBlock.send_status > 0) {
LIBRARY_sceDeci2_run_sends();
}
// if send completes, exit. Otherwise if there's an error, just try again.
if (protoBlock.send_status == 0) {
break;
}
}
return 0;
}
/*!
* Print GOAL Protocol status
*/
void GoalProtoStatus() {
Msg(6, "gproto: got %d %d\n", protoBlock.most_recent_event, protoBlock.most_recent_param);
Msg(6, "gproto: %d %d\n", protoBlock.last_receive_size, protoBlock.send_remaining);
}
+86
View File
@@ -0,0 +1,86 @@
/*!
* @file kdsnetm.h
* Low-level DECI2 wrapper for ksocket
* DONE!
*/
#ifndef JAK_KDSNETM_H
#define JAK_KDSNETM_H
#include "Ptr.h"
#include "common/listener_common.h"
struct GoalMessageHeader {
Deci2Header deci2_hdr;
u16 msg_kind;
u16 u6;
u32 msg_size;
u64 msg_id;
};
constexpr u16 DECI2_PROTOCOL = 0xe042;
struct GoalProtoBlock {
s32 socket = 0;
GoalMessageHeader* send_buffer = nullptr;
GoalMessageHeader* receive_buffer = nullptr;
u8* send_ptr = nullptr;
s32 send_remaining = 0;
s32 send_status =
0; // positive means send in progress, negative means send error, 0 means complete.
// size of pending receive to process.
s32 last_receive_size = 0;
s32 receive_progress = 0;
u32 most_recent_event = 0;
u32 most_recent_param = 0;
u32 msg_kind = 0;
u64 msg_id = 0;
Ptr<s32> deci2count;
void reset() { *this = GoalProtoBlock(); }
};
/*!
* Current state of the GOAL Protocol
*/
extern GoalProtoBlock protoBlock;
/*!
* Initialize global variables for kdsnetm
*/
void kdsnetm_init_globals();
/*!
* Register GOAL DECI2 Protocol Driver with DECI2 service
* DONE, EXACT
*/
void InitGoalProto();
/*!
* Close the DECI2 Protocol Driver
* DONE, EXACT
*/
void ShutdownGoalProto();
/*!
* Handle a DECI2 Protocol Event for the GOAL Proto.
* Called by the DECI2 Protocol driver
* DONE, EXACT
*/
void GoalProtoHandler(int event, int param, void* data);
/*!
* Low level DECI2 send
* Will block until send is complete.
* DONE, original version used an uncached address and had a FlushCache call, which were both
* removed
*/
s32 SendFromBufferD(s32 p1, u64 p2, char* data, s32 size);
/*!
* Print GOAL Protocol status
*/
void GoalProtoStatus();
#endif // JAK_KDSNETM_H
+538
View File
@@ -0,0 +1,538 @@
/*!
* @file klink.cpp
* GOAL Linker for x86-64
* Note - this is significantly different from the MIPS linker because the object file format is
* different.
* DONE!
*/
#include <cstring>
#include <cassert>
#include "klink.h"
#include "fileio.h"
#include "kscheme.h"
#include "kboot.h"
#include "kprint.h"
#include "common/symbols.h"
namespace {
// turn on printf's for debugging linking issues.
constexpr bool link_debug_printfs = false;
} // namespace
// space to store a single in-progress linking state.
link_control saved_link_control;
// pointer to GOAL *ultimate-memcpy*, if its loaded.
Ptr<Function> gfunc_774;
void klink_init_globals() {
saved_link_control.reset();
gfunc_774.offset = 0;
}
/*!
* Initialize the link control.
*/
void link_control::begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
// save data from call to begin
m_object_data = object_file;
kstrcpy(m_object_name, name);
m_object_size = size;
m_heap = heap;
m_flags = flags;
// initialize link control
m_entry.offset = 0;
m_heap_top = m_heap->top;
m_keep_debug = false;
if (link_debug_printfs) {
char* goal_name = object_file.cast<char>().c();
printf("link %s\n", goal_name);
printf("link_control::begin %c%c%c%c\n", goal_name[0], goal_name[1], goal_name[2],
goal_name[3]);
}
// points to the beginning of the linking data
m_link_block_ptr = object_file + BASIC_OFFSET;
m_code_size = 0;
m_code_start = object_file;
m_state = 0;
m_segment_process = 0;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (link_debug_printfs) {
printf("Object file header:\n");
printf(" GOAL ver %d.%d obj %d len %d\n", ofh->goal_version_major, ofh->goal_version_minor,
ofh->object_file_version, ofh->link_block_length);
printf(" segment count %d\n", ofh->segment_count);
for (int i = 0; i < N_SEG; i++) {
printf(" seg %d link 0x%04x, 0x%04x data 0x%04x, 0x%04x\n", i, ofh->link_infos[i].offset,
ofh->link_infos[i].size, ofh->code_infos[i].offset, ofh->code_infos[i].size);
}
}
m_version = ofh->object_file_version;
if (ofh->object_file_version < 4) {
// three segment file
// seek past the header
m_object_data.offset += ofh->link_block_length;
// todo, set m_code_size
if (m_link_block_ptr.offset < m_heap->base.offset ||
m_link_block_ptr.offset >= m_heap->top.offset) {
// the link block is outside our heap, or in the top of our heap. It's somebody else's
// problem.
if (link_debug_printfs) {
printf("Link block somebody else's problem\n");
}
if (m_heap->base.offset <= m_object_data.offset && // above heap base
m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?)
m_object_data.offset < m_heap->current.offset) { // less than heap current
if (link_debug_printfs) {
printf("Code block in the heap, kicking it out for copy into heap\n");
}
m_heap->current = m_object_data;
}
} else {
// in our heap, we need to move it so we can free up its space later on
if (link_debug_printfs) {
printf("Link block needs to be moved!\n");
}
// allocate space for a new one
auto new_link_block = kmalloc(m_heap, ofh->link_block_length, KMALLOC_TOP, "link-block");
auto old_link_block = m_link_block_ptr - BASIC_OFFSET;
// copy it
ultimate_memcpy(new_link_block.c(), old_link_block.c(), ofh->link_block_length);
m_link_block_ptr = new_link_block + BASIC_OFFSET;
// if we can save some memory here
if (old_link_block.offset < m_heap->current.offset) {
if (link_debug_printfs) {
printf("Kick out old link block\n");
}
m_heap->current = old_link_block;
}
}
} else {
printf("UNHANDLED OBJECT FILE VERSION\n");
assert(false);
}
if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) {
m_keep_debug = true;
}
}
/*!
* Make progress on linking.
*/
uint32_t link_control::work() {
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
DebugSegment = s7.offset + FIX_SYM_TRUE;
}
// set type tag of link block
*((m_link_block_ptr - 4).cast<u32>()) = *((s7 + FIX_SYM_LINK_BLOCK).cast<u32>());
uint32_t rv;
if (m_version == 3) {
rv = work_v3();
} else {
printf("UNHANDLED OBJECT FILE VERSION IN WORK!\n");
assert(false);
return 0;
}
DebugSegment = old_debug_segment;
return rv;
}
/*!
* Link type pointers for a single type in "v3 equivalent" link data
* Returns a pointer to the link table data after the typelinking data.
*/
uint32_t typelink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the name of the type
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
assert(seek < 256);
}
sym_name[seek] = 0;
seek++;
// determine the number of methods
uint8_t method_count = link.c()[seek++];
// intern the GOAL type, creating the vtable if it doesn't exist.
auto type_ptr = intern_type_from_c(sym_name, method_count);
// prepare to read the locations of the type pointers
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
// write the type pointers into memory
for (uint32_t i = 0; i < offset_count; i++) {
*(data + offsets.c()[i]).cast<int32_t>() = type_ptr.offset;
seek += 4;
}
return seek;
}
/*!
* Link symbols (both offsets and pointers) in "v3 equivalent" link data.
* Returns a pointer to the link table data after the linking data for this symbol.
*/
uint32_t symlink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the symbol name
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
assert(seek < 256);
}
sym_name[seek] = 0;
seek++;
// intern
auto sym = intern_from_c(sym_name);
int32_t sym_offset = sym.cast<u32>() - s7;
uint32_t sym_addr = sym.cast<u32>().offset;
// prepare to read locations of symbol links
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
for (uint32_t i = 0; i < offset_count; i++) {
uint32_t offset = offsets.c()[i];
seek += 4;
auto data_ptr = (data + offset).cast<int32_t>();
if (*data_ptr == -1) {
// a "-1" indicates that we should store the address.
*(data + offset).cast<int32_t>() = sym_addr;
} else {
// otherwise store the offset to st. Eventually this should become an s16 instead.
*(data + offset).cast<int32_t>() = sym_offset;
}
}
return seek;
}
/*!
* Link a single pointer.
*/
uint32_t cross_seg_dist_link_v3(Ptr<uint8_t> link,
ObjectFileHeader* ofh,
int current_seg,
int size) {
// target seg, dist into mine, dist into target, patch loc in mine
uint8_t target_seg = *link;
assert(target_seg < ofh->segment_count);
uint32_t* link_data = (link + 1).cast<uint32_t>().c();
int32_t mine = link_data[0] + ofh->code_infos[current_seg].offset;
int32_t tgt = link_data[1] + ofh->code_infos[target_seg].offset;
int32_t diff = tgt - mine;
uint32_t offset_of_patch = link_data[2] + ofh->code_infos[current_seg].offset;
// printf("link object in seg %d diff %d at %d (%d + %d)\n", target_seg, diff, offset_of_patch,
// link_data[2], ofh->code_infos[current_seg].offset);
// both 32-bit and 64-bit pointer links are supported, though 64-bit ones should disappear soon.
if (size == 4) {
*Ptr<int32_t>(offset_of_patch).c() = diff;
} else if (size == 8) {
*Ptr<int64_t>(offset_of_patch).c() = diff;
} else {
throw std::runtime_error("unknown size in cross_seg_dist_link_v3");
}
return 1 + 3 * 4;
}
/*!
* Run the linker. For now, all linking is done in two runs. If this turns out to be too slow,
* this should be modified to do incremental linking over multiple runs.
*/
uint32_t link_control::work_v3() {
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (m_state == 0) {
// state 0 <- copying data.
// the actual game does all copying in one shot. I assume this is ok because v3 files are just
// code and always small. Large data which takes too long to copy should use v2.
// loop over segments
for (s32 seg_id = ofh->segment_count - 1; seg_id >= 0; seg_id--) {
// link the infos
ofh->link_infos[seg_id].offset += m_link_block_ptr.offset;
ofh->code_infos[seg_id].offset += m_object_data.offset;
if (seg_id == DEBUG_SEGMENT) {
if (!DebugSegment) {
// clear code info if we aren't going to copy the debug segment.
ofh->code_infos[seg_id].offset = 0;
ofh->code_infos[seg_id].size = 0;
} else {
if (ofh->code_infos[seg_id].size == 0) {
// not actually present
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(kdebugheap, ofh->code_infos[seg_id].size, 0, "debug-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for debug-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
}
} else if (seg_id == MAIN_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, 0, "main-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for main-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else if (seg_id == TOP_LEVEL_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, KMALLOC_TOP, "top-level-segment")
.offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for top-level-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else {
printf("UNHANDLED SEG ID IN WORK V3 STATE 1\n");
}
}
m_state = 1;
m_segment_process = 0;
return 0;
} else if (m_state == 1) {
// state 1: linking. For now all links are done at once. This is probably going to be fine on a
// modern computer. But the game broke this into multiple steps.
if (m_segment_process < ofh->segment_count) {
Ptr<u8> lp(ofh->link_infos[m_segment_process].offset);
while (*lp) {
switch (*lp) {
case LINK_TABLE_END:
break;
case LINK_SYMBOL_OFFSET:
lp = lp + 1;
lp = lp + symlink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_TYPE_PTR:
lp = lp + 1; // seek past id
lp = lp + typelink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_DISTANCE_TO_OTHER_SEG_64:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 8);
break;
case LINK_DISTANCE_TO_OTHER_SEG_32:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 4);
break;
default:
printf("unknown link table thing %d\n", *lp);
exit(0);
break;
}
}
m_segment_process++;
} else {
// all done, can set the entry point to the top-level.
m_entry = Ptr<u8>(ofh->code_infos[TOP_LEVEL_SEGMENT].offset) + 4;
return 1;
}
return 0;
}
else {
printf("WORK v3 INVALID STATE\n");
return 1;
}
}
// TODO - work_v2, once v2 objects are created.
/*!
* Complete linking. This will execute the top-level code for v3 object files, if requested.
*/
void link_control::finish() {
CacheFlush(m_code_start.c(), m_code_size);
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
// note - this probably doesn't work because DebugSegment isn't *debug-segment*.
DebugSegment = s7.offset + FIX_SYM_TRUE;
}
if (m_flags & LINK_FLAG_FORCE_FAST_LINK) {
FastLink = 1;
}
*EnableMethodSet = *EnableMethodSet + m_keep_debug;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (ofh->object_file_version == 3) {
// todo check function type of entry
// execute top level!
if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) {
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
}
// inform compiler that we loaded.
if (m_flags & LINK_FLAG_OUTPUT_LOAD) {
output_segment_load(m_object_name, m_link_block_ptr, m_flags);
}
} else {
printf("UNHANDELD OBJECT FILE VERSION IN FINISH\n");
}
*EnableMethodSet = *EnableMethodSet - m_keep_debug;
FastLink = 0; // nested fast links won't work right.
m_heap->top = m_heap_top;
DebugSegment = old_debug_segment;
}
/*!
* Immediately link and execute an object file.
* DONE, EXACT
*/
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
link_control lc;
lc.begin(data, name, size, heap, flags);
uint32_t done;
do {
done = lc.work();
} while (!done);
lc.finish();
return lc.m_entry;
}
/*!
* Wrapper so this can be called from GOAL. Not in original game.
*/
u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags) {
return link_and_exec(Ptr<u8>(data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap), flags)
.offset;
}
/*!
* GOAL exported function for beginning a link with the saved_link_control
* 47 -> output_load, output_true, execute, 8, force fast
* 39 -> no 8 (s7)
*/
uint64_t link_begin(uint64_t object_data,
uint64_t name,
int32_t size,
uint64_t heap,
uint32_t flags) {
saved_link_control.begin(Ptr<u8>(object_data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap),
flags);
auto work_result = saved_link_control.work();
// if we managed to finish in one shot, take care of calling finish
if (work_result) {
saved_link_control.finish();
}
return work_result != 0;
}
/*!
* GOAL exported function for doing a small amount of linking work on the saved_link_control
*/
uint64_t link_resume() {
auto work_result = saved_link_control.work();
if (work_result) {
saved_link_control.finish();
}
return work_result != 0;
}
/*!
* The ULTIMATE MEMORY COPY
* IT IS VERY FAST
* but it may use the scratchpad. It is implemented in GOAL, and falls back to normal C memcpy
* if GOAL isn't loaded, or if the alignment isn't good enough.
*/
void* ultimate_memcpy(void* dst, void* src, uint32_t size) {
// only possible if alignment is good.
if (!(u64(dst) & 0xf) && !(u64(src) & 0xf) && !(u64(size) & 0xf)) {
if (!gfunc_774.offset) {
// GOAL function is unknown, lets see if its loaded:
auto sym = find_symbol_from_c("ultimate-memcpy");
if (sym->value == 0) {
return memcpy(dst, src, size);
}
gfunc_774.offset = sym->value;
}
printf("calling goal um\n");
return Ptr<u8>(call_goal(gfunc_774, make_u8_ptr(dst).offset, make_u8_ptr(src).offset, size,
s7.offset, g_ee_main_mem))
.c();
} else {
return memcpy(dst, src, size);
}
}
// The functions below are not ported because they are specific to the MIPS implementation.
// In the MIPS implementation, the c_ functions are used until GOAL loads its GOAL-implemented
// versions of the same functions. The update_goal_fns detects this and causes the linker to use
// the GOAL versions once possible. The GOAL version is much faster, but functionally equivalent to
// the C version. The C version is compiled without optimization, so this isn't too surprising.
// the rellink function is unused.
/*
c_rellink3__FPvP12link_segmentPUc
c_symlink2__FPvUiPUc
c_symlink3__FPvUiPUc
update_goal_fns__Fv
*/
+103
View File
@@ -0,0 +1,103 @@
/*!
* @file klink.cpp
* GOAL Linker for x86-64
* DONE!
*/
#ifndef JAK_KLINK_H
#define JAK_KLINK_H
#include "Ptr.h"
#include "kmalloc.h"
#include "common/link_types.h"
#include "common/common_types.h"
constexpr int LINK_FLAG_OUTPUT_LOAD = 0x1;
constexpr int LINK_FLAG_OUTPUT_TRUE = 0x2;
constexpr int LINK_FLAG_EXECUTE = 0x4;
constexpr int LINK_FLAG_PRINT_LOGIN = 0x8; //! Note, doesn't actually do anything.
constexpr int LINK_FLAG_FORCE_DEBUG = 0x10;
constexpr int LINK_FLAG_FORCE_FAST_LINK = 0x20;
/*!
* Stores the state of the linker. Used for multi-threaded linking, so it can be suspended.
*/
struct link_control {
Ptr<uint8_t> m_object_data; //! points to the start of the object file
Ptr<uint8_t> m_entry; //! points to first code to execute
char m_object_name[64]; //! object file name
int32_t m_object_size; //! object file size
Ptr<kheapinfo> m_heap; //! heap we are putting the object file on
uint32_t m_flags; //! linker configuration
Ptr<uint8_t> m_heap_top; //! where to reset the heap top for clearing temp allocations
bool m_keep_debug; //! keep the debug segment, even if DebugSegment is off?
Ptr<uint8_t> m_link_block_ptr;
uint32_t m_code_size;
Ptr<uint8_t> m_code_start;
uint32_t m_state;
uint32_t m_segment_process;
uint32_t m_version;
void begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags);
uint32_t work();
uint32_t work_v3();
void finish();
void reset() {
m_object_data.offset = 0;
m_entry.offset = 0;
memset(m_object_name, 0, sizeof(m_object_name));
m_object_size = 0;
m_heap.offset = 0;
m_flags = 0;
m_heap_top.offset = 0;
m_keep_debug = false;
m_link_block_ptr.offset = 0;
m_code_size = 0;
m_code_start.offset = 0;
m_state = 0;
m_segment_process = 0;
m_version = 0;
}
};
struct SegmentInfo {
uint32_t offset;
uint32_t size;
};
struct ObjectFileHeader {
uint16_t goal_version_major;
uint16_t goal_version_minor;
uint32_t object_file_version;
uint32_t segment_count;
SegmentInfo link_infos[N_SEG];
SegmentInfo code_infos[N_SEG];
uint32_t link_block_length;
};
void klink_init_globals();
u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags);
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags);
uint64_t link_begin(uint64_t object_data,
uint64_t name,
int32_t size,
uint64_t heap,
uint32_t flags);
uint64_t link_resume();
void* ultimate_memcpy(void* dst, void* src, uint32_t size);
extern link_control saved_link_control;
#endif // JAK_KLINK_H
+156
View File
@@ -0,0 +1,156 @@
/*!
* @file klisten.cpp
* Implementation of the Listener protocol
* Done
*/
#include <cstring>
#include "klisten.h"
#include "kboot.h"
#include "kprint.h"
#include "kdsnetm.h"
#include "ksocket.h"
#include "kmalloc.h"
#include "klink.h"
#include "kscheme.h"
#include "common/symbols.h"
Ptr<Symbol> ListenerLinkBlock;
Ptr<Symbol> ListenerFunction;
Ptr<Symbol> kernel_dispatcher;
Ptr<Symbol> kernel_packages;
Ptr<u32> print_column;
u32 ListenerStatus;
void klisten_init_globals() {
ListenerLinkBlock.offset = 0;
ListenerFunction.offset = 0;
kernel_dispatcher.offset = 0;
kernel_packages.offset = 0;
print_column.offset = 0;
ListenerStatus = 0;
}
/*!
* Initialize the Listener by setting up symbols shared between GOAL and C for the listener.
* Also adds "kernel" to the kernel_packages list.
* There was an "ACK" message sent here, but this is removed because we don't need it.
*/
void InitListener() {
ListenerLinkBlock = intern_from_c("*listener-link-block*");
ListenerFunction = intern_from_c("*listener-function*");
kernel_dispatcher = intern_from_c("kernel-dispatcher");
kernel_packages = intern_from_c("*kernel-packages*");
print_column = intern_from_c("*print-column*").cast<u32>();
ListenerLinkBlock->value = s7.offset;
ListenerFunction->value = s7.offset;
kernel_packages->value =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
make_string_from_c("kernel"), kernel_packages->value);
// if(MasterDebug) {
// SendFromBufferD(MSG_ACK, 0, AckBufArea + sizeof(GoalMessageHeader), 0);
// }
}
/*!
* Flush pending messages. If debugging, will send to compiler, otherwise to stdout.
*/
void ClearPending() {
if (!MasterDebug) {
// if we aren't debugging print the print buffer to stdout.
if (PrintPending.offset != 0) {
auto size = strlen(PrintBufArea.cast<char>().c() + sizeof(GoalMessageHeader));
if (size > 0) {
printf("%s", PrintBufArea.cast<char>().c() + sizeof(GoalMessageHeader));
}
}
} else {
if (ListenerStatus) {
if (OutputPending.offset != 0) {
Ptr<char> msg = OutputBufArea.cast<char>() + sizeof(GoalMessageHeader);
auto size = strlen(msg.c());
// note - if size is ever greater than 2^16 this will cause an issue.
SendFromBuffer(msg.c(), size);
clear_output();
}
if (PrintPending.offset != 0) {
char* msg = PrintBufArea.cast<char>().c() + sizeof(GoalMessageHeader);
auto size = strlen(msg);
while (size > 0) {
// sends larger than 64 kB are broken by the GoalProtoBuffer thing, so they are split
auto send_size = size;
if (send_size > 64000) {
send_size = 64000;
}
SendFromBufferD(2, 0, msg, send_size);
size -= send_size;
msg += send_size;
}
clear_print();
}
}
}
}
/*!
* Send an "ack" message. The original game had the AckBufArea which stores "ack", but did not
* calculate the length correctly, so the message would not actually contain the "ack" text.
* The "ack" text is unimportant, as the compiler can recognize the messages as ACK due to the
* ListenerMessageKind::MSG_ACK field. Both the type and msg_id fields are sent, which is enough
* for it to work.
*/
void SendAck() {
if (MasterDebug) {
SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id,
AckBufArea + sizeof(GoalMessageHeader),
strlen(AckBufArea + sizeof(GoalMessageHeader)));
}
}
/*!
* Handle an incoming listener message
*/
void ProcessListenerMessage(Ptr<char> msg) {
// flag that the listener is connected!
ListenerStatus = 1;
switch (protoBlock.msg_kind) {
case LTT_MSG_POKE:
// just flush any pending stuff.
ClearPending();
break;
case LTT_MSG_INSEPCT:
inspect_object(atoi(msg.c()));
ClearPending();
break;
case LTT_MSG_PRINT:
print_object(atoi(msg.c()));
ClearPending();
break;
case LTT_MSG_PRINT_SYMBOLS:
printf("[ERROR] unsupported message kind LTT_MSG_PRINT_SYMBOLS (NYI)\n");
break;
case LTT_MSG_RESET:
MasterExit = 1;
break;
case LTT_MSG_CODE: {
auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block");
memcpy(buffer.c(), msg.c(), MessCount);
ListenerLinkBlock->value = buffer.offset + 4;
// note - this will stash the linked code in the top level and free it.
// it will then be used-after-free, but this is OK because nobody else will allocate.
// the kernel dispatcher should immediately execute the listener function to avoid this
// getting squashed.
// this setup allows listener function execution to clean up after itself.
ListenerFunction->value =
link_and_exec(buffer, "*listener*", 0, kdebugheap, LINK_FLAG_FORCE_DEBUG).offset;
return; // don't ack yet, this will happen after the function runs.
} break;
default:
MsgErr("dkernel: unknown message error: <%d> of %d bytes\n", protoBlock.msg_kind, MessCount);
break;
}
SendAck();
}
+24
View File
@@ -0,0 +1,24 @@
/*!
* @file klisten.h
* Implementation of the Listener protocol
* Done
*/
#ifndef JAK_KLISTEN_H
#define JAK_KLISTEN_H
#include "kmachine.h"
#include "kscheme.h"
extern Ptr<Symbol> ListenerFunction;
extern Ptr<Symbol> kernel_dispatcher;
extern Ptr<u32> print_column;
extern Ptr<Symbol> kernel_packages;
void klisten_init_globals();
void InitListener();
void ClearPending();
void SendAck();
void ProcessListenerMessage(Ptr<char> msg);
#endif // JAK_KLISTEN_H
+618
View File
@@ -0,0 +1,618 @@
/*!
* @file kmachine.cpp
* GOAL Machine. Contains low-level hardware interfaces for GOAL.
* Not yet done - some controller stuff isn't implemented, and also many of the SCE functions
* are just stubs or commented out for now. Legal splash screen stuff is also missing.
*/
#include <string>
#include <cstring>
#include <cassert>
#include "kmachine.h"
#include "kboot.h"
#include "kprint.h"
#include "fileio.h"
#include "kmalloc.h"
#include "kdsnetm.h"
#include "ksocket.h"
#include "kscheme.h"
#include "ksound.h"
#include "kdgo.h"
#include "ksound.h"
#include "klink.h"
#include "klisten.h"
#include "game/sce/sif_ee.h"
#include "game/sce/libcdvd_ee.h"
#include "game/sce/stubs.h"
#include "common/symbols.h"
using namespace ee;
/*!
* Where does OVERLORD load its data from?
*/
OverlordDataSource isodrv;
// Get IOP modules from DVD or from dsefilesv
u32 modsrc;
// Reboot IOP with IOP kernel from DVD/CD on boot
u32 reboot;
u8 pad_dma_buf[2 * SCE_PAD_DMA_BUFFER_SIZE];
const char* init_types[] = {"fakeiso", "deviso", "iso_cd"};
void kmachine_init_globals() {
isodrv = iso_cd;
modsrc = 1;
reboot = 1;
memset(pad_dma_buf, 0, sizeof(pad_dma_buf));
}
/*!
* Initialize global variables based on command line parameters. Not called in retail versions,
* but it is present in the ELF.
* DONE
* Modified to use std::string, and removed call to fflush.
*/
void InitParms(int argc, const char* const* argv) {
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
// DVD Settings
// ----------------------------
// the "cd" mode uses the DVD drive for everything. This is how the game runs in retail
if (arg == "-cd") {
Msg(6, "dkernel: cd mode\n");
isodrv = iso_cd; // use the actual DVD drive for data files
modsrc = 1; // use the DVD drive data for IOP modules
reboot = 1; // Reboot the IOP (load new IOP runtime)
}
// the "cddata" uses the DVD drive for everything but IOP modules.
if (arg == "-cddata") {
Msg(6, "dkernel: cddata mode\n");
isodrv = iso_cd; // tell IOP to use actual DVD drive for data files
modsrc = 0; // don't use DVD drive for IOP modules
reboot = 0; // no need to reboot the IOP
}
// the "deviso" mode is one of two modes for testing without the need for DVDs
if (arg == "-deviso") {
Msg(6, "dkernel: deviso mode\n");
isodrv = deviso; // IOP deviso mode
modsrc = 0; // no IOP module loading (there's no DVD to load from!)
reboot = 0;
}
// the "fakeiso" mode is the other of two modes for testing without the need for DVDs
if (arg == "-fakeiso") {
Msg(6, "dkernel: fakeiso mode\n");
isodrv = fakeiso; // IOP fakeeiso mode
modsrc = 0; // no IOP module loading (there's no DVD to load from!)
reboot = 0;
}
// GOAL Settings
// ----------------------------
// the "demo" mode is used to pass the message "demo" to the gkernel in the DebugBootMessage
// (instead of play)
if (arg == "-demo") {
Msg(6, "dkernel: demo mode\n");
kstrcpy(DebugBootMessage, "demo");
}
// the "boot" mode is used to set GOAL up for running the game in retail mode
if (arg == "-boot") {
Msg(6, "dkernel: boot mode\n");
MasterDebug = 0;
DiskBoot = 1;
DebugSegment = 0;
}
// the "debug" mode is used to set GOAL up for debugging/developemtn
if (arg == "-debug") {
Msg(6, "dkernel: debug mode\n");
MasterDebug = 1;
DebugSegment = 1;
}
// the "debug-mem" mode is used to set up GOAL in debug mode, but not to load debug-segments
if (arg == "-debug-mem") {
Msg(6, "dkernel: debug-mem mode\n");
MasterDebug = 1;
DebugSegment = 0;
}
// the "-level [level-name]" mode is used to inform the game to boot a specific level
// the default level is "#f".
if (arg == "-level") {
i++;
std::string levelName = argv[i];
Msg(6, "dkernel: level %s\n", levelName.c_str());
kstrcpy(DebugBootLevel, levelName.c_str());
}
}
}
/*!
* Initialize the CD Drive
* DONE, EXACT
*/
void InitCD() {
printf("Initializing CD drive\nThis may take a while ...\n");
sceCdInit(SCECdINIT);
sceCdMmode(SCECdDVD);
while (sceCdDiskReady(0) == SCECdNotReady) {
printf("Drive not ready ... insert a disk!\n");
}
printf("Disk type %d\n", sceCdGetDiskType());
}
/*!
* Initialize the I/O Processor
* Removed calls to exit(0) if loading modules fails.
*/
void InitIOP() {
// before doing anything with the I/O Processor, we need to set up SIF RPC
sceSifInitRpc(0);
if ((isodrv == iso_cd) || modsrc || reboot) {
// we will need the DVD drive to bring up the IOP
InitCD();
}
if (!reboot) {
// reboot with development IOP kernel
printf("Rebooting IOP...\n");
while (!sceSifRebootIop("host0:/usr/local/sce/iop/modules/ioprp221.img")) {
printf("Failed, retrying...\n");
}
while (!sceSifSyncIop()) {
printf("Syncing...\n");
}
} else {
// reboot with IOP kernel off of the disk
// reboot with development IOP kernel
printf("Rebooting IOP...\n");
while (!sceSifRebootIop("cdrom0:\\DRIVERS\\IOPRP221.IMG;1")) {
printf("Failed, retrying...\n");
}
while (!sceSifSyncIop()) {
printf("Syncing...\n");
}
}
// now that the IOP is booted with the correct kernel, we need to connect SIF RPC again
sceSifInitRpc(0);
// if we plan to get files off of the DVD drive, we get ready to load files again.
// resetting the file system may not be needed here, but it does not hurt.
if ((isodrv == iso_cd) || modsrc) {
InitCD();
sceFsReset();
}
// we begin putting together a boot command for OVERLORD, the IOP driver, which must know the data
// source and the name of the boot splash screen of the game.
char overlord_boot_command[256];
kstrcpy(overlord_boot_command, init_types[(int)isodrv]);
char* cmd = overlord_boot_command + strlen(overlord_boot_command) + 1;
kstrcpy(cmd, "SCREEN1.USA");
auto len = strlen(cmd);
if (modsrc == fakeiso) {
// load from network
if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/sio2man.irx", 0, nullptr) < 0) {
MsgErr("loading sio2man.irx failed\n");
}
if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/padman.irx", 0, nullptr) < 0) {
MsgErr("loading padman.irx failed\n");
}
if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/libsd.irx", 0, nullptr) < 0) {
MsgErr("loading libsd.irx failed\n");
}
if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/mcman.irx", 0, nullptr) < 0) {
MsgErr("loading mcman.irx failed\n");
}
if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/mcserv.irx", 0, nullptr) < 0) {
MsgErr("loading mcserv.irx failed\n");
}
if (sceSifLoadModule("host0:/usr/home/src/989snd10/iop/989snd.irx", 0, nullptr) < 0) {
MsgErr("loading 989snd.irx failed\n");
}
sceSifLoadModule("host0:/usr/home/src/989snd10/iop/989ERR.IRX", 0, nullptr);
printf("Initializing CD library\n");
auto rv = sceSifLoadModule("host0:binee/overlord.irx", cmd + len + 1 - overlord_boot_command,
overlord_boot_command);
if (rv < 0) {
MsgErr("loading overlord.irx failed\n");
}
} else {
// load from DVD drive
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\SIO2MAN.IRX;1", 0, nullptr) < 0) {
MsgErr("loading sio2man.irx failed\n");
}
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\PADMAN.IRX;1", 0, nullptr) < 0) {
MsgErr("loading padman.irx failed\n");
}
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\LIBSD.IRX;1", 0, nullptr) < 0) {
MsgErr("loading libsd.irx failed\n");
}
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\MCMAN.IRX;1", 0, nullptr) < 0) {
MsgErr("loading mcman.irx failed\n");
}
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\MCSERV.IRX;1", 0, nullptr) < 0) {
MsgErr("loading mcserv.irx failed\n");
}
if (sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\989SND.IRX;1", 0, nullptr) < 0) {
MsgErr("loading 989snd.irx failed\n");
}
printf("Initializing CD library in ISO_CD mode\n");
auto rv = sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\OVERLORD.IRX;1",
cmd + len + 1 - overlord_boot_command, overlord_boot_command);
if (rv < 0) {
MsgErr("loading overlord.irx failed\n");
}
}
auto rv = sceMcInit();
if (rv < 0) {
MsgErr("MC driver init failed %d\n", rv);
} else {
printf("InitIOP OK\n");
}
}
/*!
* Initialize the GS and display the splash screen.
* Not yet implemented. TODO
*/
void InitVideo() {}
/*!
* Initialize GOAL Runtime. This is the main initialization which is called before entering
* the GOAL kernel dispatch loop (KernelCheckAndDispatch).
* TODO finish up things which are commented.
*/
int InitMachine() {
u32 debug_heap_end = (0xffffffff - DEBUG_HEAP_SPACE_FOR_STACK + 1) & 0x7ffffff;
// initialize the global heap
u32 global_heap_size = GLOBAL_HEAP_END - HEAP_START;
float size_mb = ((float)global_heap_size) / (float)(1 << 20);
printf("gkernel: global heap - 0x%x to 0x%x (size %.3f MB)\n", HEAP_START, GLOBAL_HEAP_END,
size_mb);
kinitheap(kglobalheap, Ptr<u8>(HEAP_START), global_heap_size);
// initialize the debug heap, if appropriate
if (MasterDebug) {
u32 debug_heap_size = debug_heap_end - DEBUG_HEAP_START;
kinitheap(kdebugheap, Ptr<u8>(DEBUG_HEAP_START), debug_heap_size);
float debug_size_mb = ((float)debug_heap_size) / (float)(1 << 20);
float gap_size_mb = ((float)DEBUG_HEAP_START - GLOBAL_HEAP_END) / (float)(1 << 20);
printf("gkernel: debug heap - 0x%x to 0x%x (size %.3f MB, gap %.3f MB)\n", DEBUG_HEAP_START,
debug_heap_end, debug_size_mb, gap_size_mb);
} else {
// if no debug, we make the kheapinfo structure NULL so GOAL knows not to use it.
kdebugheap.offset = 0;
}
init_output(); // GOAL input/output buffer setup
InitIOP(); // start IOP/OVERLORD, loading our legal splash screen
// sceGsResetPath(); // reset VIF1, VU1, GIF
InitVideo(); // display legal splash screen
// FlushCache(WRITEBACK_DCACHE);
// FlushCache(INVALIDATE_ICACHE);
// sceGsSyncV(0); // wait for it to show up on the screen
//
// if(scePadInit(0) != 1) { // init controllers
// MsgErr("dkernel: !init pad\n");
// }
if (MasterDebug) { // connect to GOAL compiler
InitGoalProto();
}
printf("InitSound\n");
InitSound(); // do nothing!
printf("InitRPC\n");
InitRPC(); // connect to IOP
reset_output(); // reset output buffers
clear_print();
s32 goal_status = InitHeapAndSymbol(); // init GOAL runtime, load kernel and engine
if (goal_status < 0) {
return goal_status;
}
printf("InitListenerConnect\n");
InitListenerConnect();
printf("InitCheckListener\n");
InitCheckListener();
Msg(6, "kernel: machine started\n");
return 0;
}
/*!
* Shutdown the runtime.
*/
int ShutdownMachine() {
StopIOP();
CloseListener();
ShutdownSound();
ShutdownGoalProto();
Msg(6, "kernel: machine shutdown");
return 0;
}
/*!
* Flush caches. Does all the memory, regardless of what you specify
*/
void CacheFlush(void* mem, int size) {
(void)mem;
(void)size;
// FlushCache(0);
// FlushCache(2);
}
/*!
* Open a new controller pad.
* Set the new_pad flag to 1 and state to 0.
* Prints an error if it fails to open.
*/
u64 CPadOpen(u64 cpad_info, s32 pad_number) {
auto info = Ptr<CpadInfo>(cpad_info).c();
if (info->cpad_file == 0) {
// not open, so we will open it
info->cpad_file =
ee::scePadPortOpen(pad_number, 0, pad_dma_buf + pad_number * SCE_PAD_DMA_BUFFER_SIZE);
if (info->cpad_file < 1) {
MsgErr("dkernel: !open cpad #%d (%d)\n", pad_number, info->cpad_file);
}
info->new_pad = 1;
info->state = 0;
}
return cpad_info;
}
// TODO CPadGetData
void CPadGetData() {
assert(false);
}
// TODO InstallHandler
void InstallHandler() {
assert(false);
}
// TODO InstallDebugHandler
void InstallDebugHandler() {
assert(false);
}
/*!
* Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly
* and anything else for write only.
*/
u64 kopen(u64 fs, u64 name, u64 mode) {
auto file_stream = Ptr<FileStream>(fs).c();
file_stream->mode = mode;
file_stream->name = name;
file_stream->flags = 0;
printf("****** CALL TO kopen() ******\n");
char buffer[128];
sprintf(buffer, "host:%s", Ptr<String>(name)->data());
if (!strcmp(info(Ptr<Symbol>(mode))->str->data(), "read")) {
file_stream->file = sceOpen(buffer, SCE_RDONLY);
} else {
// 0x602
file_stream->file = sceOpen(buffer, SCE_TRUNC | SCE_CREAT | SCE_WRONLY);
}
return fs;
}
/*!
* Get length of a file.
*/
s32 klength(u64 fs) {
auto file_stream = Ptr<FileStream>(fs).c();
if ((file_stream->flags ^ 1) & 1) {
// first flag bit not set. This means no errors
auto end_seek = sceLseek(file_stream->file, 0, SCE_SEEK_END);
auto reset_seek = sceLseek(file_stream->file, 0, SEEK_SET);
if (reset_seek < 0 || end_seek < 0) {
// seeking failed, flag it
file_stream->flags |= 1;
}
return end_seek;
} else {
return 0;
}
}
/*!
* Seek a file stream.
*/
s32 kseek(u64 fs, s32 offset, s32 where) {
s32 result = -1;
auto file_stream = Ptr<FileStream>(fs).c();
if ((file_stream->flags ^ 1) & 1) {
result = sceLseek(file_stream->file, offset, where);
if (result < 0) {
file_stream->flags |= 1;
}
}
return result;
}
/*!
* Read from a file stream.
*/
s32 kread(u64 fs, u64 buffer, s32 size) {
s32 result = -1;
auto file_stream = Ptr<FileStream>(fs).c();
if ((file_stream->flags ^ 1) & 1) {
result = sceRead(file_stream->file, Ptr<u8>(buffer).c(), size);
if (result < 0) {
file_stream->flags |= 1;
}
}
return result;
}
/*!
* Write to a file stream.
*/
s32 kwrite(u64 fs, u64 buffer, s32 size) {
s32 result = -1;
auto file_stream = Ptr<FileStream>(fs).c();
if ((file_stream->flags ^ 1) & 1) {
result = sceWrite(file_stream->file, Ptr<u8>(buffer).c(), size);
if (result < 0) {
file_stream->flags |= 1;
}
}
return result;
}
/*!
* Close a file stream.
*/
u64 kclose(u64 fs) {
auto file_stream = Ptr<FileStream>(fs).c();
if ((file_stream->flags ^ 1) & 1) {
sceClose(file_stream->file);
file_stream->file = -1;
}
file_stream->flags = 0;
return fs;
}
// TODO dma_to_iop
void dma_to_iop() {
assert(false);
}
u64 DecodeLanguage() {
return masterConfig.language;
}
u64 DecodeAspect() {
return masterConfig.aspect;
}
u64 DecodeVolume() {
return masterConfig.volume;
}
u64 DecodeTerritory() {
return 0;
}
u64 DecodeTimeout() {
return masterConfig.timeout;
}
u64 DecodeInactiveTimeout() {
return masterConfig.inactive_timeout;
}
// TODO DecodeTime
void DecodeTime() {
assert(false);
}
// TODO PutDisplayEnv
void PutDisplayEnv() {
assert(false);
}
/*!
* Final initialization of the system after the kernel is loaded.
* This is called from InitHeapAndSymbol at the very end.
* Exports the last of the functions written in C to the GOAL symbol table
* If DiskBooting, will load the GAME CGO, containing the engine, and calls "play", the function
* which should prepare the game engine.
*/
void InitMachineScheme() {
make_function_symbol_from_c("put-display-env", (void*)PutDisplayEnv); // used in drawable
make_function_symbol_from_c("syncv", (void*)ee::sceGsSyncV); // used in drawable
make_function_symbol_from_c("sync-path", (void*)sceGsSyncPath); // used
make_function_symbol_from_c("reset-path", (void*)sceGsResetPath); // used in dma
make_function_symbol_from_c("reset-graph", (void*)sceGsResetGraph); // used
make_function_symbol_from_c("dma-sync", (void*)sceDmaSync); // used
make_function_symbol_from_c("gs-put-imr", (void*)sceGsPutIMR); // unused
make_function_symbol_from_c("gs-get-imr", (void*)sceGsGetIMR); // unused
make_function_symbol_from_c("gs-store-image", (void*)sceGsExecStoreImage); // used
make_function_symbol_from_c("flush-cache", (void*)FlushCache); // used
make_function_symbol_from_c("cpad-open", (void*)CPadOpen); // used
make_function_symbol_from_c("cpad-get-data", (void*)CPadGetData); // used
make_function_symbol_from_c("install-handler", (void*)InstallHandler); // used
make_function_symbol_from_c("install-debug-handler", (void*)InstallDebugHandler); // used
make_function_symbol_from_c("file-stream-open", (void*)kopen); // used
make_function_symbol_from_c("file-stream-close", (void*)kclose); // used
make_function_symbol_from_c("file-stream-length", (void*)klength); // used
make_function_symbol_from_c("file-stream-seek", (void*)kseek); // unused
make_function_symbol_from_c("file-stream-read", (void*)kread); // used
make_function_symbol_from_c("file-stream-write", (void*)kwrite); // used
make_function_symbol_from_c("scf-get-language", (void*)DecodeLanguage); // used
make_function_symbol_from_c("scf-get-time", (void*)DecodeTime); // used
make_function_symbol_from_c("scf-get-aspect", (void*)DecodeAspect); // used
make_function_symbol_from_c("scf-get-volume", (void*)DecodeVolume); // used
make_function_symbol_from_c("scf-get-territory", (void*)DecodeTerritory); // used
make_function_symbol_from_c("scf-get-timeout", (void*)DecodeTimeout); // used
make_function_symbol_from_c("scf-get-inactive-timeout", (void*)DecodeInactiveTimeout); // used
make_function_symbol_from_c("dma-to-iop", (void*)dma_to_iop); // unused
make_function_symbol_from_c("kernel-shutdown", (void*)KernelShutdown); // used
make_function_symbol_from_c("aybabtu", (void*)sceCdMmode); // used
InitSoundScheme();
intern_from_c("*stack-top*")->value = 0x07ffc000;
intern_from_c("*stack-base*")->value = 0x07ffffff;
intern_from_c("*stack-size*")->value = 0x4000;
if (DiskBoot) {
intern_from_c("*kernel-boot-message*")->value = intern_from_c(DebugBootMessage).offset;
intern_from_c("*kernel-boot-mode*")->value = intern_from_c("boot").offset; // or debug-boot
intern_from_c("*kernel-boot-level*")->value = intern_from_c(DebugBootLevel).offset;
}
if (DiskBoot) {
*EnableMethodSet = (*EnableMethodSet) + 1;
load_and_link_dgo_from_c("game", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000);
*EnableMethodSet = (*EnableMethodSet) - 1;
kernel_packages->value =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
make_string_from_c("engine"), kernel_packages->value);
kernel_packages->value =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
make_string_from_c("art"), kernel_packages->value);
kernel_packages->value =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
make_string_from_c("common"), kernel_packages->value);
printf("calling play!\n");
call_goal_function_by_name("play");
}
}
+115
View File
@@ -0,0 +1,115 @@
/*!
* @file kmachine.h
* GOAL Machine. Contains low-level hardware interfaces for GOAL.
*/
#ifndef RUNTIME_KMACHINE_H
#define RUNTIME_KMACHINE_H
#include "common/common_types.h"
#include "Ptr.h"
//! How much space to leave for the stack when creating the debug heap
constexpr u32 DEBUG_HEAP_SPACE_FOR_STACK = 0x4000;
//! First free address for the GOAL heap
constexpr u32 HEAP_START = 0x13fd20;
//! Where to end the global heap so it doesn't overlap with the stack.
constexpr u32 GLOBAL_HEAP_END = 0x1ffc000;
//! Location of kglobalheap, kdebugheap kheapinfo structures.
constexpr u32 GLOBAL_HEAP_INFO_ADDR = 0x13AD00;
constexpr u32 DEBUG_HEAP_INFO_ADDR = 0x13AD10;
//! Where to place the debug heap
constexpr u32 DEBUG_HEAP_START = 0x5000000;
/*!
* Where does OVERLORD load its data from?
*/
enum OverlordDataSource : u32 {
fakeiso = 0, //! some sort of development way of getting data
deviso = 1, //! some sort of development way of getting data
iso_cd = 2, //! use the actual DVD drive
};
extern OverlordDataSource isodrv;
// Get IOP modules from DVD or from dsefilesv
extern u32 modsrc;
// Reboot IOP on start?
extern u32 reboot;
/*!
* Initialize globals for kmachine.
* This should be called before running main.
*/
void kmachine_init_globals();
/*!
* Initialize global variables based on command line parameters
*/
void InitParms(int argc, const char* const* argv);
/*!
* Initialize the CD Drive
*/
void InitCD();
/*!
* Initialize the I/O Processor
*/
void InitIOP();
/*!
* Initialize the GS and display the splash screen.
*/
void InitVideo();
/*!
* Initialze GOAL Runtime
*/
int InitMachine();
/*!
* Shutdown GOAL runtime.
*/
int ShutdownMachine();
/*!
* Flush caches. Does all the memory, regardless of what you specify
*/
void CacheFlush(void* mem, int size);
void InitMachineScheme();
//! Mirror of cpad-info
struct CpadInfo {
u8 valid;
u8 status;
s16 button0;
u8 rx;
u8 ry;
u8 lx;
u8 ly;
u8 abutton[12];
u8 dummy[12];
s32 number;
s32 cpad_file;
u8 _pad0[36];
s32 new_pad;
s32 state;
};
struct FileStream {
u32 flags;
u32 mode; // basic
u32 name; // basic
s32 file; // int32
};
// static_assert(offsetof(CpadInfo, new_pad) == 76, "cpad type offset");
#endif // RUNTIME_KMACHINE_H
+182
View File
@@ -0,0 +1,182 @@
/*!
* @file kmalloc.cpp
* GOAL Kernel memory allocator.
* Simple two-sided bump allocator.
* DONE
*/
#include <cstring>
#include "kmalloc.h"
#include "kprint.h"
#include "kscheme.h"
// global and debug kernel heaps
Ptr<kheapinfo> kglobalheap;
Ptr<kheapinfo> kdebugheap;
void kmalloc_init_globals() {
// _globalheap and _debugheap
kglobalheap.offset = GLOBAL_HEAP_INFO_ADDR;
kdebugheap.offset = DEBUG_HEAP_INFO_ADDR;
}
/*!
* In the game, this wraps PS2's libc's malloc/calloc.
* These don't work with GOAL's custom memory management, and this function
* is unused.
* DONE, malloc/calloc calls commented out because memory allocated with calloc/malloc
* cannot trivially be accessed from within GOAL.
*/
Ptr<u8> ksmalloc(Ptr<kheapinfo> heap, s32 size, u32 flags, char const* name) {
(void)heap;
(void)size;
(void)name;
printf("[ERROR] ksmalloc : cannot be used!\n");
u32 align = flags & 0xfff;
Ptr<u8> mem;
if ((flags & KMALLOC_MEMSET) == 0) {
// mem = malloc(size + align);
} else {
// mem = calloc(1, size + align);
}
if (align == KMALLOC_ALIGN_64) {
mem.offset = (mem.offset + 0x3f) & 0xffffffc0;
} else if (align == KMALLOC_ALIGN_256) {
mem.offset = (mem.offset + 0xff) & 0xffffff00;
}
return mem;
}
/*!
* Print the status of a kheap. This prints to stdout on the runtime,
* which will not be sent to the Listener.
* DONE, EXACT
*/
void kheapstatus(Ptr<kheapinfo> heap) {
Msg(6,
"[%8x] kheap\n"
"\tbase: #x%x\n"
"\ttop-base: #x%x\n"
"\tcur: #x%x\n"
"\ttop: #x%x\n",
heap.offset, heap->base.offset, heap->top_base.offset, heap->current.offset,
heap->top.offset);
Msg(6,
"\t used bot: %d of %d bytes\n"
"\t used top: %d of %d bytes\n"
"\t symbols: %d of %d\n",
heap->current - heap->base, heap->top_base - heap->base, heap->top_base - heap->top,
heap->top_base - heap->base, NumSymbols, GOAL_MAX_SYMBOLS);
if (heap == kglobalheap) {
Msg(6, "\t %d bytes before stack\n", GLOBAL_HEAP_END - heap->current.offset);
}
}
/*!
* Initialize a kheapinfo structure, and clear the kheap's memory to 0.
* DONE, EXACT
*/
Ptr<kheapinfo> kinitheap(Ptr<kheapinfo> heap, Ptr<u8> mem, s32 size) {
heap->base = mem;
heap->current = mem;
heap->top = mem + size;
heap->top_base = heap->top;
std::memset(mem.c(), 0, size);
return heap;
}
/*!
* Return how much of the bottom (non-temp) allocator is used.
* DONE, EXACT
*/
u32 kheapused(Ptr<kheapinfo> heap) {
return heap->current - heap->base;
}
/*!
* Allocate memory using bump allocation strategy.
* @param heapPtr : heap to allocate on. If null heap, use global but print a warning
* @param size : size of memory needed
* @param flags : flags for alignment, top/bottom allocation, set to zero
* @param name : name of allocation (printed if things go wrong)
* @return : memory. 0 if we run out of room
* DONE, PRINT ADDED
*/
Ptr<u8> kmalloc(Ptr<kheapinfo> heap, s32 size, u32 flags, char const* name) {
uint32_t alignment_flag = flags & 0xfff;
// if we got a null heap, put it on the global heap, but warn about it
if (!heap.offset) {
Msg(6, "-----------> kmalloc: alloc %s, mem %s #x%x (a:%d %dbytes)\n", "DEBUG", name, -1,
alignment_flag, size);
heap = kglobalheap;
}
uint32_t memstart;
if (!(flags & KMALLOC_TOP)) {
// allocate from bottom
if (alignment_flag == KMALLOC_ALIGN_64)
memstart = (0xffffffc0 & (heap->current.offset + 0x40 - 1));
else if (alignment_flag == KMALLOC_ALIGN_256)
memstart = (0xffffff00 & (heap->current.offset + 0x100 - 1));
else // includes 0x10!
memstart = (0xfffffff0 & (heap->current.offset + 0x10 - 1));
if (size == 0) {
Msg(6, "[WARNING] kmalloc : size 0 allocation from bottom.\n");
return Ptr<u8>(memstart);
}
uint32_t memend = memstart + size;
if (heap->top.offset < memend) {
kheapstatus(heap);
Msg(6, "kmalloc: !alloc mem %s (%d bytes) heap %x\n", name, size, heap.offset);
return Ptr<u8>(0);
}
heap->current.offset = memend;
if (flags & KMALLOC_MEMSET)
std::memset(Ptr<u8>(memstart).c(), 0, (size_t)size);
return Ptr<u8>(memstart);
} else {
// allocate from top
if (alignment_flag == 0) {
alignment_flag = KMALLOC_ALIGN_16;
}
memstart = (heap->top.offset - size) & (-alignment_flag);
if (size == 0) {
Msg(6, "[WARNING] kmalloc : size 0 allocation from top\n");
return Ptr<u8>(memstart);
}
if (heap->current.offset >= memstart) {
Msg(6, "kmalloc: !alloc mem from top %s (%d bytes) heap %x\n", name, size, heap.offset);
kheapstatus(heap);
return Ptr<u8>(0);
}
heap->top.offset = memstart;
if (flags & 0x1000)
std::memset(Ptr<u8>(memstart).c(), 0, (size_t)size);
return Ptr<u8>(memstart);
}
}
/*!
* GOAL does not support automatic freeing of memory. This function does nothing.
* Programmers wishing to free memory must do it themselves.
* DONE, PRINT ADDED
*/
void kfree(Ptr<u8> a) {
(void)a;
Msg(6, "[ERROR] kmalloc: kfree called\n");
}
+46
View File
@@ -0,0 +1,46 @@
/*!
* @file kmalloc.h
* GOAL Kernel memory allocator.
* Simple two-sided bump allocator.
* DONE
*/
#ifndef JAK_KMALLOC_H
#define JAK_KMALLOC_H
#include "common/common_types.h"
#include "Ptr.h"
#include "kmachine.h"
/*!
* A kheap has a top/bottom linear allocator
*/
struct kheapinfo {
Ptr<u8> base; //! beginning of heap
Ptr<u8> top; //! current location of bottom of top allocations
Ptr<u8> current; //! current location of top of bottom allocations
Ptr<u8> top_base; //! end of heap
};
// Kernel heaps
extern Ptr<kheapinfo> kglobalheap;
extern Ptr<kheapinfo> kdebugheap;
// flags for kmalloc/ksmalloc
constexpr u32 KMALLOC_TOP = 0x2000; //! Flag to allocate temporary memory from heap top
constexpr u32 KMALLOC_MEMSET = 0x1000; //! Flag to clear memory
constexpr u32 KMALLOC_ALIGN_256 = 0x100;
constexpr u32 KMALLOC_ALIGN_64 = 0x40;
constexpr u32 KMALLOC_ALIGN_16 = 0x10;
// kmalloc funcions
Ptr<u8> ksmalloc(Ptr<kheapinfo> heap, s32 size, u32 flags, char const* name);
void kheapstatus(Ptr<kheapinfo> heap);
Ptr<kheapinfo> kinitheap(Ptr<kheapinfo> heap, Ptr<u8> mem, s32 size);
u32 kheapused(Ptr<kheapinfo> heap);
Ptr<u8> kmalloc(Ptr<kheapinfo> heap, s32 size, u32 flags, char const* name);
void kfree(Ptr<u8> a);
void kmalloc_init_globals();
#endif // JAK_KMALLOC_H
+214
View File
@@ -0,0 +1,214 @@
/*!
* @file kmemcard.cpp
* Memory card interface. Very messy code.
*/
//#include "ps2/SCE_MC.h"
//#include "ps2/SCE_FS.h"
//#include "ps2/common_types.h"
//#include "kernel/kmachine.h"
#include "kmemcard.h"
// static s32 next;
// static s32 language;
// static MemoryCardOperation op;
// static mc_info mc[2];
void kmemcard_init_globals() {
// next = 0;
}
///*!
// * Get a new memory card handle.
// * Will never return 0.
// */
// s32 new_mc_handle() {
// s32 handle = next++;
//
// // if you wrap around, it avoid the zero handle.
// // it doesn't seem like you will need billions of memory card handles
// if(handle == 0) {
// handle = next++;
// }
// return handle;
//}
//
///*!
// * A questionable checksum.
// */
// u32 mc_checksum(Ptr<u8> data, s32 size) {
// if(size < 0) {
// size += 3;
// }
//
// u32 result = 0;
// u32* data_u32 = (u32*)data.c();
// for(s32 i = 0; i < size / 4; i++) {
// result = result << 1 ^ result >> 0x1f ^ data_u32[i*4] ^ 0x12345678;
// }
//
// return result ^ 0xedd1e666;
//}
//
// u32 handle_to_slot(s32 handle, s32 p2) {
// if(mc[0].p0 == p2 && mc[0].handle == handle) {
// return 0;
// }
// if(mc[1].p0 == p2 && mc[0].handle == handle) {
// return 1;
// } else {
// return -1;
// }
//}
//
// void MC_run() {
//
//}
//
///*!
// * Set the language or something.
// */
// void MC_set_language(s32 l) {
// printf("Language set to %d\n", l);
// language = l;
//}
//
// u64 MC_format(s32 param) {
// u64 can_add = op.operation == NO_OP;
// if(can_add) {
// op.operation = FORMAT;
// op.result = 0;
// op.f_10 = 100;
// op.param = param;
// }
// return can_add;
//}
//
//
// u64 MC_unformat(s32 param) {
// u64 can_add = op.operation == NO_OP;
// if(can_add) {
// op.operation = UNFORMAT;
// op.result = 0;
// op.f_10 = 100;
// op.param = param;
// }
// return can_add;
//}
//
// u64 MC_createfile(s32 param, Ptr<u8> data) {
// u64 can_add = op.operation == NO_OP;
// if(can_add) {
// op.operation = CREATE_FILE;
// op.result = 0;
// op.f_10 = 100;
// op.param = param;
// op.data_ptr = data;
// }
// return can_add;
//}
//
// u64 MC_save(s32 param, s32 param2, Ptr<u8> data, Ptr<u8> data2) {
// u64 can_add = op.operation == NO_OP;
// if(can_add) {
// op.operation = SAVE;
// op.result = 0;
// op.f_10 = 100;
// op.param = param;
// op.param2 = param2;
// op.data_ptr = data;
// op.data_ptr2 = data2;
// }
// return can_add;
//}
//
// u64 MC_load(s32 param, s32 param2, Ptr<u8> data) {
// u64 can_add = op.operation == NO_OP;
// if(can_add) {
// op.operation = LOAD;
// op.result = 0;
// op.f_10 = 100;
// op.param = param;
// op.param2 = param2;
// op.data_ptr = data;
// }
// return can_add;
//}
//
///*!
// * Some sort of test function for memory card stuff.
// */
// void MC_makefile(s32 port, s32 size) {
// sceMcMkdir(port, 0, "/BASCUS-00000XXXXXXXX");
// // wait for operation to complete
// s32 cmd, result, fd;
// sceMcSync(0, &cmd, &result);
//
// if(result == sceMcResSucceed || result == sceMcResNoEntry) {
// // it worked, or the folder already exists...
//
// // open file
// sceMcOpen(port, 0, "/BASCUS-00000XXXXXXXX/BASCUS-00000XXXXXXXX", SCE_CREAT | SCE_WRONLY);
// sceMcSync(0, &cmd, &fd);
//
// if(result < 0) {
// printf("Can\'t open file on memcard [%d]\n", result);
// } else {
// // write some random crap into the memory card.
// sceMcWrite(fd, Ptr<u8>(0x1000000).c(), size);
// sceMcSync(0, &cmd, &result);
// if(result != size) {
// printf("Only written %d bytes\n", result);
// }
// sceMcClose(fd);
// sceMcSync(0, &cmd, &result);
// }
// } else {
// printf("Can\'t create garbage folder [%d]\n", result);
// }
//}
//
// u32 MC_check_result() {
// return op.result;
//}
//
// void MC_get_status(s32 slot, Ptr<mc_slot_info> info) {
// info->handle = 0;
// info->known = 0;
// info->formatted = 0;
// info->initted = 0;
// for(s32 i = 0; i < 4; i++) {
// info->files[i].present = 0;
// }
// info->last_file = 0xffffffff;
// info->mem_required = SAVE_SIZE;
// info->mem_actual = 0;
//
// switch(mc[slot].p0) {
// case 1:
// info->known = 1;
// break;
// case 2:
// info->known = 1;
// info->handle = mc[slot].handle;
// break;
// case 3:
// info->known = 1;
// info->handle = mc[slot].handle;
// info->formatted = 1;
// if(mc[slot].inited == 0) {
// info->mem_actual = mc[slot].mem_actual;
// } else {
// info->initted = 1;
// for(s32 file = 0; file < 4; file++) {
// info->files[file].present = mc[slot].files[file].present;
// for(s32 i = 0; i < 64; i++) { // actually a loop over u32's
// info->files[file].data[i] = mc[slot].files[file].data[i];
// }
// }
// info->last_file = mc[slot].last_file;
//
// }
// }
//
//}
+82
View File
@@ -0,0 +1,82 @@
/*!
* @file kmemcard.h
* Memory card interface. Very messy code.
*/
#ifndef JAK_KMEMCARD_H
#define JAK_KMEMCARD_H
#include "common/common_types.h"
#include "kmachine.h"
void kmemcard_init_globals();
constexpr s32 SAVE_SIZE = 0x2b3; // likely different by versions!
enum MemoryCardOperationKind {
NO_OP = 0,
FORMAT = 1,
UNFORMAT = 2,
CREATE_FILE = 3,
SAVE = 4,
LOAD = 5,
};
struct MemoryCardOperation {
uint32_t operation;
uint32_t param;
uint32_t param2;
uint32_t result;
uint32_t f_10;
Ptr<u8> data_ptr;
Ptr<u8> data_ptr2;
};
struct mc_file_info {
u32 present;
u8 data[64];
};
struct mc_file_info_2 {
u32 present;
u32 pad1;
u32 pad2;
u8 data[64];
};
struct mc_slot_info {
u32 handle;
u32 known;
u32 formatted;
u32 initted;
u32 last_file;
u32 mem_required;
u32 mem_actual;
mc_file_info files[4];
};
struct mc_info {
s32 p0;
s32 handle;
s32 inited;
s32 mem_actual;
s32 last_file;
mc_file_info_2 files[4];
};
s32 new_mc_handle();
u32 mc_checksum(Ptr<u8> data, s32 size);
u32 handle_to_slot(s32 p1, s32 p2);
void MC_run();
void MC_set_language(s32 lang);
u64 MC_format(s32 param);
u64 MC_unformat(s32 param);
u64 MC_createfile(s32 param, Ptr<u8> data);
u64 MC_save(s32 param, s32 param2, Ptr<u8> data, Ptr<u8> data2);
u64 MC_load(s32 param, s32 param2, Ptr<u8> data);
void MC_makefile(s32 port, s32 size);
u32 MC_check_result();
void MC_get_status(s32 slot, Ptr<mc_slot_info> info);
#endif // JAK_KMEMCARD_H
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
/*!
* @file kprint.h
* GOAL Print. Contains GOAL I/O, Print, Format...
*/
#ifndef RUNTIME_KPRINT_H
#define RUNTIME_KPRINT_H
#include "kmachine.h"
constexpr u32 DEBUG_MESSAGE_BUFFER_SIZE = 0x80000;
constexpr u32 DEBUG_OUTPUT_BUFFER_SIZE = 0x80000;
constexpr u32 DEBUG_PRINT_BUFFER_SIZE = 0x200000;
constexpr u32 PRINT_BUFFER_SIZE = 0x2000;
///////////
// SDATA
///////////
extern Ptr<u8> OutputPending;
extern Ptr<u8> PrintPending;
extern s32 MessCount;
extern char AckBufArea[40];
extern Ptr<u8> MessBufArea;
extern Ptr<u8> OutputBufArea;
extern Ptr<u8> PrintBufArea;
/*!
* Initialize global variables for kprint
*/
void kprint_init_globals();
/*!
* Initialize GOAL Kernel printing/messaging system.
* Allocates buffers.
*/
void init_output();
/*!
* Empty output buffer (only if MasterDebug)
*/
void clear_output();
/*!
* Clear all data in the print buffer
*/
void clear_print();
/*!
* Buffer message to compiler indicating the target has reset.
* Write to the beginning of the output buffer.
*/
void reset_output();
/*!
* Buffer message to compiler indicating some object file has been unloaded.
*/
void output_unload(const char* name);
/*!
* Buffer message to compiler indicating some object file has been loaded.
*/
void output_segment_load(const char* name, Ptr<u8> link_block, u32 flags);
/*!
* Print to the GOAL print buffer from C
*/
void cprintf(const char* format, ...) __attribute__((format(printf, 1, 2)));
/*!
* Print directly to the C stdout
* The "k" parameter is ignored, so this is just like printf
*/
void Msg(s32 k, const char* format, ...) __attribute__((format(printf, 2, 3)));
/*!
* Print directly to the C stdout
* This is identical to Msg.
*/
void MsgWarn(const char* format, ...) __attribute__((format(printf, 1, 2)));
/*!
* Print directly to the C stdout
* This is identical to Msg.
*/
void MsgErr(const char* format, ...) __attribute__((format(printf, 1, 2)));
/*!
* Reverse string in place.
*/
void reverse(char* s);
/*!
* Helper function for floating point to string conversion.
*/
s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* buff_end, u32 flags);
/*!
* Convert floating point to a string.
*/
void ftoa(char* out_str, float x, s32 desired_len, char pad_char, s32 precision, u32 flags);
/*!
* Convert integer to a string.
*/
char* kitoa(char* buffer, s64 value, u64 base, s32 length, char pad, u32 flag);
/*!
* Convert 128-bit integer to string. Not implemented because it is never used in the game.
* The format function does have the ability to call it, but it always passes a zero because
* getting a 128-bit integer in PS2 gcc's varargs doesn't work.
*/
void kqtoa();
extern "C" {
s32 format_impl(uint64_t* args);
}
#endif // RUNTIME_KPRINT_H
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
/*!
* @file kscheme.h
* Implementation of GOAL runtime.
*/
#ifndef JAK_KSCHEME_H
#define JAK_KSCHEME_H
#include "common/common_types.h"
#include "kmachine.h"
#include "kmalloc.h"
extern u32 FastLink;
extern s32 NumSymbols;
extern Ptr<u32> EnableMethodSet;
extern Ptr<u32> s7;
extern Ptr<u32> SymbolTable2;
extern Ptr<u32> LastSymbol;
constexpr s32 GOAL_MAX_SYMBOLS = 0x2000;
constexpr s32 BINTEGER_OFFSET = 0;
constexpr s32 PAIR_OFFSET = 2;
constexpr s32 BASIC_OFFSET = 4;
constexpr s32 SYM_INFO_OFFSET = 0xff34;
constexpr u32 EMPTY_HASH = 0x8454B6E6;
constexpr u32 OFFSET_MASK = 7;
constexpr u32 CRC_POLY = 0x04c11db7;
constexpr u32 GOAL_NEW_FUNC = 0; // method ID of GOAL new
constexpr u32 GOAL_DEL_FUNC = 1; // method ID of GOAL delete
constexpr u32 GOAL_PRINT_FUNC = 2; // method ID of GOAL print
constexpr u32 GOAL_INSPECT_FUNC = 3; // method ID of GOAL inspect
constexpr u32 GOAL_LENGTH_FUNC = 4; // method ID of GOAL length
constexpr u32 GOAL_ASIZE_FUNC = 5; // method ID of GOAL size
constexpr u32 GOAL_COPY_FUNC = 6; // method ID of GOAL copy
constexpr u32 GOAL_RELOC_FUNC = 7; // method ID of GOAL relocate
constexpr u32 DEFAULT_METHOD_COUNT = 12;
constexpr u32 FALLBACK_UNKNOWN_METHOD_COUNT = 44;
struct String {
u32 len;
char* data() { return ((char*)this) + sizeof(String); }
};
struct SymInfo {
u32 hash;
Ptr<String> str;
};
struct Symbol {
u32 value;
};
inline Ptr<SymInfo> info(Ptr<Symbol> s) {
return s.cast<SymInfo>() + SYM_INFO_OFFSET;
}
struct Function {};
/*!
* GOAL Type
*/
struct Type {
Ptr<Symbol> symbol; //! The type's symbol 0x0
Ptr<Type> parent; //! The type's parent 0x4
u16 allocated_size; //! The type's size in memory 0x8
u16 padded_size; //! The type's size, when padded? 0xa
u16 heap_base; //! relative location of heap 0xc
u16 num_methods; //! allocated-length field 0xe - 0xf
Ptr<Function> new_method; // 16 0
Ptr<Function> delete_method; // 20 1
Ptr<Function> print_method; // 24 2
Ptr<Function> inspect_method; // 28 3
Ptr<Function> length_method; // 32 4
Ptr<Function> asize_of_method; // 36 5
Ptr<Function> copy_method; // 40 6
Ptr<Function> relocate_method; // 44 7
Ptr<Function> memusage_method; // 48 8
Ptr<Function>& get_method(u32 i) {
Ptr<Function>* f = &new_method;
return f[i];
}
};
u32 crc32(const u8* data, s32 size);
void kscheme_init_globals();
void init_crc();
u64 alloc_from_heap(u32 heapSymbol, u32 type, s32 size);
Ptr<Symbol> intern_from_c(const char* name);
Ptr<Type> intern_type_from_c(const char* name, u64 methods);
Ptr<Type> set_type_values(Ptr<Type> type, Ptr<Type> parent, u64 flags);
u64 print_object(u32 obj);
u64 print_pair(u32 obj);
u64 print_binteger(u64 obj);
u64 inspect_pair(u32 obj);
u64 inspect_binteger(u64 obj);
s32 InitHeapAndSymbol();
u64 call_goal(Ptr<Function> f, u64 a, u64 b, u64 c, u64 st, void* offset);
void print_symbol_table();
u64 make_string_from_c(const char* c_str);
Ptr<Symbol> find_symbol_from_c(const char* name);
u64 call_method_of_type(u32 arg, Ptr<Type> type, u32 method_id);
u64 inspect_object(u32 obj);
u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr);
s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags);
u64 load(u32 file_name_in, u32 heap_in);
u64 loado(u32 file_name_in, u32 heap_in);
u64 unload(u32 name);
Ptr<Function> make_function_symbol_from_c(const char* name, void* f);
u64 call_goal_function_by_name(const char* name);
Ptr<Type> alloc_and_init_type(Ptr<Symbol> sym, u32 method_count);
Ptr<Symbol> set_fixed_symbol(u32 offset, const char* name, u32 value);
#endif // JAK_KSCHEME_H
+109
View File
@@ -0,0 +1,109 @@
/*!
* @file ksocket.cpp
* GOAL Socket connection to listener using DECI2/DSNET
* DONE!
*/
#include "ksocket.h"
#include "kdsnetm.h"
#include "kprint.h"
#include "kboot.h"
#include "fileio.h"
#include "klisten.h"
/*!
* Update GOAL message header after receiving and verify message is ok.
* Return the size of the message in bytes (not including DECI or GOAL headers)
* Return -1 on error.
* The buffer parameter is unused.
* DONE, removed call to FlushCache(0);
*/
u32 ReceiveToBuffer(char* buff) {
(void)buff;
// if we received less than the size of the message header, we either got nothing, or there was an
// error
if (protoBlock.last_receive_size < (int)sizeof(GoalMessageHeader)) {
return -1;
}
// FlushCache(0);
GoalMessageHeader* gbuff = protoBlock.receive_buffer;
u32 msg_size = gbuff->msg_size;
// check it's our protocol
if (gbuff->deci2_hdr.proto == DECI2_PROTOCOL) {
// null terminate
((u8*)gbuff)[sizeof(GoalMessageHeader) + msg_size] = '\0';
// copy stuff to block
protoBlock.msg_kind = gbuff->msg_kind;
protoBlock.msg_id = gbuff->msg_id;
// and mark message as received!
protoBlock.last_receive_size = -1;
} else {
// not our protocol, something has gone wrong.
MsgErr("dkernel: got a bad packet to goal proto (goal #x%lx bytes %d %d %d %ld %d)\n",
(int64_t)protoBlock.receive_buffer, protoBlock.last_receive_size,
protoBlock.receive_buffer->msg_kind, protoBlock.receive_buffer->u6,
protoBlock.receive_buffer->msg_id, msg_size);
protoBlock.last_receive_size = -1;
return -1;
}
return msg_size;
}
/*!
* Do a DECI2 send and block until it is complete.
* The message type is OUTPUT
* DONE, EXACT
*/
s32 SendFromBuffer(char* buff, s32 size) {
return SendFromBufferD(u16(ListenerMessageKind::MSG_OUTPUT), 0, buff, size);
}
/*!
* Just prepare the Ack buffer, doesn't actually connect.
* Must be called before attempting to use the socket connection.
* DONE, EXACT
*/
void InitListenerConnect() {
if (MasterDebug) {
kstrcpy(AckBufArea + sizeof(GoalMessageHeader), "ack");
}
}
/*!
* Does nothing.
* DONE, EXACT
*/
void InitCheckListener() {}
/*!
* Doesn't actually wait for a message, just checks if there's currently a message.
* Doesn't actually send an ack either.
* More accurate name would be "CheckForMessage"
* Returns pointer to the message.
* Updates MessCount to be equal to the size of the new message
* DONE, EXACT
*/
Ptr<char> WaitForMessageAndAck() {
if (!MasterDebug) {
MessCount = -1;
} else {
MessCount = ReceiveToBuffer((char*)MessBufArea.c() + sizeof(GoalMessageHeader));
}
if (MessCount < 0) {
return Ptr<char>(0);
}
return MessBufArea.cast<char>() + sizeof(GoalMessageHeader);
}
/*!
* Doesn't close anything, just print a closed message.
* DONE, EXACT
*/
void CloseListener() {
Msg(6, "dconnect: closed socket at kernel side\n");
}
+51
View File
@@ -0,0 +1,51 @@
/*!
* @file ksocket.h
* GOAL Socket connection to listener using DECI2/DSNET
*/
#ifndef JAK_KSOCKET_H
#define JAK_KSOCKET_H
#include "common/common_types.h"
#include "kmachine.h"
#include "Ptr.h"
/*!
* Update GOAL message header after receiving and verify message is ok.
* Return the size of the message in bytes (not including DECI or GOAL headers)
* Return -1 on error.
* The buffer parameter is unused.
*/
u32 ReceiveToBuffer(char* buff);
/*!
* Do a DECI2 send and block until it is complete.
* The message type is OUTPUT
*/
s32 SendFromBuffer(char* buff, s32 size);
/*!
* Just prepare the Ack buffer, doesn't actually connect.
* Must be called before attempting to use the socket connection.
*/
void InitListenerConnect();
/*!
* Does nothing.
*/
void InitCheckListener();
/*!
* Doesn't actually wait for a message, just checks if there's currently a message.
* Doesn't actually send an ack either.
* More accurate name would be "CheckForMessage"
* Returns pointer to the message.
*/
Ptr<char> WaitForMessageAndAck();
/*!
* Doesn't close anything, just print a closed message.
*/
void CloseListener();
#endif // JAK_KSOCKET_H
+28
View File
@@ -0,0 +1,28 @@
/*!
* @file ksound.cpp
* There's not much here. My guess is this was set up as framework to match the kmachine.cpp format,
* but whoever did the sound didn't use this.
*/
#include "ksound.h"
#include "kscheme.h"
#include "kdgo.h"
/*!
* Does nothing!
*/
void InitSound() {}
/*!
* Does nothing!
*/
void ShutdownSound() {}
/*!
* Set up some functions which are somewhat related to sound.
*/
void InitSoundScheme() {
make_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper);
make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy);
make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest);
}
+14
View File
@@ -0,0 +1,14 @@
/*!
* @file ksound.h
* There's not much here. My guess is this was set up as framework to match the kmachine.cpp format,
* but whoever did the sound didn't use this.
*/
#ifndef JAK_KSOUND_H
#define JAK_KSOUND_H
void InitSound();
void ShutdownSound();
void InitSoundScheme();
#endif // JAK_KSOUND_H
+29
View File
@@ -0,0 +1,29 @@
kboot
---------
usleep in KernelCheckAndDispatch
kmachine
---------
rewrite InitParms to not use std::string
InitVideo
InitMachine
CPadGetData
InstallHandler
InstallDebugHandler
dma_to_iop
DecodeTime
PutDisplayEnv
kscheme
----------
remove the test function
add memory card stuff
read_clock_code
klink
-------
v2 support
kmemcard
---------
all of it, basically.
+16
View File
@@ -0,0 +1,16 @@
/*!
* @file main.cpp
* Main for the game. Launches the runtime.
*/
#include <cstdio>
#include "runtime.h"
#include "common/versions.h"
int main(int argc, char** argv) {
while(true) {
// run the runtime in a loop so we can reset the game and have it restart cleanly
printf("gk %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
exec_runtime(argc, argv);
}
return 0;
}
+93
View File
@@ -0,0 +1,93 @@
/*!
* @file dma.cpp
* DMA Related functions for Overlord.
* This code is not great.
*/
#include <cstring>
#include <cstdio>
#include "dma.h"
#include "common/common_types.h"
#include "game/sce/iop.h"
using namespace iop;
u32 dmaid; // ID of in-progress DMA. 0 if no DMA in progress
sceSifDmaData cmd; // DMA settings
u32 strobe; // ?? mysterious sound DMA flag.
void dma_init_globals() {
dmaid = 0;
memset(&cmd, 0, sizeof(cmd));
strobe = 0;
}
/*!
* Wait for an ongoing DMA transfer to finish.
* IOP DMAs are instant in this version, so we return immediately and clear dmaid.
*/
void DMA_Sync() {
// The DMA is complete. Clear dmaid.
dmaid = 0;
// for fun, the original code
// if(dmaid != 0) {
// if(sceSifDmaStat(dmaid) > 0) {
// u32 count = 10000;
// while(sceSifDmaStat(dmaid) > 0) {
// DelayThread(10);
// count--;
// if(count == 0) {
// u32 count = 10000;
// }
// }
// }
//
// // better do that again, just to be sure i did it the first time.
// u32 count = 10000;
// while(sceSifDmaStat(dmaid) > 0) {
// DelayThread(10);
// count--;
// if(count == 0) {
// u32 count = 10000;
// }
// }
// dmaid = 0;
// }
}
/*!
* Start DMA transfer to the EE.
*/
void DMA_SendToEE(void* data, u32 size, void* dest) {
// finish previous DMA
DMA_Sync();
// setup command
cmd.mode = 0;
cmd.data = data;
cmd.addr = dest;
cmd.size = size;
// start DMA (with disabled interrupts)
CpuDisableIntr();
dmaid = sceSifSetDma(&cmd, 1);
CpuEnableIntr();
if (dmaid == 0) {
do {
printf("Got a bad DMA ID!\n"); // added
} while (true);
}
}
/*!
* SPU DMA interrupt handler.
*/
u32 intr() {
strobe = 1;
return 0;
}
// TODO DMA_SendToSPUAndSync()
+16
View File
@@ -0,0 +1,16 @@
/*!
* @file dma.h
* DMA Related functions for Overlord.
* This code is not great.
*/
#ifndef JAK_V2_DMA_H
#define JAK_V2_DMA_H
#include "common/common_types.h"
void DMA_Sync();
void DMA_SendToEE(void* data, u32 size, void* dest);
void dma_init_globals();
#endif // JAK_V2_DMA_H
+353
View File
@@ -0,0 +1,353 @@
/*!
* @file fake_iso.cpp
* This provides an implementation of IsoFs for reading a "fake iso".
* A "fake iso" is just a map file which maps 8.3 ISO file names to files in the source folder.
* This way we don't need to actually create an ISO.
*
* The game has this compilation unit, but there is nothing in it. Probably it is removed to save
* IOP memory and was only included on TOOL-only builds. So this is my interpretation of how it
* should work.
*/
#include <cstring>
#include <cassert>
#include "fake_iso.h"
#include "game/sce/iop.h"
#include "isocommon.h"
#include "overlord.h"
using namespace iop;
IsoFs fake_iso;
/*!
* Map from iso file name to file path in the src folder.
*/
struct FakeIsoEntry {
char iso_name[16];
char file_path[128];
};
static LoadStackEntry sLoadStack[MAX_OPEN_FILES]; //! List of all files that are "open"
FakeIsoEntry fake_iso_entries[MAX_ISO_FILES]; //! List of all known files
static FileRecord sFiles[MAX_ISO_FILES]; //! List of "FileRecords" for IsoFs API consumers
u32 fake_iso_entry_count; //! Total count of fake iso files
static bool read_in_progress; //! Does the ISO Thread think we're reading?
static int FS_Init(u8* buffer);
static FileRecord* FS_Find(const char* name);
static FileRecord* FS_FindIN(const char* iso_name);
static uint32_t FS_GetLength(FileRecord* fr);
static LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset);
static LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset);
static void FS_Close(LoadStackEntry* fd);
static uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len);
static uint32_t FS_SyncRead();
static uint32_t FS_LoadSoundBank(char*, void*);
static uint32_t FS_LoadMusic(char*, void*);
static void FS_PollDrive();
void fake_iso_init_globals() {
// init file lists
memset(fake_iso_entries, 0, sizeof(fake_iso_entries));
memset(sFiles, 0, sizeof(sFiles));
memset(sLoadStack, 0, sizeof(sLoadStack));
fake_iso_entry_count = 0;
// init API struct
fake_iso.init = FS_Init;
fake_iso.find = FS_Find;
fake_iso.find_in = FS_FindIN;
fake_iso.get_length = FS_GetLength;
fake_iso.open = FS_Open;
fake_iso.open_wad = FS_OpenWad;
fake_iso.close = FS_Close;
fake_iso.begin_read = FS_BeginRead;
fake_iso.sync_read = FS_SyncRead;
fake_iso.load_sound_bank = FS_LoadSoundBank;
fake_iso.load_music = FS_LoadMusic;
fake_iso.poll_drive = FS_PollDrive;
read_in_progress = false;
}
//! will hold prefix for the source folder.
static const char* next_dir = nullptr;
/*!
* Initialize the file system.
*/
int FS_Init(u8* buffer) {
(void)buffer;
// get path to next/. Will be set in the gk.sh launch script.
next_dir = std::getenv("NEXT_DIR"); // todo windows?
assert(next_dir);
// get path to next/data/fake_iso.txt, the map file.
char fakeiso_path[512];
strcpy(fakeiso_path, next_dir);
strcat(fakeiso_path, "/game/fake_iso.txt"); // todo windows paths?
// open the map.
FILE* fp = fopen(fakeiso_path, "r");
assert(fp);
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
rewind(fp);
char* fakeiso = (char*)malloc(len);
if (fread(fakeiso, len, 1, fp) != 1) {
assert(false);
}
// loop over lines
char* ptr = fakeiso;
while (*ptr) {
// newlines
while (*ptr && *ptr == '\n')
ptr++;
// comment line
if (*ptr == ';') {
while (*ptr && (*ptr != '\n')) {
ptr++;
}
continue;
}
// entry line
assert(fake_iso_entry_count < MAX_ISO_FILES);
FakeIsoEntry* e = &fake_iso_entries[fake_iso_entry_count];
int i = 0;
while (*ptr && (*ptr != ' ') && i < 16) {
e->iso_name[i] = *ptr;
ptr++;
i++;
}
while (*ptr == ' ') {
ptr++;
}
i = 0;
while (*ptr && (*ptr != '\n') && (*ptr != ' ') && i < 128) {
e->file_path[i] = *ptr;
ptr++;
i++;
}
fake_iso_entry_count++;
}
for (u32 i = 0; i < fake_iso_entry_count; i++) {
MakeISOName(sFiles[i].name, fake_iso_entries[i].iso_name);
// we don't figure out the size yet.
// this is so you can change the file without restarting the game.
sFiles[i].size = -1;
// repurpose "location" as the index.
sFiles[i].location = i;
}
free(fakeiso);
// TODO load tweak music.
return 0;
}
/*!
* Find a file on the disc and return a FileRecord.
* Find using a "normal" 8.3 name.
* This is an ISO FS API Function
*/
FileRecord* FS_Find(const char* name) {
char name_buff[16];
MakeISOName(name_buff, name);
return FS_FindIN(name_buff);
}
/*!
* Find a file on the disc. Uses the "ISO name" of the file, which is different from the normal 8.3
* name. This can be generated with MakeISOFile.
* This is an ISO FS API Function.
*/
FileRecord* FS_FindIN(const char* iso_name) {
const uint32_t* buff = (const uint32_t*)iso_name;
uint32_t count = 0;
while (count < fake_iso_entry_count) {
const uint32_t* ref = (uint32_t*)sFiles[count].name;
if (ref[0] == buff[0] && ref[1] == buff[1] && ref[2] == buff[2]) {
return sFiles + count;
}
count++;
}
printf("[FAKEISO] failed to find %s\n", iso_name);
assert(false);
return nullptr;
}
/*!
* Build a full file path for a FileRecord.
*/
static const char* get_file_path(FileRecord* fr) {
assert(fr->location < fake_iso_entry_count);
static char path_buffer[1024];
strcpy(path_buffer, next_dir);
strcat(path_buffer, "/");
strcat(path_buffer, fake_iso_entries[fr->location].file_path);
return path_buffer;
}
/*!
* Determine the length of a file. This isn't very fast, but nobody checks file sizes extremely
* quickly. This is an ISO FS API Function
*/
uint32_t FS_GetLength(FileRecord* fr) {
const char* path = get_file_path(fr);
FILE* fp = fopen(path, "rb");
assert(fp);
fseek(fp, 0, SEEK_END);
uint32_t len = ftell(fp);
rewind(fp);
fclose(fp);
return len;
}
/*!
* Open a file by putting it on the load stack.
* Set the offset to 0 or -1 if you do not want to have an offset.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
printf("[OVERLORD] FS Open %s\n", fr->name); // Added
LoadStackEntry* selected = nullptr;
// find first unused spot on load stack.
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = 0;
if (offset != -1) {
selected->location += offset;
}
return selected;
}
}
printf("[OVERLORD ISO CD] Failed to FS_Open %s\n", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Open a file by putting it on the load stack.
* Like Open, but allows an offset of -1 to be applied.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
printf("[OVERLORD] FS Open %s\n", fr->name); // Added
LoadStackEntry* selected = nullptr;
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = offset;
return selected;
}
}
printf("[OVERLORD ISO CD] Failed to FS_OpenWad %s\n", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Close an open file.
* This is an ISO FS API Function
*/
void FS_Close(LoadStackEntry* fd) {
printf("[OVERLORD] FS Close %s\n", fd->fr->name);
// close the FD
fd->fr = nullptr;
read_in_progress = false;
}
/*!
* Begin reading! Returns FS_READ_OK on success (always)
* This is an ISO FS API Function
*
* Idea: do the fopen in FS_Open and keep the file open? It would be faster.
*/
uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
assert(fd->fr->location < fake_iso_entry_count);
int32_t real_size = len;
if (len < 0) {
// not sure what this is about...
printf("[OVERLORD ISO CD] negative length warning!\n");
real_size = len + 0x7ff;
}
u32 sectors = real_size / SECTOR_SIZE;
real_size = sectors * SECTOR_SIZE;
u32 offset_into_file = SECTOR_SIZE * fd->location;
const char* path = get_file_path(fd->fr);
FILE* fp = fopen(path, "rb");
assert(fp);
fseek(fp, 0, SEEK_END);
uint32_t file_len = ftell(fp);
rewind(fp);
if (offset_into_file < file_len) {
if (offset_into_file) {
fseek(fp, offset_into_file, SEEK_SET);
}
if (offset_into_file + real_size > file_len) {
real_size = (file_len - offset_into_file);
}
if (fread(buffer, real_size, 1, fp) != 1) {
assert(false);
}
}
if (len < 0) {
len = len + 0x7ff;
}
fd->location += (len / SECTOR_SIZE);
read_in_progress = true;
return CMD_STATUS_IN_PROGRESS;
}
/*!
* Block until read completes.
*/
uint32_t FS_SyncRead() {
// FS_BeginRead is blocking, so this is useless.
if(read_in_progress) {
read_in_progress = false;
return CMD_STATUS_IN_PROGRESS;
} else {
return CMD_STATUS_READ_ERR;
}
}
/*!
* Poll drive
*/
void FS_PollDrive() {}
// TODO FS_LoadMusic
uint32_t FS_LoadMusic(char* name, void* buffer) {
(void)name;
(void)buffer;
assert(false);
}
// TODO FS_LoadSoundBank
uint32_t FS_LoadSoundBank(char* name, void* buffer) {
(void)name;
(void)buffer;
assert(false);
}
+20
View File
@@ -0,0 +1,20 @@
/*!
* @file fake_iso.h
* This provides an implementation of IsoFs for reading a "fake iso".
* A "fake iso" is just a map file which maps 8.3 ISO file names to files in the source folder.
* This way we don't need to actually create an ISO.
*
* The game has this compilation unit, but there is nothing in it. Probably it is removed to save
* IOP memory and was only included on TOOL-only builds. So this is my interpretation of how it
* should work.
*/
#ifndef JAK_V2_FAKE_ISO_H
#define JAK_V2_FAKE_ISO_H
#include "isocommon.h"
void fake_iso_init_globals();
extern IsoFs fake_iso;
#endif //JAK_V2_FAKE_ISO_H
+926
View File
@@ -0,0 +1,926 @@
/*!
* @file iso.cpp
* CD/DVD Reading.
* This is a huge mess
*/
#include <assert.h>
#include <cstring>
#include <cstdio>
#include "iso.h"
#include "iso_cd.h"
#include "iso_queue.h"
#include "iso_api.h"
#include "game/sce/iop.h"
#include "stream.h"
#include "dma.h"
#include "fake_iso.h"
#include "game/common/dgo_rpc_types.h"
using namespace iop;
u32 ISOThread();
u32 DGOThread();
u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 CopyDataToEE(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 CopyDataToIOP(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 NullCallback(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
constexpr int VAGDIR_SIZE = 0x28b4;
constexpr int LOADING_SCREEN_SIZE = 0x800000;
constexpr u32 LOADING_SCREEN_DEST_ADDR = 0x1000000;
IsoFs* isofs;
u32 iso_init_flag;
s32 sync_mbx;
s32 iso_mbx;
s32 dgo_mbx;
s32 iso_thread;
s32 dgo_thread;
s32 str_thread;
s32 play_thread;
u8 gVagDir[VAGDIR_SIZE];
u32 gPlayPos;
RPC_Dgo_Cmd sRPCBuff[1]; // todo move...
DgoCommand scmd;
void iso_init_globals() {
isofs = nullptr;
iso_init_flag = 0;
sync_mbx = 0;
iso_mbx = 0;
dgo_mbx = 0;
iso_thread = 0;
dgo_thread = 0;
str_thread = 0;
play_thread = 0;
memset(gVagDir, 0, sizeof(gVagDir));
gPlayPos = 0;
memset(sRPCBuff, 0, sizeof(sRPCBuff));
memset(&scmd, 0, sizeof(DgoCommand));
}
/*!
* Initialize the ISO Driver.
* Requires a buffer large enough to hold 3 sector (or 4 if you have DUP files)
*/
void InitDriver(u8* buffer) {
MsgPacket msg_packet;
if (!isofs->init(buffer)) {
// succesful init!
iso_init_flag = 0;
}
// you idiots, you're giving the kernel a pointer to a stack variable!
// (this is fixed in Jak 1 Japan and NTSC Greatest Hits)
SendMbx(sync_mbx, &msg_packet);
}
/*!
* Does the messagebox have a message in it?
*/
u32 LookMbx(s32 mbx) {
MsgPacket* msg_packet;
return PollMbx((&msg_packet), mbx) != KE_MBOX_NOMSG;
}
/*!
* Wait for a messagebox to have a message. This is inefficient and polls with a 100 us wait.
* This is stupid because the IOP does have much better syncronization primitives so you don't have
* to do this.
*/
void WaitMbx(s32 mbx) {
while (!LookMbx(mbx)) {
DelayThread(100);
}
}
/*!
* Initialize the ISO FileSystem system.
* Returns 0 on success.
*/
u32 InitISOFS(const char* fs_mode, const char* loading_screen) {
// in retail:
// isofs = &iso_cd;
// ADDED
if (!strcmp(fs_mode, "iso_cd")) {
isofs = &iso_cd_;
} else if (!strcmp(fs_mode, "fakeiso")) {
isofs = &fake_iso;
} else {
printf("[OVERLORD ISO] ISOFS has unknown fs_mode %s\n", fs_mode);
}
// END ADDED
// mark us as NOT initialized.
iso_init_flag = 1;
// TODO ADD
// while(!DMA_SendToSPUAndSync(&VAG_SilentLoop, 0x30, gTrapSRAM)) {
// DelayThread(1000);
// }
// INITIALIZE MESSAGE BOXES
MbxParam mbx_param;
mbx_param.attr = 0;
mbx_param.option = 0;
iso_mbx = CreateMbx(&mbx_param);
if (iso_mbx <= 0) {
return 1;
}
mbx_param.attr = 0;
mbx_param.option = 0;
dgo_mbx = CreateMbx(&mbx_param);
if (dgo_mbx <= 0) {
return 1;
}
mbx_param.attr = 0;
mbx_param.option = 0;
sync_mbx = CreateMbx(&mbx_param);
if (sync_mbx <= 0) {
return 1;
}
// INITIALIZE THREADS
ThreadParam thread_param;
thread_param.attr = TH_C;
thread_param.initPriority = 100;
thread_param.stackSize = 0x1000;
thread_param.option = 0;
thread_param.entry = (void*)ISOThread;
strcpy(thread_param.name, "ISOThread");
iso_thread = CreateThread(&thread_param);
if (iso_thread <= 0) {
return 1;
}
thread_param.attr = TH_C;
thread_param.initPriority = 98;
thread_param.stackSize = 0x800;
thread_param.option = 0;
thread_param.entry = (void*)DGOThread;
strcpy(thread_param.name, "DGOThread");
dgo_thread = CreateThread(&thread_param);
if (dgo_thread <= 0) {
return 1;
}
// thread_param.attr = TH_C;
// thread_param.initPriority = 97;
// thread_param.stackSize = 0x800;
// thread_param.option = 0;
// thread_param.entry = (void*)STRThread;
// strcpy(thread_param.name, "STRThread");
// str_thread = CreateThread(&thread_param);
// if(str_thread <= 0) {
// return 1;
// }
//
// thread_param.attr = TH_C;
// thread_param.initPriority = 97;
// thread_param.stackSize = 0x800;
// thread_param.option = 0;
// thread_param.entry = (void*)PLAYThread;
// strcpy(thread_param.name, "PLAYThread");
// play_thread = CreateThread(&thread_param);
// if(play_thread <= 0) {
// return 1;
// }
// Start the threads!
StartThread(iso_thread, 0);
StartThread(dgo_thread, 0);
// StartThread(str_thread, 0);
// StartThread(play_thread, 0);
// wait for ISO Thread to initialize
WaitMbx(sync_mbx);
// LOAD VAGDIR file
FileRecord* vagdir_file = FindISOFile("VAGDIR.AYB");
if (vagdir_file) {
LoadISOFileToIOP(vagdir_file, gVagDir, VAGDIR_SIZE);
}
FileRecord* loading_screen_file = FindISOFile(loading_screen);
if (loading_screen_file) {
LoadISOFileToEE(loading_screen_file, LOADING_SCREEN_DEST_ADDR, LOADING_SCREEN_SIZE);
}
// should be set by ISOThread to 0 before the WaitMbx(sync_mbx);
return iso_init_flag;
}
/*!
* Find a file by name. Return nullptr if it fails.
*/
FileRecord* FindISOFile(const char* name) {
return isofs->find(name);
}
/*!
* Get the length of an ISO File by FileRecord
*/
u32 GetISOFileLength(FileRecord* f) {
return isofs->get_length(f);
}
struct VagDirEntry {
union {
char name[8];
s32 name_as_s32s[2];
};
u32 unknown;
};
static_assert(sizeof(VagDirEntry) == 12, "bad size of VagDirEntry");
/*!
* Find VAG file by "name", where name is 8 bytes (chars with spaces at the end, treated as two
* s32's). Returns pointer to name in the VAGDIR file data.
*/
VagDirEntry* FindVAGFile(s32* name) {
// First 4 bytes of VAGDIR file are the number of entries.
// Next is a list of entries.
VagDirEntry* entry = (VagDirEntry*)(gVagDir + 4);
// loop over entries
for (s32 idx = 0; idx < *(s32*)gVagDir; idx++) {
// check if matching name
if (entry->name_as_s32s[0] == name[0] && entry->name_as_s32s[1] == name[1]) {
return entry;
}
entry++;
}
return nullptr;
}
/*!
* The CD/DVD Reading Thread. This is a mess.
*/
u32 ISOThread() {
// Initialize!
InitBuffers();
auto temp_buffer = AllocateBuffer(BUFFER_PAGE_SIZE);
InitDriver(temp_buffer->get_data()); // unblocks InitISOFS's WaitMbx
FreeBuffer(temp_buffer);
// main CD/DVD read loop
for (;;) {
/////////////////////////////////////
// Receive Messages and Add to Queue
/////////////////////////////////////
// receive a message
IsoMessage* msg_from_mbx;
IsoCommandLoadSingle* load_single_cmd;
s32 mbx_status = PollMbx((MsgPacket**)(&msg_from_mbx), iso_mbx);
load_single_cmd = (IsoCommandLoadSingle*)msg_from_mbx;
if (mbx_status == 0) {
// we got a new message!
// initialize fields of the message
msg_from_mbx->callback_buffer = nullptr;
msg_from_mbx->ready_for_data = 1;
msg_from_mbx->callback_function = NullCallback;
msg_from_mbx->fd = nullptr;
if (msg_from_mbx->cmd_id == LOAD_TO_EE_CMD_ID || msg_from_mbx->cmd_id == LOAD_TO_IOP_CMD_ID ||
msg_from_mbx->cmd_id == LOAD_TO_EE_OFFSET_CMD_ID) {
// A Simple File Load, add it to the queue
if (QueueMessage(msg_from_mbx, 2, "LoadSingle")) {
// if queued successfully, start by opening the file:
if (load_single_cmd->cmd_id == LOAD_TO_EE_OFFSET_CMD_ID) {
load_single_cmd->fd =
isofs->open(load_single_cmd->file_record, load_single_cmd->offset);
} else {
// open takes -1 as "no offset", same as 0.
load_single_cmd->fd = isofs->open(load_single_cmd->file_record, -1);
}
// Check to see if it opened correctly:
if (!load_single_cmd->fd) {
// nope, set the status to indicate we failed
load_single_cmd->status = CMD_STATUS_FAILED_TO_OPEN;
// remove us from the queue...
UnqueueMessage(load_single_cmd);
// and wake up whoever requested this.
ReturnMessage(load_single_cmd);
} else {
// yep, opened correctly. Set up the pointers/sizes
load_single_cmd->dst_ptr = load_single_cmd->dest_addr;
load_single_cmd->bytes_done = 0;
// by default, copy size is the full file.
load_single_cmd->length_to_copy = isofs->get_length(load_single_cmd->file_record);
if (load_single_cmd->length_to_copy == 0) {
// if we get zero for some reason, use the commanded length.
assert(false);
load_single_cmd->length_to_copy = load_single_cmd->length;
} else if (load_single_cmd->length < load_single_cmd->length_to_copy) {
// if we ask for less than the full length, use the smaller value.
load_single_cmd->length_to_copy = load_single_cmd->length;
}
// set status and callback function.
load_single_cmd->status = CMD_STATUS_IN_PROGRESS;
switch (msg_from_mbx->cmd_id) {
case LOAD_TO_EE_CMD_ID:
case LOAD_TO_EE_OFFSET_CMD_ID:
msg_from_mbx->callback_function = CopyDataToEE;
break;
case LOAD_TO_IOP_CMD_ID:
msg_from_mbx->callback_function = CopyDataToIOP;
break;
}
}
}
} else if (msg_from_mbx->cmd_id == LOAD_DGO_CMD_ID) {
// Got a DGO command. There is one LoadDGO command for the entire DGO.
if (QueueMessage(msg_from_mbx, 0, "LoadDGO")) {
// queued successfully, open the file.
load_single_cmd->fd = isofs->open(load_single_cmd->file_record, -1);
if (!load_single_cmd->fd) {
// failed to open, return error
load_single_cmd->status = CMD_STATUS_FAILED_TO_OPEN;
UnqueueMessage(load_single_cmd);
ReturnMessage(load_single_cmd);
} else {
// init DGO state machine and register as the callback.
load_single_cmd->status = CMD_STATUS_IN_PROGRESS;
((DgoCommand*)load_single_cmd)->dgo_state = DgoState::Init;
load_single_cmd->callback_function = RunDGOStateMachine;
}
}
} else {
printf("[OVERLORD] Unknown ISOThread message id 0x%x\n", msg_from_mbx->cmd_id);
}
// TODO magic number
} else if (mbx_status == -0x1a9) {
return 0;
}
////////////////////////////
// Handle Sound (TODO)
////////////////////////////
////////////////////////////
// Begin a read
////////////////////////////
IsoBufferHeader* read_buffer = nullptr;
IsoMessage* cmd_to_process = GetMessage();
if (cmd_to_process) { // okay, there's a command queued that we should process
// prep for a read !! DANGER !! - this read _may_ complete after the command is done.
// At this point we don't know if the command actually needs another read or not!
if (cmd_to_process->callback_function == ProcessVAGData) {
read_buffer = AllocateBuffer(STR_BUFFER_DATA_SIZE);
} else {
read_buffer = AllocateBuffer(BUFFER_PAGE_SIZE);
}
if (!read_buffer) {
// there aren't enough buffers. give up on this command for now.
cmd_to_process = nullptr;
} else {
// kick off read
if (cmd_to_process->callback_function == ProcessVAGData) {
cmd_to_process->status =
isofs->begin_read(cmd_to_process->fd, read_buffer->get_data(), STR_BUFFER_DATA_SIZE);
} else {
cmd_to_process->status =
isofs->begin_read(cmd_to_process->fd, read_buffer->get_data(), BUFFER_PAGE_SIZE);
}
// if we have bad status, kill read buffer
if (cmd_to_process->status != CMD_STATUS_IN_PROGRESS) {
FreeBuffer(read_buffer);
read_buffer = nullptr;
cmd_to_process = nullptr;
}
}
}
if (!cmd_to_process) {
// drive is doing nothing, make sure the DVD is still in there.
isofs->poll_drive();
}
// Deal with completed reads. NOTE - this can close files and terminate return commands!
ProcessMessageData();
if (!read_buffer) {
// didn't actually start a read, just delay for a bit I guess.
DelayThread(100);
} else {
// attempt to sync read. If we closed the file mid-read in ProcessMessageData, this returns
// an error code.
u32 read_status = isofs->sync_read();
if (read_status == CMD_STATUS_READ_ERR) {
// closed file mid-read, or the read failed. Either way we can't give this read buffer to
// anybody, so we should just free it.
FreeBuffer(read_buffer);
} else {
// read is good!
cmd_to_process->status = read_status;
// setup the buffer for the callback.
if (cmd_to_process->callback_function == ProcessVAGData) {
read_buffer->data = read_buffer->get_data();
read_buffer->data_size = STR_BUFFER_DATA_SIZE;
} else {
read_buffer->data = read_buffer->get_data();
read_buffer->data_size = BUFFER_PAGE_SIZE;
}
// add buffer to linked list of buffers.
if (!cmd_to_process->callback_buffer) {
cmd_to_process->callback_buffer = read_buffer;
} else {
auto* bh = cmd_to_process->callback_buffer;
while (bh->next) {
bh = (IsoBufferHeader*)bh->next;
}
bh->next = read_buffer;
}
}
}
} // for
return 0;
}
/*!
* Handler for DGO data buffers.
*/
u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) {
auto* cmd = (DgoCommand*)_cmd;
u32 return_value = CMD_STATUS_IN_PROGRESS;
u8* unprocessed_data = (u8*)buffer->data;
u32 bytes_left = buffer->data_size;
// loop until we've read all the data
while (bytes_left) {
// printf("run DGO in state %d (%s) with %d unprocessed buffered bytes\n", cmd->dgoState,
// names[cmd->dgoState], buffer->data_size);
switch (cmd->dgo_state) {
case DgoState::Init: // init
cmd->bytes_processed = 0;
// start by reading header.
cmd->dgo_state = DgoState::Read_Header;
cmd->finished_first_obj = 0;
cmd->want_abort = 0;
break;
case DgoState::Read_Header: // read dgo header. If we are unlucky this crosses a boundary
// and we have to do this in two chunks
{
u32 bytes_to_read = sizeof(DgoHeader) - cmd->bytes_processed;
if (bytes_to_read > bytes_left) {
bytes_to_read = bytes_left;
}
// copy to our local storage
memcpy((u8*)&cmd->dgo_header + cmd->bytes_processed, unprocessed_data, bytes_to_read);
unprocessed_data += bytes_to_read;
bytes_left -= bytes_to_read;
cmd->bytes_processed += bytes_to_read;
// if we are done with header
if (cmd->bytes_processed == sizeof(DgoHeader)) {
printf("[Overlord DGO] Got DGO file header for %s with %d objects\n",
cmd->dgo_header.name,
cmd->dgo_header.object_count); // added
cmd->bytes_processed = 0;
cmd->objects_loaded = 0;
if (cmd->dgo_header.object_count == 1) {
// if there's only one object, load to top immediately
cmd->buffer_toggle = 0;
cmd->ee_destination_buffer = cmd->buffer_heaptop;
cmd->dgo_state = DgoState::Read_Obj_Header;
} else {
// otherwise load to buffer1 first.
cmd->buffer_toggle = 1;
cmd->ee_destination_buffer = cmd->buffer1;
cmd->dgo_state = DgoState::Read_Obj_Header;
}
}
} break;
case DgoState::Finish_Obj: // we have reached the end of an object file!
{
// EE synchronization occurs here.
// we skip this if we're loading the first object so we can double buffer the
// linking/loading process and have two in flight at a time (one loading, other linking)
if (cmd->finished_first_obj) {
s32 isSync = LookMbx(sync_mbx); // did we get a "sync" message?
if (isSync) {
// if so, this means we got a CancelDGO or NextDGO
if (cmd->want_abort) {
// we got a CancelDGO.
cmd->dgo_state = DgoState::Finish_Dgo;
break;
}
} else {
// nope, ee isn't ready. bail and wait for next run.
goto cleanup_and_return;
}
}
cmd->finished_first_obj = 1;
cmd->status = CMD_STATUS_IN_PROGRESS;
// select a buffer for next time.
if (cmd->buffer_toggle == 1) {
cmd->selectedBuffer = cmd->buffer1;
} else {
cmd->selectedBuffer = cmd->buffer2;
}
// we've processed the command, go wake up the DGO RPC thread.
// doesn't terminate the command (ReleaseMessage does this, ReturnMessage just
// wakes up the caller while keeping the command alive).
ReturnMessage(cmd);
// toggle buffer
if (cmd->buffer_toggle == 1) {
cmd->ee_destination_buffer = cmd->buffer2;
cmd->buffer_toggle = 2;
} else {
cmd->ee_destination_buffer = cmd->buffer1;
cmd->buffer_toggle = 1;
}
// setup for next run
if (cmd->objects_loaded + 1 == cmd->dgo_header.object_count) {
cmd->dgo_state = DgoState::Read_Last_Obj;
} else {
cmd->dgo_state = DgoState::Read_Obj_Header;
}
break;
}
case DgoState::Read_Last_Obj: // setup load last
{
// extra sync here
s32 sync = LookMbx(sync_mbx);
if (sync) {
if (cmd->want_abort) {
cmd->dgo_state = DgoState::Finish_Dgo;
} else {
// EE ready, no abort. Nothing in flight, so we are safe to do a top load!
cmd->ee_destination_buffer = cmd->buffer_heaptop;
cmd->buffer_toggle = 0;
cmd->dgo_state = DgoState::Read_Obj_Header;
}
} else {
goto cleanup_and_return;
}
} break;
case DgoState::Read_Obj_Header: // read object file header
{
u32 bytesToRead = sizeof(ObjectHeader) - cmd->bytes_processed;
if (bytes_left < bytesToRead) {
bytesToRead = bytes_left;
}
// for now, buffer locally
memcpy((u8*)&cmd->objHeader + cmd->bytes_processed, unprocessed_data, bytesToRead);
unprocessed_data += bytesToRead;
bytes_left -= bytesToRead;
cmd->bytes_processed += bytesToRead;
// once we're done, send the header to the EE, and start reading object data
if (cmd->bytes_processed == sizeof(ObjectHeader)) {
printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent to 0x%p)\n",
cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer);
DMA_SendToEE(&cmd->objHeader, sizeof(ObjectHeader), cmd->ee_destination_buffer);
DMA_Sync();
cmd->ee_destination_buffer += sizeof(ObjectHeader);
cmd->objHeader.size = (cmd->objHeader.size + 0xf) & 0xfffffff0;
cmd->dgo_state = DgoState::Read_Obj_data;
cmd->bytes_processed = 0;
}
} break;
case DgoState::Read_Obj_data: // read object file data
{
u32 bytesToRead = cmd->objHeader.size - cmd->bytes_processed;
if (bytes_left < bytesToRead) {
bytesToRead = bytes_left;
}
// send contents directly to EE
DMA_SendToEE(unprocessed_data, bytesToRead, cmd->ee_destination_buffer);
DMA_Sync();
unprocessed_data += bytesToRead;
bytes_left -= bytesToRead;
cmd->ee_destination_buffer += bytesToRead;
cmd->bytes_processed += bytesToRead;
if (cmd->bytes_processed == cmd->objHeader.size) {
cmd->objects_loaded++;
if (cmd->objects_loaded == cmd->dgo_header.object_count) {
cmd->dgo_state = DgoState::Finish_Dgo;
} else {
cmd->dgo_state = DgoState::Finish_Obj;
cmd->bytes_processed = 0;
}
}
} break;
case DgoState::Finish_Dgo: {
// done with buffer, complete. Kill the ISO thread read.
return_value = CMD_STATUS_DONE;
goto cleanup_and_return;
}
default:
printf("unknown dgoState!\n");
}
}
printf("[DGO State Machine Complete] Out of things to read!\n");
cleanup_and_return:
if (return_value == 0) {
buffer->data = nullptr;
buffer->data_size = 0;
} else {
if (!bytes_left) {
buffer->data = nullptr;
buffer->data_size = 0;
} else {
buffer->data = unprocessed_data;
buffer->data_size = bytes_left;
}
}
return return_value;
}
/*!
* Callback for sending to EE.
*/
u32 CopyDataToEE(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
auto* cmd = (IsoCommandLoadSingle*)_cmd;
s32 bytes_to_send = cmd->length_to_copy - cmd->bytes_done;
// make sure we don't copy too much (if the buffer does not have enough data)
if (buffer_header->data_size < (u32)bytes_to_send) {
bytes_to_send = (s32)buffer_header->data_size;
}
DMA_SendToEE(buffer_header->get_data(), bytes_to_send, cmd->dest_addr);
DMA_Sync();
cmd->dest_addr += bytes_to_send;
cmd->bytes_done += bytes_to_send;
buffer_header->data = nullptr;
buffer_header->data_size = 0;
if (cmd->bytes_done == cmd->length_to_copy) {
return CMD_STATUS_DONE;
} else {
return CMD_STATUS_IN_PROGRESS;
}
}
/*!
* Callback for loading to IOP buffer.
*/
u32 CopyDataToIOP(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
auto* cmd = (IsoCommandLoadSingle*)_cmd;
s32 bytes_to_send = cmd->length_to_copy - cmd->bytes_done;
// make sure we don't copy too much (if the buffer does not have enough data)
if (buffer_header->data_size < (u32)bytes_to_send) {
bytes_to_send = (s32)buffer_header->data_size;
}
memcpy(cmd->dst_ptr, buffer_header->get_data(), bytes_to_send);
cmd->dest_addr += bytes_to_send;
cmd->bytes_done += bytes_to_send;
buffer_header->data = nullptr;
buffer_header->data_size = 0;
if (cmd->bytes_done == cmd->length_to_copy) {
return CMD_STATUS_DONE;
} else {
return CMD_STATUS_IN_PROGRESS;
}
}
/*!
* Callback which does nothing.
*/
u32 NullCallback(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
(void)_cmd;
buffer_header->data_size = 0;
return CMD_STATUS_NULL_CB;
}
/*!
* Initialize a VagCommand.
*/
void InitVAGCmd(VagCommand* cmd, u32 x) {
cmd->field_0x30 = 0;
cmd->field_0x34 = 0;
cmd->field_0x38 = 0;
cmd->field_0x3c = x;
cmd->field_0x40 = 0;
cmd->field_0x44 = 0;
cmd->field_0x48 = 0xffffffff;
gPlayPos = 0x30;
cmd->messagebox_to_reply = 0;
cmd->thread_id = 0;
}
/*!
* Byte-swap.
*/
u32 bswap(u32 in) {
return ((in >> 0x18) & 0xff) | ((in >> 8) & 0xff00) | ((in & 0xff00) << 8) | (in << 0x18);
}
/*!
* TODO - implement.
*/
u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
(void)_cmd;
(void)buffer_header;
assert(false);
return 0;
}
// TODO - StopVAG
// TODO - PauseVAG
// TODO - CalculateVAGVolumes
// TODO - UnpauseVAG
// TODO - SetVAGVol
// TODO - GetPlayPos
// TODO - UpdatePlayPos
// TODO - CheckVAGStreamProgress
void* RPC_DGO(unsigned int fno, void* _cmd, int y);
void LoadDGO(RPC_Dgo_Cmd* cmd);
void LoadNextDGO(RPC_Dgo_Cmd* cmd);
void CancelDGO(RPC_Dgo_Cmd* cmd);
/*!
* DGO RPC Thread.
*/
u32 DGOThread() {
sceSifQueueData dq;
sceSifServeData serve;
// setup RPC.
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, DGO_RPC_ID, RPC_DGO, sRPCBuff, nullptr, nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
/*!
* DGO RPC Handler.
*/
void* RPC_DGO(unsigned int fno, void* _cmd, int y) {
(void)y;
auto* cmd = (RPC_Dgo_Cmd*)_cmd;
// call appropriate handler.
switch (fno) {
case DGO_RPC_LOAD_FNO:
LoadDGO(cmd);
break;
case DGO_RPC_LOAD_NEXT_FNO:
LoadNextDGO(cmd);
break;
case DGO_RPC_CANCEL_FNO:
CancelDGO(cmd);
break;
default:
cmd->result = DGO_RPC_RESULT_ERROR;
}
return cmd;
}
/*!
* Begin loading a DGO. Returns when the first obj is loaded.
* Then will load the next obj into the second buffer.
* Then the DGO loader will block until LoadNextDGO is called.
* This approach keeps two loads in flight at a time to increase loading throughput.
* One load will be read from DVD / DMA'd to EE
* Another will be linked on the EE.
* The final load is done directly onto the heap, and isn't double buffered
* (otherwise the linking object could allocate on the heap where the final loading object is
* being copied). This avoids having to relocate the data from the temporary load buffer to the
* heap, and is the only way to make sure that the entire heap can be filled.
*/
void LoadDGO(RPC_Dgo_Cmd* cmd) {
// Find the file
FileRecord* fr = isofs->find(cmd->name);
if (!fr) {
cmd->result = DGO_RPC_RESULT_ERROR;
return;
}
// cancel an in progress command and wait for it to end.
// note - this doesn't handle a nullptr correctly, so if this actually ends up cancelling
// it will crash.
CancelDGO(nullptr);
// set up the ISO Command
scmd.cmd_id = LOAD_DGO_CMD_ID;
scmd.messagebox_to_reply = dgo_mbx;
scmd.thread_id = 0;
scmd.buffer1 = (u8*)(u64)(cmd->buffer1);
scmd.buffer2 = (u8*)(u64)(cmd->buffer2);
scmd.buffer_heaptop = (u8*)(u64)(cmd->buffer_heap_top);
scmd.fr = fr;
// send the command to ISO Thread
SendMbx(iso_mbx, &scmd);
// wait for the ReturnMessage in the DGO callback state machine.
// this happens when the first file is loaded
WaitMbx(dgo_mbx);
if (scmd.status == CMD_STATUS_IN_PROGRESS) {
// we got one, but there's more to load.
// we don't set cmd->buffer1 as it's already the correct buffer in this case -
// when there are >1 objs, we load into buffer1 first.
cmd->result = DGO_RPC_RESULT_MORE;
} else if (scmd.status == CMD_STATUS_DONE) {
// all done! make sure our reply says we loaded to the top.
cmd->result = DGO_RPC_RESULT_DONE;
cmd->buffer1 = cmd->buffer_heap_top;
scmd.cmd_id = 0;
} else {
// error.
cmd->result = DGO_RPC_RESULT_ERROR;
scmd.cmd_id = 0;
}
}
/*!
* Signal to the IOP it can keep loading and overwrite the oldest obj buffer.
* This will return when there's another loaded obj.
*/
void LoadNextDGO(RPC_Dgo_Cmd* cmd) {
if (scmd.cmd_id == 0) {
// something went wrong.
cmd->result = DGO_RPC_RESULT_ERROR;
} else {
// update heap location
scmd.buffer_heaptop = (u8*)(u64)cmd->buffer_heap_top;
// allow DGO state machine to advance
SendMbx(sync_mbx, nullptr);
// wait for another load to finish.
WaitMbx(dgo_mbx);
// another load finished, respond with the result.
if (scmd.status == CMD_STATUS_IN_PROGRESS) {
// more, use the selected buffer.
cmd->result = DGO_RPC_RESULT_MORE;
cmd->buffer1 = (u32)(u64)scmd.selectedBuffer;
} else if (scmd.status == CMD_STATUS_DONE) {
// last obj, always loaded to top.
cmd->result = DGO_RPC_RESULT_DONE;
cmd->buffer1 = cmd->buffer_heap_top;
scmd.cmd_id = 0;
} else {
cmd->result = DGO_RPC_RESULT_ERROR;
scmd.cmd_id = 0;
}
}
}
/*!
* Abort an in progress load.
*/
void CancelDGO(RPC_Dgo_Cmd* cmd) {
if (scmd.cmd_id) {
scmd.want_abort = 1;
// wake up DGO state machine with abort
SendMbx(sync_mbx, nullptr);
// wait for it to abort.
WaitMbx(dgo_mbx);
assert(cmd); // bug
cmd->result = DGO_RPC_RESULT_ABORTED;
scmd.cmd_id = 0;
}
}
// TODO - GetVAGStreamPos
// TODO - VAG_MarkLoopStart
// TODO - VAG_MarkLoopEnd
// TODO - VAG_MarkNonloopStart
// TODO - VAG_MarkNonloopEnd
+18
View File
@@ -0,0 +1,18 @@
/*!
* @file iso.h
* CD/DVD Reading.
* This is a huge mess
*/
#ifndef JAK_V2_ISO_H
#define JAK_V2_ISO_H
#include "common/common_types.h"
#include "isocommon.h"
void iso_init_globals();
FileRecord* FindISOFile(const char* name);
u32 GetISOFileLength(FileRecord* f);
u32 InitISOFS(const char* fs_mode, const char* loading_screen);
#endif // JAK_V2_ISO_H
+44
View File
@@ -0,0 +1,44 @@
#include "iso_api.h"
#include "game/sce/iop.h"
using namespace iop;
/*!
* Load a File to IOP memory (blocking)
*/
void LoadISOFileToIOP(FileRecord *file, void *addr, uint32_t length) {
printf("[OVERLORD] LoadISOFileToIOP %s, %d/%d bytes\n", file->name, length, file->size);
IsoCommandLoadSingle cmd;
cmd.cmd_id = LOAD_TO_IOP_CMD_ID;
cmd.messagebox_to_reply = 0;
cmd.thread_id = GetThreadId();
cmd.file_record = file;
cmd.dest_addr = (u8*)addr;
cmd.length = length;
SendMbx(iso_mbx, &cmd);
SleepThread();
if(cmd.status) {
cmd.length_to_copy = 0;
}
}
/*!
* Load a File to IOP memory (blocking)
*/
void LoadISOFileToEE(FileRecord *file, uint32_t addr, uint32_t length) {
printf("[OVERLORD] LoadISOFileToEE %s, %d/%d bytes\n", file->name, length, file->size);
IsoCommandLoadSingle cmd;
cmd.cmd_id = LOAD_TO_EE_CMD_ID;
cmd.messagebox_to_reply = 0;
cmd.thread_id = GetThreadId();
cmd.file_record = file;
cmd.dest_addr = (u8*)(u64)addr;
cmd.length = length;
SendMbx(iso_mbx, &cmd);
SleepThread();
if(cmd.status) {
cmd.length_to_copy = 0;
}
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef JAK_V2_ISO_API_H
#define JAK_V2_ISO_API_H
#include "isocommon.h"
void LoadISOFileToIOP(FileRecord *file, void *addr, uint32_t length);
void LoadISOFileToEE(FileRecord *file, uint32_t ee_addr, uint32_t length);
#endif //JAK_V2_ISO_API_H
+985
View File
@@ -0,0 +1,985 @@
/*!
* @file iso_cd.cpp
* IsoFs API for accessing the CD/DVD drive.
*/
#include <cstring>
#include "game/sce/iop.h"
#include "game/sce/stubs.h"
#include "iso_cd.h"
#include "isocommon.h"
#include "overlord.h"
#include "soundcommon.h"
#include "srpc.h"
// iso_cd is an implementation of the IsoFs API for loading files from a CD/DVD with an ISO and/or
// DUP filesystem.
// The DUP filesystem is a custom Naughty Dog filesystem which attempts to hide
// files. The DUP filesystem also stores all files twice on the disk and will try reading from the
// other copy if it reading the first copy encounters errors. The DUP filesystem is unused.
using namespace iop;
typedef int (*mmode_func)(int);
// Drive State
// sector to read from (for DUP files, sector of the first copy of the file)
u32 _sector;
// number of sectors to read
u32 _sectors;
// number of retries in the current read
u32 _retries;
// buffer to read into
void* _buffer;
// set to 0 or 1 to indicate if the first or second copy of DUP files should be used.
uint32_t _dupseg;
// the actual sector to read from (differs from _sector when reading second copy of DUP file)
uint32_t _real_sector;
// set 1 if the current read was continuous from the previous read (didn't require a seek)
uint32_t _continuous;
// time when the current read was started
SysClock _starttime;
// time when the current read has ended
SysClock _endtime;
// Globals
u32 gDirtyCd; // set when we're waiting on a read which has errors
u32 gNoCD; // set when we believe the game disc has been removed.
static u32 sNumFiles; // number of files (includes both ISO and DUP files)
static u32 sArea1; // Sector where the first copy of DUP files live.
static u32 sAreaDiff; // Sectors in between the first and second copy of files.
u32 pirated; // do we think the game is pirated?
mmode_func cdmmode = nullptr; // function to call to set the expected media (CD/DVD)
static sceCdRMode sNominalMode; // drive settings for "nominal" reading
static sceCdRMode sStreamMode; // drive settings for "streaming" reading
static sceCdRMode* sMode; // pointer to currently selected read mode
LoadStackEntry* sReadInfo; // LoadStackEntry for currently reading file
static u8* sSecBuffer[3]; // Buffers for a single sector
u32 add_files; // Should we add files we discover to the sFiles list?
static FileRecord sFiles[MAX_ISO_FILES]; // Info for all files on the disc
u32 CD_ID_SectorNum; // Sector of the DISK.ID file
s32 CD_ID_Sector[SECTOR_SIZE / 4]; // Contents of the DISK.ID file
s32 CD_ID_SectorSum; // Sum of the CD_ID_SECTOR array
LoadStackEntry sLoadStack[MAX_OPEN_FILES]; // List of all files that are "open"
static u32 sound_bank_loads; // might be a static variable in a function?
IsoFs iso_cd_; // IsoFs function pointers
constexpr int TIME_SIZE = 16; // how many samples for read timing
s32 _times[TIME_SIZE]; // read timing data
s32 _timesix;
s32 _tsamps[2];
s32 _tkps[2];
s32 gLastSpeed;
s32 gDiskSpeed[2];
s32 gDupSeg;
u32 ReadU32(u8* buffer);
u32 ReadSectorsNow(uint32_t sector, uint32_t len, void* buffer);
u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID);
void DecodeDUP(u8* buffer);
void LoadMusicTweaks(u8* buffer);
void LoadDiscID();
u32 CheckDiscID();
void SetRealSector();
void CD_WaitReturn();
static int FS_Init(u8* buffer);
static FileRecord* FS_Find(const char* name);
static FileRecord* FS_FindIN(const char* iso_name);
static uint32_t FS_GetLength(FileRecord* fr);
static LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset);
static LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset);
static void FS_Close(LoadStackEntry* fd);
static uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len);
static uint32_t FS_SyncRead();
static uint32_t FS_LoadSoundBank(char*, void*);
static uint32_t FS_LoadMusic(char*, void*);
static void FS_PollDrive();
void iso_cd_init_globals() {
_sector = 0;
_sectors = 0;
_retries = 0;
gDirtyCd = 0;
gNoCD = 0;
_dupseg = 0;
_real_sector = 0;
_continuous = 0;
_buffer = nullptr;
memset(&_starttime, 0, sizeof(SysClock));
memset(&_endtime, 0, sizeof(SysClock));
sNumFiles = 0;
sArea1 = 0;
sAreaDiff = 0;
pirated = 0;
cdmmode = nullptr;
sNominalMode.trycount = 0;
sNominalMode.spindlctrl = 1;
sNominalMode.datapattern = 0;
sNominalMode.pad = 0;
sStreamMode.trycount = 0xf;
sStreamMode.spindlctrl = 0;
sStreamMode.datapattern = 0;
sStreamMode.pad = 0;
sMode = &sStreamMode;
sReadInfo = nullptr;
memset(sSecBuffer, 0, sizeof(sSecBuffer));
add_files = 0;
memset(sFiles, 0, sizeof(sFiles));
CD_ID_SectorNum = 0;
memset(CD_ID_Sector, 0, sizeof(CD_ID_Sector));
CD_ID_SectorSum = 0;
memset(sLoadStack, 0, sizeof(sLoadStack));
sound_bank_loads = 0;
iso_cd_.init = FS_Init;
iso_cd_.find = FS_Find;
iso_cd_.find_in = FS_FindIN;
iso_cd_.get_length = FS_GetLength;
iso_cd_.open = FS_Open;
iso_cd_.open_wad = FS_OpenWad;
iso_cd_.close = FS_Close;
iso_cd_.begin_read = FS_BeginRead;
iso_cd_.sync_read = FS_SyncRead;
iso_cd_.load_sound_bank = FS_LoadSoundBank;
iso_cd_.load_music = FS_LoadMusic;
iso_cd_.poll_drive = FS_PollDrive;
memset(_times, 0, sizeof(_times));
memset(_tsamps, 0, sizeof(_tsamps));
memset(_tkps, 0, sizeof(_tkps));
_timesix = 0;
memset(gDiskSpeed, 0, sizeof(gDiskSpeed));
gLastSpeed = 0;
gDupSeg = 0;
}
/*!
* Read unaligned uint32_t from buffer. ISO file systems may have 32-bit values that aren't word
* aligned. DONE, EXACT
*/
uint32_t ReadU32(u8* data) {
return (uint32_t)data[0] + (((uint32_t)data[1]) * 0x100) + (((uint32_t)data[2]) * 0x10000) +
(((uint32_t)data[3]) * 0x1000000);
}
/*!
* Read from disc, immediately (blocking), into a local buffer. Will retry if needed, setting
* gDirtyCd. This does not use the DUP file system or drive state, so this should not be used
* outside of initialization. Will clear gDirtyCd on successful read. Returns 1 on success, 0 on
* sceCdRead failure, or otherwise retries forever until sceCdGetError is OK.
* The length is in terms of sectors.
* DONE, EXACT
*/
u32 ReadSectorsNow(uint32_t sector, uint32_t len, void* buffer) {
// reset the sector state to break any continuous reads in progress
_sector = 0;
// retry loop for read
while (true) {
// Start async read from DVD...
if (sceCdRead(sector, len, buffer, sMode) == 0) {
// if this fails, it indicates catastrophic failure of the DVD drive, so give up immediately.
return 0;
}
// Wait for read to finish (0x00 is blocking)
sceCdSync(0);
// check for error
if (sceCdGetError() == 0) {
// no error, we are good!
break;
}
// we got an error. Try again, and set a dirty flag so the EE knows we're having trouble
gDirtyCd = 1;
}
// success! clear the dirty flag and return!
gDirtyCd = 0;
return 1;
}
/*!
* Read ISO file systems directory tree, adding files to the filerecord table if add_files is set.
* Regardless of add_files is not set, it will be set for folders under NAUGHTY.DOG.
* This feature is used on demos with multiple games and Jak is in the NAUGHTY.DOG folder.
* Recursively walks the tree.
* There is a stack of single sector buffers used to recursively read the directories.
* Returns 1 on success and 0 on failure.
* Running out of sector buffers because of too many nested folders is considered success?
* DONE
*/
u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID) {
if (secBufID < 3) {
// grab our buffer from the stack
u8* buffer = sSecBuffer[secBufID];
uint32_t lsector = sector;
int32_t lsize = size;
// loop over sector reads
while (lsize > 0) {
// ISO low-level read
if (!ReadSectorsNow(lsector, 1, buffer)) {
printf("[OVERLORD ISO CD] Failed to read sector in ReadDirectory\n");
return 0;
}
u8* lbuffer = buffer;
// loop over stuff in the sector
while ((*lbuffer != 0) && (lbuffer < buffer + SECTOR_SIZE)) {
u8 dir_record_size = *lbuffer;
if ((lbuffer[0x21] != 0) && (lbuffer[0x21] != 1)) { // skip over whatever these things are
uint32_t extent = ReadU32(lbuffer + 2);
uint32_t dir_size = ReadU32(lbuffer + 10);
uint32_t name_len = lbuffer[0x20];
bool is_directory = true;
if ((lbuffer[0x1f + name_len] == ';') && (lbuffer[0x20 + name_len] == '1')) {
is_directory = false;
}
if (is_directory) {
if (!add_files) {
// don't add file by default, but add files if we recurse in the NAUGHTY.DOG folder
if (!memcmp(lbuffer + 0x21, "NAUGHTY.DOG", 0xb)) {
add_files = true;
ReadDirectory(extent, dir_size, secBufID + 1);
add_files = false;
}
} else {
// otherwise just recurse
ReadDirectory(extent, dir_size, secBufID + 1);
}
} else {
if (sNumFiles == MAX_ISO_FILES) {
printf("[OVERLORD ISO CD] There are too many files on the disc!\n");
return 0;
}
if (add_files) {
lbuffer[0x1f + name_len] = 0; // null terminate the name
MakeISOName(sFiles[sNumFiles].name, (char*)(lbuffer + 0x21));
sFiles[sNumFiles].location = extent;
sFiles[sNumFiles].size = dir_size;
sNumFiles++;
}
}
}
lbuffer += dir_record_size;
}
lsector++;
lsize -= 0x800;
}
} else {
printf("[OVERLORD ISO CD] ReadDirectory ran out of sector buffers!\n");
}
return 1;
}
struct DupIndexEntry {
// 20 bytes
char name[12];
u32 location;
u32 size;
};
/*!
* DUP code. The DUP files aren't on the disc, so this doesn't do anything.
* This would allow for hidden files that aren't in the standard ISO format, presumably to make
* pirating harder? The DUP files store the location of these hidden files. Also it support having
* two copies of some files. There are two areas, both of which are identical. But it was never
* used.
*/
void DecodeDUP(u8* buffer) {
(void)buffer;
// set sArea1 to point to an impossibly large sector - if DUP initialization fails this means no
// file will be in the DUP zones.
sArea1 = 0x7fffffff;
char iso_name[16];
// all three of these will fail.
MakeISOName(iso_name, "Z1INDEX.DUP");
FileRecord* index_file = FS_FindIN(iso_name);
MakeISOName(iso_name, "Z3AREA1.DUP");
FileRecord* area1_file = FS_FindIN(iso_name);
MakeISOName(iso_name, "Z5AREA2.DUP");
FileRecord* area2_file = FS_FindIN(iso_name);
// Note - this reads 4 sectors, but the buffer only has enough room for 3 sectors.
// So this code would likely cause a crash if it was run.
// Maybe this is why it was removed?
// Or maybe there used to be 4 init buffers, but one was removed once they gave up on DUP?
if (index_file && area1_file && area2_file && ReadSectorsNow(index_file->location, 4, buffer)) {
sArea1 = area1_file->location; // marks start of 1st zone
sAreaDiff = area2_file->location - area1_file->location; // difference between zones
// make sure we have enough room to store all entries
if (sNumFiles + *(s32*)(buffer) <= MAX_ISO_FILES) {
// read entries
DupIndexEntry* dup_entries = (DupIndexEntry*)(((u8*)buffer) + 4);
for (int i = 0; i < *(s32*)(buffer); i++) {
*(s32*)(&sFiles[sNumFiles].name) = *(s32*)(&dup_entries[i].name);
*(s32*)(&sFiles[sNumFiles].name + 4) = *(s32*)(&dup_entries[i].name + 4);
*(s32*)(&sFiles[sNumFiles].name + 8) = *(s32*)(&dup_entries[i].name + 8);
sFiles[sNumFiles].size = dup_entries[i].size;
sFiles[sNumFiles].location = dup_entries[i].location;
sNumFiles++;
}
}
}
}
/*!
* Load the TWEAKVAL.MUS file into the gMusicTweakInfo file.
* Only works if the file is less than 1 sector long.
* If loading fails, writes a 0 to the first 32-bits of gMusicTweakInfo
* @param buffer a sector buffer which will be used
*/
void LoadMusicTweaks(u8* buffer) {
char iso_name[16];
MakeISOName(iso_name, "TWEAKVAL.MUS");
FileRecord* fr = FS_FindIN(iso_name);
if (!fr || !ReadSectorsNow(fr->location, 1, buffer)) {
*(s32*)gMusicTweakInfo = 0;
printf("[OVERLORD ISO CD] Failed to load music tweaks!\n");
} else {
memcpy(gMusicTweakInfo, buffer, MUSIC_TWEAK_SIZE);
}
}
/*!
* Load the DISK ID file and compute the sum.
* This is used as a checksum to make sure the disc is correct.
* A literal sum is not a great checksum
* Also, this function name and the file on the disc itself spell dis{c,k} differently.
*
* If there is no DISK_ID.DIZ file, uses whatever is stored at 0x400 instead.
*/
void LoadDiscID() {
char iso_name[16];
MakeISOName(iso_name, "DISK_ID.DIZ");
FileRecord* fr = FS_FindIN(iso_name);
if (!fr) {
printf(
"[OVERLORD ISO CD] LoadDiscID failed to find DISK_ID.DIZ, using sector 0x400 instead!\n");
CD_ID_SectorNum = 0x400;
} else {
CD_ID_SectorNum = fr->location;
}
ReadSectorsNow(CD_ID_SectorNum, 1, &CD_ID_Sector);
CD_ID_SectorSum = 0;
for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) {
CD_ID_SectorSum += CD_ID_Sector[i];
}
printf("[OVERLORD] DISK_ID.DIZ OK 0x%x\n", CD_ID_SectorSum);
}
/*!
* Verify that the DISK ID file has not changed. Returns 1 if it is good.
*/
u32 CheckDiskID() {
if (ReadSectorsNow(CD_ID_SectorNum, 1, CD_ID_Sector) == 0) {
// failed to read CD ID data
return 0;
}
int sum = 0;
for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) {
sum += CD_ID_Sector[i];
}
return sum == CD_ID_SectorSum;
}
/*!
* Set _real_sector in preparation for a read, based on the requested _sector.
* This has logic for a system which has a double copy of some data on the disc and can pick between
* two different copies. This selection is done with the dupseg flag.
*/
void SetRealSector() {
// if we are below sArea1, it's not a duplicated file, so ignore the dupseg flag and read directly
if (_sector < sArea1 || _dupseg == 0) {
_real_sector = _sector;
} else {
// it's a duplicated file, and duplicate read is enabled, so get the area 2 sector.
_real_sector = _sector + sAreaDiff;
printf("[OVERLORD] Warning, adjusting real sector in SetRealSector\n");
}
// we suspect the game is pirated, load the wrong sector.
if (pirated) {
_real_sector += 3;
printf("pirated!\n"); // added, so I don't trip this by accident!
}
}
/*!
* Initialize the ISO CD system and builds file record table.
* This is an ISO_FS API Function.
* Also loads music tweaks/DISK ID
* @param buffer : a buffer larger enough to hold 3 sectors
* this buffer can be freed immediately this returns
* Return 0 on success.
*/
int FS_Init(u8* buffer) {
// determine disk type
int disk_type = SCECdDETCT;
while (disk_type = sceCdGetDiskType(), disk_type == SCECdDETCT) {
// This SleepThread will cause the Overlord initialization to lock up. It's called with an
// argument of 10000, but SleepThread accepts no arguments. Probably they meant to call
// DelayThread. It ends up working because the drive already knows the disk type at this point.
SleepThread();
}
// what is this. it's crazy. why?
if (disk_type <= SCECdPS2DVD || disk_type < SCECdCDDA || disk_type <= SCECdDVDV ||
disk_type != SCECdIllegalMedia) {
// we are actually using the CD drive, so set the mmode function to the SCE function.
// This is called in FS_LoadMusic. If you call this with the wrong media type, it locks up.
// I guess this is an attempt at making convoluted anti-piracy code so it's harder to find
// calls to sceCdMmode with static analysis. But they left in debug symbols and the variable
// is called "cdmmode", which is not a very sneaky way to hide it! (At least on the EE it's
// called aybabtu and is a GOAL symbol which is way harder to figure out.) Also it seems like
// the primary mode of piracy they were concerned with is somebody swapping a DVD with a CD?
cdmmode = sceCdMmode;
// verify the disc is a DVD.
sceCdMmode(SCECdDVD);
// set up sector buffers used for initialization reads.
for (int i = 0; i < 3; i++) {
sSecBuffer[i] = buffer + i * SECTOR_SIZE;
}
// read primary volume descriptor into buffer
if (!ReadSectorsNow(0x10, 1, sSecBuffer[0])) {
printf("[OVERLORD ISO CD] Failed to read primary volume descriptor\n");
return 1;
}
// check volume descriptor identifier
if (memcmp(sSecBuffer[0] + 1, "CD001", 5)) {
printf("[OVERLORD ISO CD] Got the wrong volume descriptor identifier\n");
char* cptr = (char*)sSecBuffer[0] + 1;
printf("%c%c%c%c%c\n", cptr[0], cptr[1], cptr[2], cptr[3], cptr[4]);
return 1;
}
// read path table into buffer
uint32_t path_table_sector = ReadU32(sSecBuffer[0] + 0x8c);
if (!ReadSectorsNow(path_table_sector, 1, sSecBuffer[0])) {
printf("[OVERLORD ISO CD] Failed to read path table\n");
return 1;
}
// read path table's extent into buffer
uint32_t path_table_extent = ReadU32(sSecBuffer[0] + 2);
if (!ReadSectorsNow(path_table_extent, 1, sSecBuffer[0])) {
printf("[OVERLORD ISO CD] Failed to read path table extent\n");
}
// read root directory
add_files = true;
uint32_t dir_size = ReadU32(sSecBuffer[0] + 10);
if (!ReadDirectory(path_table_extent, dir_size, 0)) {
printf("[OVERLORD ISO CD] Failed to ReadDirectory\n");
return 1;
}
// load filesystem stuff
DecodeDUP(sSecBuffer[0]);
LoadMusicTweaks(sSecBuffer[0]);
LoadDiscID();
// there's some sort of weird loop here over all file that does nothing.
// my guess is its some commented out print thing?
// empty load stack
for (int i = 0; i < MAX_OPEN_FILES; i++) {
sLoadStack[i].fr = nullptr;
}
// kill sector buffers
for (int i = 0; i < 3; i++) {
sSecBuffer[i] = nullptr;
}
return 0;
} else {
printf("[OVERLORD ISO CD] Bad Media Type\n");
return 1;
}
}
/*!
* Find a file on the disc and return a FileRecord.
* This is an ISO FS API Function
*/
FileRecord* FS_Find(const char* name) {
char name_buff[16];
MakeISOName(name_buff, name);
return FS_FindIN(name_buff);
}
/*!
* Find a file on the disc. Uses the ISO name of the file.
* This can be generated with MakeISOFile
* This is an ISO FS API Function
* There is a weird anti-piracy thing in here to prevent people from making copies with less than
* 1 GB of data? I guess you could remove the audio in languages you don't care about and put in
* on a CD, and this would block this from happening.
*/
FileRecord* FS_FindIN(const char* iso_name) {
const uint32_t* buff = (const uint32_t*)iso_name;
for (;;) { // this loop will spin forever if you have < 1 GB of files
uint32_t size = 0; // total sum of file sizes
uint32_t count = 0;
while (count < sNumFiles) {
const uint32_t* ref = (uint32_t*)sFiles[count].name;
if (ref[0] == buff[0] && ref[1] == buff[1] && ref[2] == buff[2]) {
return sFiles + count;
}
size += sFiles[count].size;
count++;
}
// if we get here, we haven't found the file, we should return 0 to indicate we don't have it
// however, if we haven't found 1 GB of files after searching the whole thing
// we assume that we've pirated the game and should continue looping
// Note that the game attempts to load DUP files which will fails and will hit this condition.
buff +=
3; // to make this look less suspicious, lets increment buff. also will crash eventually.
if (0x3fffffff < size) {
return nullptr; // we got 1 GB of files, okay to return
}
// we didn't get 1 GB of files, you're a pirate.
printf("pirated!\n"); // i added this so i know if it hangs here
}
}
/*!
* Determine the length of a file.
* This is an ISO FS API Function
*/
uint32_t FS_GetLength(FileRecord* fr) {
return fr->size;
}
/*!
* Open a file by putting it on the load stack.
* Set the offset to 0 or -1 if you do not want to have an offset.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
printf("[OVERLORD] FS Open %s\n", fr->name); // Added
LoadStackEntry* selected = nullptr;
// find first unused spot on load stack.
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = fr->location;
if (offset != -1) {
selected->location += offset;
}
return selected;
}
}
printf("[OVERLORD ISO CD] Failed to FS_Open %s\n", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Open a file by putting it on the load stack.
* Like Open, but allows an offset of -1 to be applied.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
printf("[OVERLORD] FS Open %s\n", fr->name);
LoadStackEntry* selected = nullptr;
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = fr->location + offset;
return selected;
}
}
printf("[OVERLORD ISO CD] Failed to FS_OpenWad %s\n", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Close an open file.
* This is an ISO FS API Function
*/
void FS_Close(LoadStackEntry* fd) {
printf("[OVERLORD] FS Close %s\n", fd->fr->name);
if (fd == sReadInfo) {
// the file is currently being read, so lets try to finish out the read, if possible.
int count = 0;
// the non-blocking sync, so we don't get stuck here on a catastrophic error.
while (sceCdSync(1)) {
DelayThread(1000); // wait 1 ms and allow other stuff to run.
count++;
if (count == 1000) { // waited too long to close this file
sceCdBreak(); // interrupt the read
break;
}
}
sReadInfo = nullptr;
}
// close the FD
fd->fr = nullptr;
}
/*!
* Begin reading! Returns FS_READ_OK on success (always)
* This is an ISO FS API Function
*/
uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
// set the reading state:
// I guess continuous stream buffer reads don't count as continuous?
_continuous = (len == BUFFER_PAGE_SIZE) && (fd->location == (_sector + _sectors));
_sector = fd->location;
int32_t real_size = len;
if (len < 0) {
// not sure what this is about...
printf("[OVERLORD ISO CD] negative length warning!\n");
real_size = len + 0x7ff;
}
_sectors = real_size >> 11;
_retries = 0;
_buffer = buffer;
GetSystemTime(&_starttime);
// compute _real_sector
SetRealSector();
while (!sceCdRead(_real_sector, _sectors, _buffer, sMode)) {
// error starting the read. this is bad and possibly indicates somebody took the CD out.
// lets wait for the CD to be ready again...
CD_WaitReturn();
_retries++;
if (_sector >= sArea1) {
// the original file we tried to read is duplicated...
// so lets try reading the other copy of it!
_dupseg = 1 - _dupseg;
_continuous = 0; // mark as noncontinuous read
SetRealSector(); // recompute!
}
}
// ??? this is strangely set up.
if (len < 0) {
len = len + 0x7ff;
}
fd->location += (len >> 0xb);
// set sReadInfo to point to the current read.
sReadInfo = fd;
return CMD_STATUS_IN_PROGRESS;
}
/*!
* Wait for current read to complete!
* This is an ISO FS API Function
* @return
*/
uint32_t FS_SyncRead() {
// make sure a read is actually in progress
if (!sReadInfo) {
return CMD_STATUS_READ_ERR;
}
// remember when we start doing a SyncRead.
SysClock now;
GetSystemTime(&now);
// block and wait for completion
sceCdSync(0);
// remember when we sync.
GetSystemTime(&_endtime);
// Loop to check if read succeed and start an additional read if not.
while (sceCdGetError()) {
// no, it didn't, lets retry
_retries++;
// toggle dupseg
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
// try until a read starts...
while (!sceCdRead(_real_sector, _sectors, _buffer, sMode)) {
// read start failed, possibly CD is removed
CD_WaitReturn();
// retry!
_retries++;
// toggle dupseg if possible
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
}
// read has started
// set dirty cd to indicate we had trouble
gDirtyCd = 1;
// wait for read to finish...
sceCdSync(0);
// if the read/sync fails, the loop will go again.
}
// Read complete! Mark CD as not dirty and clear active read!
gDirtyCd = 0;
sReadInfo = nullptr;
// Optionally do some timing checks
// (note that these never run because we don't have DUP files)
// continuous read with no failures from dup zone
// more than half the time spent in FS_SyncRead
// Basically it tries to learn about which segment does worse in "too slow" reads
// by averaging all historical too slow reads. Once the other is winning by a certain amount
// it will swap. It will also swap if there isn't enough samples on one.
if (_retries == 0 && _continuous && _sectors >= sArea1 &&
(_endtime.hi - _starttime.hi) / 2 < (_endtime.hi - now.hi)) {
// record the time
_times[_timesix++] = _endtime.hi - _starttime.hi;
// if we filled the time buffer
if (_timesix == TIME_SIZE) {
// compute total time
s32 total_time = 0;
for (s32 i = 0; i < TIME_SIZE; i++) {
total_time += _times[i];
}
// determine read speed
gLastSpeed = 0x69780000 / (total_time >> 4); // todo - work out this constant
// add to average kps for this seg
if (_tsamps[_dupseg] < 0x40000) {
_tkps[_dupseg] = _tkps[_dupseg] + gLastSpeed;
_tsamps[_dupseg] = _tsamps[_dupseg];
}
// average speed of this segment
gDiskSpeed[_dupseg] = _tkps[_dupseg] / _tsamps[_dupseg];
_timesix = 0;
if (_tsamps[0] < 8) {
// not much information about segment 0
if (_dupseg) {
// and we aren't reading segment 0...
// so let's read segment 0
_dupseg = 0;
_sector = 0;
}
} else {
// got enough info about segment 0.
if (_tsamps[1] < 8) {
// not enough information about segment 1
if (!_dupseg) {
// and not reading, so lets read it.
_dupseg = 1;
_sector = 0;
}
} else {
// enough info about both.
if ((_tkps[1] / _tsamps[1] + 0x32) < (_tkps[0] / _tsamps[0])) {
// section 0 wins by at least 0x32, lets use it if we aren't already
if (_dupseg) {
_dupseg = 0;
_sector = 0;
}
} else if ((_tkps[0] / _tsamps[0] + 0x32) < (_tkps[1] / _tsamps[1])) {
if (!_dupseg) {
_dupseg = 1;
_sector = 0;
}
}
}
}
// set our current decision in a global for the EE to read.
gDupSeg = _dupseg;
}
}
return CMD_STATUS_IN_PROGRESS;
}
/*!
* Load a SoundBank now. Doesn't do any fancy read stuff.
*/
uint32_t FS_LoadSoundBank(char* name, void* buffer) {
char full_name[32]; // may actually be 8, but lets be safe
// ??? todo this is probably a field of the buffer.
u32 header_size;
if (*(s32*)(((u8*)buffer) + 0x14) == 0x65) {
header_size = 1;
} else {
header_size = 10;
}
if (strlen(name) > 16) {
printf("[OVERLORD ISO CD] FS_LoadSoundBank has an invalid name!\n");
}
// append .sbk
strcpy(full_name, name);
strcat(full_name, ".sbk");
FileRecord* fr = FS_Find(full_name);
if (!fr) {
printf("[OVERLORD ISO CD] FS_LoadSoundBank cannot find bank %s, loading empty instead.\n",
full_name);
fr = FS_Find("empty1.sbk");
}
// hack to do a read now (the Sound Bank loads bypass all the other fancy loading stuff evidently)
_sector = fr->location;
SetRealSector();
// loop until we read header successfully.
// don't set retries or dirty cd
while (!ReadSectorsNow(_real_sector, header_size, buffer)) {
// ReadSectorsNow will only return if the read fails to start. in this case we assume the disc
// was removed:
CD_WaitReturn();
// we don't increment retries...
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
}
// now have the sound library do a load.
// (this time we set dirty cd if it fails, but no retries)
auto load_status = snd_BankLoadByLoc(_real_sector + header_size, 0);
while (!load_status && snd_GetLastLoadError() < 0x100) {
CD_WaitReturn();
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
load_status = snd_BankLoadByLoc(_real_sector + header_size, 0);
if (!load_status) {
gDirtyCd = 1;
}
}
gDirtyCd = 0;
// pirate check sometimes
sound_bank_loads++;
if ((sound_bank_loads & 7) == 0) {
pirated = 1;
// check that one file is past sector 0x80000 (approx 1 GB)
for (u32 i = 0; i < sNumFiles; i++) {
if (sFiles[i].location + (sFiles[i].size >> 11) > 0x80000) {
pirated = 0;
}
}
}
snd_ResolveBankXREFS();
PrintBankInfo(buffer);
_sector = 0;
// ??? todo this is probably a field of the buffer.
*(s32*)(((u8*)buffer) + 0x10) = load_status;
return 0;
}
/*!
* Load a music file. Load now, doesn't do fancy reading stuff.
*/
uint32_t FS_LoadMusic(char* name, void* buffer) {
char full_name[32]; // may actually be 8, but lets be safe
if (strlen(name) > 16) {
printf("[OVERLORD ISO CD] FS_LoadMusic has an invalid name!\n");
}
// append .mus
strcpy(full_name, name);
strcat(full_name, ".mus");
FileRecord* fr = FS_Find(full_name);
if (!fr) {
printf("[OVERLORD ISO CD] FS_LoadMusic cannot find bank %s.\n", full_name);
return 6;
}
_sector = fr->location;
SetRealSector();
// another "piracy" check to make sure the media is the correct type...
(*cdmmode)(SCECdDVD);
// now have the sound library do a load.
auto load_status = snd_BankLoadByLoc(_real_sector, 0);
// TODO magic constant 0x100
while (!load_status && snd_GetLastLoadError() < 0x100) {
CD_WaitReturn();
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
load_status = snd_BankLoadByLoc(_real_sector, 0);
if (!load_status) {
gDirtyCd = 1;
}
}
gDirtyCd = 0;
snd_ResolveBankXREFS();
_sector = 0;
*(s32*)buffer = load_status;
return 0;
}
/*!
* Make sure the drive is happy.
* NOTE - only call this when the drive should have nothing to do!
*/
void FS_PollDrive() {
if (sceCdDiskReady(1) == SCECdNotReady) { // non-blocking
CD_WaitReturn();
}
}
/*!
* Wait for the game CD/DVD to be put back in the playstation.
* Only call this if you think the CD/DVD has been removed, as requires a seek.
*/
void CD_WaitReturn() {
gNoCD = 1;
do {
while (sceCdDiskReady(1) == SCECdNotReady) {
}
} while (!CheckDiskID());
gNoCD = 0;
}
+15
View File
@@ -0,0 +1,15 @@
/*!
* @file iso_cd.cpp
* IsoFs API for accessing the CD/DVD drive.
*/
#ifndef JAK_ISO_CD_H
#define JAK_ISO_CD_H
#include "common/common_types.h"
#include "iso.h"
void iso_cd_init_globals();
extern IsoFs iso_cd_;
#endif // JAK_ISO_CD_H
+377
View File
@@ -0,0 +1,377 @@
#include <cstring>
#include <cstdio>
#include <cassert>
#include "game/sce/iop.h"
#include "iso_queue.h"
#include "isocommon.h"
using namespace iop;
constexpr int N_BUFFERS = 4;
constexpr int N_STR_BUFFERS = 1;
constexpr int N_VAG_CMDS = 64;
struct IsoBuffer {
IsoBufferHeader header;
u8 data[BUFFER_PAGE_SIZE];
};
struct IsoStrBuffer {
IsoBufferHeader header;
u8 data[STR_BUFFER_DATA_SIZE];
};
static IsoBuffer sBuffer[N_BUFFERS];
static IsoStrBuffer sStrBuffer[N_STR_BUFFERS];
static IsoBuffer* sFreeBuffer;
static IsoStrBuffer* sFreeStrBuffer;
PriStackEntry gPriStack[N_PRIORITIES];
u32 vag_cmd_cnt;
u32 vag_cmd_used;
u32 max_vag_cmd_cnt;
VagCommand vag_cmds[N_VAG_CMDS];
static s32 sSema;
IsoBufferHeader* TryAllocateBuffer(uint32_t size);
void ReleaseMessage(IsoMessage *cmd);
void FreeVAGCommand(VagCommand* cmd);
void iso_queue_init_globals() {
memset(sBuffer, 0, sizeof(sBuffer));
memset(sStrBuffer, 0, sizeof(sStrBuffer));
sFreeBuffer = nullptr;
sFreeStrBuffer = nullptr;
for(auto& e : gPriStack) e.reset();
vag_cmd_cnt = 0;
vag_cmd_used = 0;
max_vag_cmd_cnt = 0;
memset(vag_cmds, 0, sizeof(vag_cmds));
sSema = 0;
}
void PriStackEntry::reset() {
for(auto& c : cmds) c = nullptr;
n = 0;
for(auto& x : names) x.clear();
}
void InitBuffers() {
// chain all buffers together and set them as free.
for(uint32_t i = 0; i < N_BUFFERS; i++) {
sBuffer[i].header.data = nullptr;
sBuffer[i].header.data_size = 0;
sBuffer[i].header.buffer_size = BUFFER_PAGE_SIZE;
sBuffer[i].header.next = &sBuffer[i+1].header;
}
sBuffer[N_BUFFERS - 1].header.next = nullptr;
sFreeBuffer = &sBuffer[0];
for(uint32_t i = 0; i < N_STR_BUFFERS; i++) {
sStrBuffer[i].header.data = nullptr;
sStrBuffer[i].header.data_size = 0;
sStrBuffer[i].header.buffer_size = STR_BUFFER_DATA_SIZE;
sStrBuffer[i].header.next = &sStrBuffer[i + 1].header;
}
sStrBuffer[N_STR_BUFFERS - 1].header.next = nullptr;
sFreeStrBuffer = &sStrBuffer[0];
// TODO - this has options
SemaParam params;
params.attr = 1;
params.max_count = 1;
params.option = 1;
params.init_count = 0;
sSema = CreateSema(&params);
if(sSema < 0) {
for(;;) {
printf("[OVERLORD] VAG Semaphore creation failed!\n");
}
}
}
/*!
* Allocate a buffer of the given size. If not possible, loop forever. Size must be BUFFER_PAGE_SIZE or STR_BUFFER_DATA_SIZE,
*/
IsoBufferHeader* AllocateBuffer(uint32_t size) {
IsoBufferHeader *buffer = TryAllocateBuffer(size);
if(buffer) {
printf("--------------- allocated buffer size %d\n", size);
return buffer;
} else {
while (true) {
printf("[OVERLORD ISO QUEUE] Failed to allocate buffer!\n");
}
}
}
/*!
* Allocate a buffer of given size. If the size isn't BUFFER_PAGE_SIZE, you get a streaming buffer (STR_BUFFER_DATA_SIZE).
* If no allocation can be done, return nullptr.
*/
IsoBufferHeader* TryAllocateBuffer(uint32_t size) {
IsoStrBuffer* top_str = sFreeStrBuffer;
IsoBuffer* top_buff = sFreeBuffer;
if(size == BUFFER_PAGE_SIZE) {
if(sFreeBuffer) {
auto next = sFreeBuffer->header.next;
sFreeBuffer->header.data = nullptr;
sFreeBuffer = (IsoBuffer*)next;
top_buff->header.data_size = 0;
top_buff->header.next = nullptr;
return (IsoBufferHeader*)top_buff;
}
} else {
if(sFreeStrBuffer) {
auto next = sFreeStrBuffer->header.next;
sFreeStrBuffer->header.data = nullptr;
sFreeStrBuffer = (IsoStrBuffer*)next;
top_str->header.data_size = 0;
top_str->header.next = nullptr;
return (IsoBufferHeader*)top_str;
}
}
printf("[OVERLORD] Failed to allocate buffer (requested size 0x%x)\n", size);
return nullptr;
}
/*!
* Return a buffer once you are done using it so somebody else can have a turn
*/
void FreeBuffer(IsoBufferHeader *buffer) {
IsoBufferHeader* b = (IsoBufferHeader*)buffer;
printf("--------------- free buffer size %d\n", b->buffer_size);
if(b->buffer_size == BUFFER_PAGE_SIZE) {
b->next = sFreeBuffer;
sFreeBuffer = (IsoBuffer*)b;
} else {
b->next = sFreeStrBuffer;
sFreeStrBuffer = (IsoStrBuffer*)b;
}
}
/*!
* Display all messages in the priority stack
* The actual function does nothing.
*/
void DisplayQueue() {
for(int pri = 0; pri < N_PRIORITIES; pri++) {
for(int cmd = 0; cmd < (int)gPriStack[pri].n; cmd++) {
printf(" PRI %d elt %d %s\n", pri, cmd, gPriStack[pri].names[cmd].c_str());
}
}
}
/*!
* Add a message to the back of the queue for the given priority.
* If there is no room left in the queue, ReturnMessage with a CMD_STATUS_FAILED_TO_QUEUE.
* Return 1 on success.
*/
u32 QueueMessage(IsoMessage *cmd, int32_t priority, const char *name) {
u32 ok = gPriStack[priority].n != PRI_STACK_LENGTH;
if(ok) {
gPriStack[priority].cmds[gPriStack[priority].n] = cmd;
gPriStack[priority].names[gPriStack[priority].n] = name;
gPriStack[priority].n++;
printf("[OVERLORD] Queue %d (%d/%d), %s\n", priority, gPriStack[priority].n, PRI_STACK_LENGTH, gPriStack[priority].names[gPriStack[priority].n - 1].c_str());
DisplayQueue();
} else {
printf("[OVERLORD ISO QUEUE] Failed to queue!\n");
cmd->status = CMD_STATUS_FAILED_TO_QUEUE;
ReturnMessage(cmd);
}
return ok;
}
/*!
* Remove a message from the priority stack.
*/
void UnqueueMessage(IsoMessage *cmd) {
int pri = 0;
u32 idx = 0;
PriStackEntry* pse;
// loop over priorities
for(pri = 0; pri < N_PRIORITIES; pri++) {
pse = gPriStack + pri;
// loop over entries
for(idx = 0; idx < gPriStack[pri].n; idx++) {
if(pse->cmds[idx] == cmd) {
goto found;
}
}
}
printf("[OVERLORD ISO QUEUE] Failed to unqueue!\n");
found:
assert(gPriStack[pri].cmds[idx] == cmd);
// pop
gPriStack[pri].n--;
// and move other entries up.
while(idx < gPriStack[pri].n) {
pse->cmds[idx] = pse->cmds[idx + 1];
idx++;
}
DisplayQueue();
}
/*!
* Get the highest priority message with an open buffer.
* (Note - messages with priority less than max priority will be gotten if they have < 2 buffers filled)
* @return
*/
IsoMessage* GetMessage() {
// loop over all priorities
for(int pri = (N_PRIORITIES - 1); pri >= 0; pri--) {
auto pse = gPriStack + pri;
int idx = gPriStack[pri].n;
for(idx = idx - 1; idx >= 0; idx--) {
if(pse->cmds[idx]->fd &&
pse->cmds[idx]->status == CMD_STATUS_IN_PROGRESS &&
pse->cmds[idx]->ready_for_data) {
if(pri == N_PRIORITIES - 1) {
// return high priority commands only if they don't have any buffers filled
if(!pse->cmds[idx]->callback_buffer) {
return pse->cmds[idx];
}
} else {
// return lower priority commands if they don't have 2 buffers filled.
if(!pse->cmds[idx]->callback_buffer ||
!(IsoBufferHeader*)(pse->cmds[idx]->callback_buffer)->next) {
return pse->cmds[idx];
}
}
}
}
}
return nullptr;
}
/*!
* Execute callbacks and maintain buffers for finished reads in the priority stack
*/
void ProcessMessageData() {
int32_t pri = N_PRIORITIES - 1;
for (;;) {
if (pri < 0) return;
int32_t cmdID = gPriStack[pri].n;
IsoMessage *popped_command;
do {
cmdID--;
if (cmdID < 0) goto end_cur;
popped_command = gPriStack[pri].cmds[cmdID];
auto* callback_buffer = popped_command->callback_buffer;
if(popped_command->status == CMD_STATUS_IN_PROGRESS && callback_buffer) { // if we have a callback buffer (meaning a read finished and let us know)
// execute the callback!
uint32_t callback_result = popped_command->callback_function(popped_command, callback_buffer);
popped_command->status = callback_result;
// printf("ProcessMessage Data set command %p status to %d\n", popped_command, popped_command->status);
// if we're done with the buffer, free it and load the next one (if there is one)
if(callback_buffer->data_size == 0) {
popped_command->callback_buffer = (IsoBufferHeader*)callback_buffer->next;
printf("free 1\n");
FreeBuffer(callback_buffer);
}
}
} while (popped_command->status == CMD_STATUS_IN_PROGRESS);
ReleaseMessage(popped_command);
ReturnMessage(popped_command);
// return message todo this will free vag commands!
pri++;
end_cur:
pri--;
}
}
/*!
* Wakeup thread/message mbx for a message
*/
void ReturnMessage(IsoMessage *cmd) {
if(!cmd->messagebox_to_reply) {
if(cmd->thread_id == 0) {
FreeVAGCommand((VagCommand*)cmd);
} else {
WakeupThread(cmd->thread_id);
}
} else {
SendMbx(cmd->messagebox_to_reply, (MsgPacket*)cmd);
}
}
/*!
* Free buffers, close files, and remove from priority stack
*/
void ReleaseMessage(IsoMessage *cmd) {
// kill all buffers
while(cmd->callback_buffer) {
auto old_head = cmd->callback_buffer;
cmd->callback_buffer = (IsoBufferHeader*)old_head->next;
printf("free 2\n");
FreeBuffer(old_head);
}
// close file
if(cmd->fd) {
isofs->close(cmd->fd);
}
// unqueue message
UnqueueMessage(cmd);
}
// GetVAGCommand
VagCommand* GetVAGCommand() {
for(;;) {
// wait for command to be available
while(vag_cmd_cnt == (N_VAG_CMDS - 1)) {
DelayThread(100);
}
// wait for VAG semaphore
while(WaitSema(sSema)) {
}
// try to get something.
for(s32 i = 0; i < N_VAG_CMDS; i++) {
if(!((vag_cmd_used >> (i & 0x1f)) & 1)) {
// free!
vag_cmd_used |= (1 << (i & 0x1f));
vag_cmd_cnt++;
if(vag_cmd_cnt > max_vag_cmd_cnt) {
max_vag_cmd_cnt = vag_cmd_cnt;
}
SignalSema(sSema);
return &vag_cmds[i];
}
}
SignalSema(sSema);
}
}
void FreeVAGCommand(VagCommand* cmd) {
s32 idx = cmd - vag_cmds;
if(idx >= 0 && idx < N_VAG_CMDS && ((vag_cmd_used >> (idx & 0x1f)) & 1)) {
while(WaitSema(sSema)) {
}
vag_cmd_used &= ~(1 << (idx & 0x1f));
vag_cmd_cnt--;
SignalSema(sSema);
} else {
printf("[OVERLORD] Invalid FreeVAGCommand!\n");
}
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef JAK_V2_ISO_QUEUE_H
#define JAK_V2_ISO_QUEUE_H
#include "common/common_types.h"
#include "isocommon.h"
void iso_queue_init_globals();
void InitBuffers();
IsoBufferHeader* AllocateBuffer(uint32_t size);
void FreeBuffer(IsoBufferHeader *buffer);
u32 QueueMessage(IsoMessage *cmd, int32_t priority, const char *name);
void UnqueueMessage(IsoMessage *cmd);
IsoMessage* GetMessage();
void ProcessMessageData();
void ReturnMessage(IsoMessage *cmd);
#endif //JAK_V2_ISO_QUEUE_H
+196
View File
@@ -0,0 +1,196 @@
/*!
* @file isocommon.cpp
* Common ISO utilities.
*/
#include <assert.h>
#include "common/common_types.h"
#include <cstring>
#include "isocommon.h"
/*!
* Convert file name to "ISO Name"
* ISO names are upper case and 12 bytes long.
* xxxxxxxxyyy0
*
* x - uppercase letter of file name, or space
* y - uppercase letter of file extension, or space
* 0 - null terminator (\0, not the character zero)
*/
void MakeISOName(char* dst, const char* src) {
int i = 0;
const char* src_ptr = src;
char* dst_ptr = dst;
// copy name and upper case
while ((i < 8) && (*src_ptr) && (*src_ptr != '.')) {
char c = *src_ptr;
src_ptr++;
if (('`' < c) && (c < '{')) { // lower case
c -= 0x20;
}
*dst_ptr = c;
dst_ptr++;
i++;
}
// pad out name with spaces
while (i < 8) {
*dst_ptr = ' ';
dst_ptr++;
i++;
}
// increment past period
if (*src_ptr == '.')
src_ptr++;
// same for extension
while (i < 11 && (*src_ptr)) {
char c = *src_ptr;
src_ptr++;
if (('`' < c) && (c < '{')) { // lower case
c -= 0x20;
}
*dst_ptr = c;
dst_ptr++;
i++;
}
while (i < 11) {
*dst_ptr = ' ';
dst_ptr++;
i++;
}
*dst_ptr = 0;
}
/*!
* Unmakes an ISO name back to the original name.
* Keeps it upper case.
* Not used.
*/
void UnmakeISOName(char* dst, const char* src) {
int i = 0;
const char* src_ptr = src;
char* dst_ptr = dst;
// copy non-space characters
while ((i < 8) && (*src != ' ')) {
*dst_ptr = *src_ptr;
src_ptr++;
dst_ptr++;
i++;
}
// skip src to the extension
src_ptr += 8 - i;
if (*src_ptr != ' ') {
// if there's an extension, add the period
*dst_ptr = '.';
i = 0;
// copy extension
dst_ptr++;
while (i < 3 && *src_ptr != ' ') {
*dst_ptr = *src_ptr;
src_ptr++;
i++;
}
}
*dst_ptr = 0;
}
/*!
* Convert an animation name to ISO name.
* The animation name is a bunch of dash separated words.
* The resulting ISO name has the same first two chars as the animation name, and one char from each
* remaining word. Once there are no more words but remaining chars in the ISO name, the ith extra
* char is the i+1 th char of the last word. A word ending in a number (or just a number) is turned
* into the number. The word "resolution" becomes z. The word "accept" becomes y. The word "reject"
* becomes n. Other words become the first char of the word. The result is uppercased and the file
* extension is STR Examples (animation name and disc file name, not ISO name):
* green-sagecage-outro-beat-boss-enough-cells -> GRSOBBEC.STR
* swamp-tetherrock-swamprockexplode-4 -> SWTS4.STR
* minershort-resolution-1-orbs -> MIZ1ORBS.STR
* @param dst
* @param src
*/
void ISONameFromAnimationName(char* dst, const char* src) {
// The Animation Name is a bunch of words separated by dashes
// copy first two chars of the first word exactly
dst[0] = src[0];
dst[1] = src[1];
s32 i = 2; // 2 chars added to dst.
// skip ahead to the first dash (or \0 if there's no dashes)
const char* src_ptr = src;
while (*src_ptr && *src_ptr != '-') {
src_ptr++;
}
// the points to the next dash (or \0 if there's none).
const char* next_ptr = src_ptr;
if (*src_ptr) {
// loop over words (next_ptr points to dash before word, i counts chars in dest)
while (src_ptr = next_ptr + 1, i < 8) {
// scan next_ptr forward to next dash
next_ptr = src_ptr;
while (*next_ptr && *next_ptr != '-') {
next_ptr++;
}
// there's no next word, so break (the current word will be handled there)
if (!*next_ptr)
break;
// add a char for the current word:
char char_to_add;
if (next_ptr[-1] < '0' || next_ptr[-1] > '9') {
// word doesn't end in a number.
// some special case words map to special letters (likely to avoid animation name conflicts)
if (next_ptr - src_ptr == 10 && !memcmp(src_ptr, "resolution", 10)) {
char_to_add = 'z';
} else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "accept", 6)) {
char_to_add = 'y';
} else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "reject", 6)) {
char_to_add = 'n';
} else {
// not a special case, just take the first letter.
char_to_add = *src_ptr;
}
} else {
// the current word ends in a number, just use this number (I think usually the whole word
// is just a number)
char_to_add = next_ptr[-1];
}
dst[i++] = char_to_add;
}
// here we ran out of room in dest, or words in source.
// if there's still room in dest and chars in source, just add them
while (*src_ptr && (i < 8)) {
dst[i] = *src_ptr;
src_ptr++;
i++;
}
}
// pad with spaces (for ISO Name)
while (i < 8) {
dst[i++] = ' ';
}
// upper case
for (i = 0; i < 8; i++) {
if (dst[i] > '`' && dst[i] < '{') {
dst[i] -= 0x20;
}
}
// append file extension
strcpy(dst + 8, "STR");
}
+187
View File
@@ -0,0 +1,187 @@
/*!
* @file isocommon.h
* Common ISO utilities.
*/
#ifndef JAK_V2_ISOCOMMON_H
#define JAK_V2_ISOCOMMON_H
#include <string>
#include "common/common_types.h"
#include "common/link_types.h"
constexpr int PRI_STACK_LENGTH = 4; // number of queued commands per priority
constexpr int N_PRIORITIES = 4; // number of priorities
constexpr u32 CMD_STATUS_READ_ERR = 8; // read encountered a problem or was canceled.
constexpr u32 CMD_STATUS_NULL_CB = 7; // status returned if you don't set a callback
constexpr u32 CMD_STATUS_FAILED_TO_OPEN = 6; // status if file couldn't be opened
constexpr u32 CMD_STATUS_FAILED_TO_QUEUE = 2; // status if we couldn't be queued
constexpr u32 CMD_STATUS_IN_PROGRESS = 0xffffffff; // status if command is running and healthy
constexpr u32 CMD_STATUS_DONE = 0; // status if command is done.
constexpr int BUFFER_PAGE_SIZE = 0xc000; // size in bytes of normal read buffer
constexpr int STR_BUFFER_DATA_SIZE = 0x6000; // size in bytes of vag read buffer
constexpr int LOAD_TO_EE_CMD_ID = 0x100; // command to load file to ee
constexpr int LOAD_TO_IOP_CMD_ID = 0x101; // command to load to iop
constexpr int LOAD_TO_EE_OFFSET_CMD_ID = 0x102; // command to load file to ee with offset.
constexpr int LOAD_DGO_CMD_ID = 0x200; // command to load DGO
constexpr int SECTOR_SIZE = 0x800; // media sector size
constexpr int MAX_ISO_FILES = 350; // maximum files on FS
constexpr int MAX_OPEN_FILES = 16; // maximum number of open files at a time.
/*!
* Record for file. There is one for each file in the FS, and pointers to each FileRecord act as
* an identifier.
* The location/size can't be counted on to be anything meaningful as it depends on the IsoFs
* implementation being used.
*/
struct FileRecord {
char name[12];
uint32_t location;
uint32_t size;
};
/*!
* Record for an open file.
*/
struct LoadStackEntry {
FileRecord* fr;
uint32_t location;
};
/*!
* Header for a ISO data buffer.
*/
struct IsoBufferHeader {
void* data; // 0
uint32_t data_size; // 1
uint32_t buffer_size;
void* next;
// follows the header.
u8* get_data() { return ((u8*)this) + sizeof(IsoBufferHeader); }
};
struct IsoMessage;
struct LoadStackEntry;
//! Callback function for data loads.
typedef u32 (*iso_callback_func)(IsoMessage* cmd, IsoBufferHeader* buffer);
/*!
* Command, common parent.
*/
struct IsoMessage {
uint32_t field_0x0; // 0x00
uint32_t field_0x4; // 0x04
uint32_t cmd_id; // 0x08
uint32_t status; // 0x0c
s32 messagebox_to_reply; // 0x10
s32 thread_id; // 0x14
uint32_t ready_for_data; // 0x18
IsoBufferHeader* callback_buffer; // 0x1c
iso_callback_func callback_function; // 0x20
LoadStackEntry* fd; // 0x24
};
/*!
* Command to load a single file.
*/
struct IsoCommandLoadSingle : public IsoMessage {
FileRecord* file_record; // 0x28
u8* dest_addr; // 0x2c
s32 length; // 0x30
s32 length_to_copy; // 0x34
u32 offset; // 0x38
u8* dst_ptr; // 0x3c
s32 bytes_done; // 0x40
};
/*!
* Command to do something.
*/
struct VagCommand : public IsoMessage {
u32 field_0x30;
u32 field_0x34;
u32 field_0x38;
u32 field_0x3c;
u32 field_0x40;
u32 field_0x44;
u32 field_0x48;
u32 field_0x4c;
// 0x6c max
};
/*!
* DGO Load State Machine states.
*/
enum class DgoState {
Init = 0,
Read_Header = 1,
Finish_Obj = 2,
Read_Last_Obj = 3,
Read_Obj_Header = 4,
Read_Obj_data = 5,
Finish_Dgo = 6
};
/*!
* Command to load a DGO.
*/
struct DgoCommand : public IsoMessage {
FileRecord* fr; // 0x28, DGO file that's open
u8* buffer1; // 0x2c, first EE buffer
u8* buffer2; // 0x30, second EE buffer
u8* buffer_heaptop; // 0x34, top of the heap
DgoHeader dgo_header; // 0x38, current DGO's header
ObjectHeader objHeader; // 0x78, current obj's header
u8* ee_destination_buffer; // 0xb8, where we are currently loading to on ee
u32 bytes_processed; // 0xbc, how many bytes processed in the current state
u32 objects_loaded; // 0xc0, completed object count
DgoState dgo_state; // 0xc4, state machine state
u32 finished_first_obj; // 0xc8, have we finished loading the first object?
u32 buffer_toggle; // 0xcc, which buffer to load into (top, buffer1, buffer2)
u8* selectedBuffer; // 0xd0, most recently completed load destination
u32 want_abort; // 0xd4, should we quit?
};
/*!
* Priority Stack entry.
*/
struct PriStackEntry {
IsoMessage* cmds[PRI_STACK_LENGTH]; // cmds at this priority
std::string names[PRI_STACK_LENGTH]; // my addition for debug
uint32_t n; // how many in this priority?
void reset();
};
/*!
* API to access files. There are debug modes + reading from an ISO filesystem.
*/
struct IsoFs {
int (*init)(u8*);
FileRecord* (*find)(const char*);
FileRecord* (*find_in)(const char*);
uint32_t (*get_length)(FileRecord*);
LoadStackEntry* (*open)(FileRecord*, int32_t);
LoadStackEntry* (*open_wad)(FileRecord*, int32_t);
void (*close)(LoadStackEntry*);
uint32_t (*begin_read)(LoadStackEntry*, void*, int32_t);
uint32_t (*sync_read)();
uint32_t (*load_sound_bank)(char*, void*);
uint32_t (*load_music)(char*, void*);
void (*poll_drive)();
};
extern IsoFs* isofs;
extern s32 iso_mbx;
void MakeISOName(char* dst, const char* src);
#endif // JAK_V2_ISOCOMMON_H
+72
View File
@@ -0,0 +1,72 @@
#include <cstring>
#include "overlord.h"
#include "game/sce/iop.h"
#include "ramdisk.h"
#include "iso.h"
#include "ssound.h"
#include "sbank.h"
using namespace iop;
int start_overlord(int argc, const char* const* argv) {
(void)argc;
FlushDcache();
CpuEnableIntr();
if(!sceSifCheckInit()) {
sceSifInit();
}
sceSifInitRpc(0);
InitBanks();
InitSound_Overlord();
InitRamdisk();
// RegisterVblankHandler(0, 0x20, VBlank_Handler, nullptr);
ThreadParam thread_param;
thread_param.attr = TH_C;
thread_param.initPriority = 98;
thread_param.stackSize = 0x800;
thread_param.option = 0;
thread_param.entry = (void*)Thread_Server;
strcpy(thread_param.name, "Server"); // added
auto thread_server = CreateThread(&thread_param);
if(thread_server <= 0) {
return 1;
}
// thread_param.attr = TH_C;
// thread_param.initPriority = 96;
// thread_param.stackSize = 0x800;
// thread_param.option = 0;
// thread_param.entry = Thread_Player;
// auto thread_player = CreateThread(&thread_param);
// if(thread_player <= 0) {
// return 1;
// }
//
// thread_param.attr = TH_C;
// thread_param.initPriority = 99;
// thread_param.stackSize = 0x1000;
// thread_param.option = 0;
// thread_param.entry = Thread_Loader;
// auto thread_loader = CreateThread(&thread_param);
// if(thread_loader <= 0) {
// return 1;
// }
InitISOFS(argv[1], argv[2]);
StartThread(thread_server, 0);
// StartThread(thread_player, 0);
// StartThread(thread_loader, 0);
return 0;
}
/*!
* Loop endlessly and never return.
*/
void ExitIOP() {
while(true) {
}
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef JAK_V2_OVERLORD_H
#define JAK_V2_OVERLORD_H
int start_overlord(int argc, const char* const* argv);
void ExitIOP();
#endif //JAK_V2_OVERLORD_H
+186
View File
@@ -0,0 +1,186 @@
/*!
* @file ramdisk.cpp
* A RAMDISK RPC for storing files in the extra RAM left over on the IOP.
* Also called "Server".
*/
#include <cstring>
#include <cassert>
#include <cstdio>
#include "common/common_types.h"
#include "game/common/ramdisk_rpc_types.h"
#include "ramdisk.h"
#include "iso.h"
#include "iso_api.h"
#include "game/sce/iop.h"
// Note - the RAMDISK code supports having multiple files, but it appears only one file can ever be
// used at a time.
constexpr int RAMDISK_SIZE = 0xcac00; // Memory size of RAMDISK
constexpr int RAMDISK_MAX_FILES = 16; // Maximum number of files to store in RAMDISK.
constexpr int RAMDISK_RETURN_BUFFER_SIZE = 0x2000; // Maximum size of an individual RAMDISK read
constexpr int DEVTOOL_IOP_MEM_ALLOC =
0x1f5d00; // Extra memory to waste to compensate for extra RAM in dev kit
u32 gNumFiles; // Number of files in the RAMDISK
u32 gMemUsed; // Memory of RAMDISK used
u32 gMemSize; // Total memory of RAMDISK
u32 gMemFreeAtStart; // Memory free after allocation of RAMDISK
uint8_t* gMem; // Allocation for RAMDISK
uint8_t* gRamdiskRAM; // Also allocation for RAMDISK
uint8_t gRPCBuf[40]; // Buffer for RAMDISK RPC handler
// Each file stored in the ramdisk has a file record:
struct RamdiskFileRecord {
uint32_t size; // size of file in bytes (will be 16-byte aligned)
uint32_t additional_offset; // an offset into the memory for the file
uint32_t file_id; // an ID number used to identify this file.
};
RamdiskFileRecord gFiles[RAMDISK_MAX_FILES]; // File records
uint8_t gReturnBuffer[RAMDISK_RETURN_BUFFER_SIZE]; // Buffer to hold data requested by EE
using namespace iop;
void ramdisk_init_globals() {
gNumFiles = 0;
gMemUsed = 0;
gMemSize = 0;
gMemFreeAtStart = 0;
gMem = nullptr;
gRamdiskRAM = nullptr;
memset(gRPCBuf, 0, sizeof(gRPCBuf));
memset(gFiles, 0, sizeof(gFiles));
memset(gReturnBuffer, 0, sizeof(gReturnBuffer));
}
/*!
* Initialze the RAMDISK IOP System.
* For some reason the name of this function is lost, so this is a guess at the name.
* DONE, EXACT
*/
void InitRamdisk() {
gNumFiles = 0;
gMemUsed = 0;
gMemSize = RAMDISK_SIZE;
// some sort of "trick" to allocate memory if we are on a debug system to simulate the memory size
// of the real PS2.
if (QueryTotalFreeMemSize() > 0x200000) {
AllocSysMemory(SMEM_Low, DEVTOOL_IOP_MEM_ALLOC, nullptr);
}
// allocate RAMDISK RAM
gMem = (uint8_t*)AllocSysMemory(SMEM_Low, gMemSize, nullptr);
if (gMem) {
gMemFreeAtStart = QueryTotalFreeMemSize();
gRamdiskRAM = gMem;
} else {
printf("[OVERLORD RAMDISK] Failed to allocate memory for RAMDISK!\n"); // added
}
}
void* RPC_Ramdisk(unsigned int fno, void* data, int size);
/*!
* The main function for the IOP Ramdisk/Server thread.
* DONE, EXACT
*/
u32 Thread_Server() {
sceSifQueueData dq;
sceSifServeData serve;
// set up RPC
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RAMDISK_RPC_ID, RPC_Ramdisk, gRPCBuf, nullptr, nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
/*!
* Ramdisk RPC Handler.
* DONE
* Added some debugging print statements.
* Returns a pointer to the file contents on successful GET_DATA.
* Returns nullptr in all other cases.
*/
void* RPC_Ramdisk(unsigned int fno, void* data, int size) {
(void)size;
auto cmd = (RPC_Ramdisk_LoadCmd*)data;
if (fno == RAMDISK_RESET_AND_LOAD_FNO) {
// reset files and memory
gNumFiles = 0;
gMemUsed = 0;
// locate file to load into ramdisk
auto file_record = FindISOFile(cmd->name);
if (!file_record) {
printf("[OVERLORD RAMDISK] Failed to find ISO file for load.\n"); // added
return nullptr;
}
// if we have available memory and records (we'll always have enough records, we just reset it!)
// NOTE - there is a bug here where the rounding up to 16-bytes can cause it to overflow!
auto file_length = GetISOFileLength(file_record);
if ((file_length + gMemUsed <= gMemSize) && (gNumFiles != RAMDISK_MAX_FILES)) {
// Create the new file record
gFiles[gNumFiles].size = (file_length + 0xf) & 0xfffffff0;
assert(gFiles[gNumFiles].size + gMemUsed <
gMemSize); // ADDED! this checks for a real bug in the code.
gFiles[gNumFiles].additional_offset = 0;
gFiles[gNumFiles].file_id = cmd->file_id_or_ee_addr;
// Increment file count
gNumFiles++;
// Load file into IOP at the appropriate spot
LoadISOFileToIOP(file_record, gMem + gMemUsed, file_length);
gMemUsed += gFiles[gNumFiles].size;
} else {
printf("[OVERLORD RAMDISK] Failed to load file because RAMDISK is out of memory or files!\n");
}
} else if (fno == RAMDISK_GET_DATA_FNO) {
// Copy data into a local IOP buffer
// Total offset into ramdisk memory
auto offset = cmd->offset_into_file;
// find a matching file, and compute its offset
u32 file_idx = 0;
while (file_idx < gNumFiles && gFiles[file_idx].file_id != cmd->file_id_or_ee_addr) {
offset += gFiles[file_idx].size;
file_idx++;
}
if (file_idx == gNumFiles) {
// didn't find the file
printf("[OVERLORD RAMDISK] Failed to find ISO file for read.\n"); // added
return nullptr;
}
if (cmd->size > RAMDISK_RETURN_BUFFER_SIZE) {
printf("[OVERLORD RAMDISK] requested file read size is too large.\n"); // added
return nullptr;
}
// copy to return buffer. This way RAMDISK data is valid until another GET_DATA.
memcpy(gReturnBuffer, gMem + offset + gFiles[file_idx].additional_offset, size);
return gReturnBuffer;
} else if (fno == RAMDISK_BYPASS_LOAD_FILE) {
// This is just a normal file load to the EE.
auto file_record = FindISOFile(cmd->name);
if (!file_record) {
printf("[OVERLORD RAMDISK] Failed to open file for bypass load.\n"); // added
return nullptr;
}
LoadISOFileToEE(file_record, cmd->file_id_or_ee_addr, cmd->size);
} else {
printf("[OVERLORD RAMDISK] Unsupported fno\n"); // ADDED
}
return nullptr;
}
+16
View File
@@ -0,0 +1,16 @@
/*!
* @file ramdisk.cpp
* A RAMDISK RPC for storing files in the extra RAM left over on the IOP.
* Also called "Server".
*/
#ifndef JAK_RAMDISK_H
#define JAK_RAMDISK_H
#include "common/common_types.h"
void ramdisk_init_globals();
void InitRamdisk();
u32 Thread_Server();
#endif // JAK_RAMDISK_H
+5
View File
@@ -0,0 +1,5 @@
#include "sbank.h"
void InitBanks() {
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef JAK_V2_SBANK_H
#define JAK_V2_SBANK_H
void InitBanks();
#endif //JAK_V2_SBANK_H
+8
View File
@@ -0,0 +1,8 @@
#include <assert.h>
#include "soundcommon.h"
void PrintBankInfo(void* buffer) {
(void)buffer;
assert(false);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef JAK_V2_SOUNDCOMMON_H
#define JAK_V2_SOUNDCOMMON_H
void PrintBankInfo(void* buffer);
#endif //JAK_V2_SOUNDCOMMON_H
+8
View File
@@ -0,0 +1,8 @@
#include <cstring>
#include "srpc.h"
u8 gMusicTweakInfo[0x204];
void srpc_init_globals() {
memset(gMusicTweakInfo, 0, sizeof(gMusicTweakInfo));
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef JAK_V2_SRPC_H
#define JAK_V2_SRPC_H
#include "common/common_types.h"
void srpc_init_globals();
constexpr int MUSIC_TWEAK_SIZE = 0x204;
extern u8 gMusicTweakInfo[MUSIC_TWEAK_SIZE];
#endif //JAK_V2_SRPC_H
+5
View File
@@ -0,0 +1,5 @@
#include "ssound.h"
void InitSound_Overlord() {
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef JAK_V2_SSOUND_H
#define JAK_V2_SSOUND_H
void InitSound_Overlord();
#endif //JAK_V2_SSOUND_H
+12
View File
@@ -0,0 +1,12 @@
#include <assert.h>
#include "stream.h"
u32 STRThread() {
assert(false);
return 0;
}
u32 PLAYThread() {
assert(false);
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef JAK_V2_STREAM_H
#define JAK_V2_STREAM_H
#include "common/common_types.h"
u32 STRThread();
u32 PLAYThread();
#endif //JAK_V2_STREAM_H
+48
View File
@@ -0,0 +1,48 @@
TESTS!
fake_iso
---------
FS_Init should load tweak music file
Consider making it less slow? (probably doesn't matter)
FS_LoadMusic
FS_LoadSoundBank.
iso_cd
-------
work out timing constant
FS_LoadSoundBank magic constants
FS_LoadMusic magic constants
dma
------
DMA_SendToSPUAndSync
iso
-----
InitISOFS - DMA_SendToSPUAndSync
- STRThread
- PLAYThread
Move VagDirEntry to somewhere else
magic numbers
more ISOThread message Ids
Handle Sound Stuff
VagCommand field names.
ProcessVAGData
StopVAG
PauseVAG
CalculateVAGVolumes
UnpauseVAG
SetVAGVol
GetPlayPos
UpdatePlayPos
CheckVAGStreamProgress
GetVAGStreamPos
VAG_MarkLoopStart
VAG_MarkLoopEnd
VAG_MarkNonloopStart
VAG_MarkNonloopEnd
stream
---------
the whole thing.
+247
View File
@@ -0,0 +1,247 @@
/*!
* @file runtime.cpp
* Setup and launcher for the runtime.
*/
#include <unistd.h>
#include <sys/mman.h>
#include <cstring>
#include "runtime.h"
#include "system/SystemThread.h"
#include "sce/libcdvd_ee.h"
#include "sce/deci2.h"
#include "sce/sif_ee.h"
#include "sce/iop.h"
#include "game/system/Deci2Server.h"
#include "game/kernel/fileio.h"
#include "game/kernel/kboot.h"
#include "game/kernel/klink.h"
#include "game/kernel/kscheme.h"
#include "game/kernel/kdsnetm.h"
#include "game/kernel/klisten.h"
#include "game/kernel/kmemcard.h"
#include "game/kernel/kprint.h"
#include "game/kernel/kdgo.h"
#include "game/system/iop_thread.h"
#include "game/overlord/dma.h"
#include "game/overlord/iso.h"
#include "game/overlord/fake_iso.h"
#include "game/overlord/iso_queue.h"
#include "game/overlord/ramdisk.h"
#include "game/overlord/iso_cd.h"
#include "game/overlord/overlord.h"
#include "game/overlord/srpc.h"
u8* g_ee_main_mem = nullptr;
namespace {
/*!
* SystemThread function for running the DECI2 communication with the GOAL compiler.
*/
void deci2_runner(SystemThreadInterface& interface) {
// callback function so the server knows when to give up and shutdown
std::function<bool()> shutdown_callback = [&]() { return interface.get_want_exit(); };
// create and register server
Deci2Server server(shutdown_callback);
ee::LIBRARY_sceDeci2_register(&server);
// now its ok to continue with initialization
interface.initialization_complete();
// in our own thread, wait for the EE to register the first protocol driver
printf("[DECI2] waiting for EE to register protos\n");
server.wait_for_protos_ready();
// then allow the server to accept connections
if (!server.init()) {
throw std::runtime_error("DECI2 server init failed");
}
printf("[DECI2] waiting for listener...\n");
bool saw_listener = false;
while (!interface.get_want_exit()) {
if (server.check_for_listener()) {
if (!saw_listener) {
printf("[DECI2] Connected!\n");
}
saw_listener = true;
// we have a listener, run!
server.run();
} else {
// no connection yet. Do a sleep so we don't spam checking the listener.
usleep(50000);
}
}
}
// EE System
constexpr int EE_MAIN_MEM_SIZE = 128 * (1 << 20); // 128 MB, same as PS2 TOOL
constexpr u64 EE_MAIN_MEM_MAP = 0x2000000000; // intentionally > 32-bit to catch pointer bugs
// when true, attempt to map the EE memory in the low 2 GB of RAM
// this allows us to use EE pointers as real pointers. However, this might not always work,
// so this should be used only for debugging.
constexpr bool EE_MEM_LOW_MAP = false;
// GOAL Boot arguments
constexpr const char* GOAL_ARGV[] = {"", "-fakeiso", "-boot", "-debug"};
constexpr int GOAL_ARGC = 4;
/*!
* SystemThread Function for the EE (PS2 Main CPU)
*/
void ee_runner(SystemThreadInterface& interface) {
// Allocate Main RAM. Must have execute enabled.
if (EE_MEM_LOW_MAP) {
g_ee_main_mem =
(u8*)mmap((void*)0x10000000, EE_MAIN_MEM_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE | MAP_POPULATE, 0, 0);
} else {
g_ee_main_mem =
(u8*)mmap((void*)EE_MAIN_MEM_MAP, EE_MAIN_MEM_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
}
if (g_ee_main_mem == (u8*)(-1)) {
printf(" Failed to initialize main memory! %s\n", strerror(errno));
interface.initialization_complete();
return;
}
printf(" Main memory mapped at 0x%016lx\n", (u64)(g_ee_main_mem));
printf(" Main memory size 0x%x bytes (%.3f MB)\n", EE_MAIN_MEM_SIZE,
(double)EE_MAIN_MEM_SIZE / (1 << 20));
printf("[EE] Initialization complete!\n");
interface.initialization_complete();
printf("[EE] Run!\n");
memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE);
fileio_init_globals();
kboot_init_globals();
kdgo_init_globals();
kdsnetm_init_globals();
klink_init_globals();
kmachine_init_globals();
kscheme_init_globals();
kmalloc_init_globals();
klisten_init_globals();
kmemcard_init_globals();
kprint_init_globals();
goal_main(GOAL_ARGC, GOAL_ARGV);
printf("[EE] Done!\n");
// // kill the IOP todo
iop::LIBRARY_kill();
munmap(g_ee_main_mem, EE_MAIN_MEM_SIZE);
// after main returns, trigger a shutdown.
interface.trigger_shutdown();
}
/*!
* SystemThread function for running the IOP (separate I/O Processor)
*/
void iop_runner(SystemThreadInterface& interface) {
IOP iop;
printf("\n\n\n[IOP] Restart!\n");
iop.reset_allocator();
ee::LIBRARY_sceSif_register(&iop);
iop::LIBRARY_register(&iop);
// todo!
dma_init_globals();
iso_init_globals();
fake_iso_init_globals();
// iso_api
iso_cd_init_globals();
iso_queue_init_globals();
// isocommon
// overlord
ramdisk_init_globals();
// sbank
// soundcommon
srpc_init_globals();
// ssound
// stream
interface.initialization_complete();
printf("[IOP] Wait for OVERLORD to be started...\n");
iop.wait_for_overlord_start_cmd();
if (iop.status == IOP_OVERLORD_INIT) {
printf("[IOP] Run!\n");
} else {
printf("[IOP] shutdown!\n");
return;
}
iop.reset_allocator();
// init
start_overlord(iop.overlord_argc, iop.overlord_argv); // todo!
// unblock the EE, the overlord is set up!
iop.signal_overlord_init_finish();
// IOP Kernel loop
while (!interface.get_want_exit() && !iop.want_exit) {
// the IOP kernel just runs at full blast, so we only run the IOP when the EE is waiting on the
// IOP. Each time the EE is waiting on the IOP, it will run an iteration of the IOP kernel.
iop.wait_run_iop();
iop.kernel.dispatchAll();
}
// stop all threads in the iop kernel.
// if the threads are not stopped nicely, we will deadlock on trying to destroy the kernel's
// condition variables.
iop.kernel.shutdown();
}
} // namespace
/*!
* Main function to launch the runtime.
* Arguments are currently ignored.
*/
void exec_runtime(int argc, char** argv) {
(void)argc;
(void)argv;
// step 1: sce library prep
iop::LIBRARY_INIT();
ee::LIBRARY_INIT_sceCd();
ee::LIBRARY_INIT_sceDeci2();
ee::LIBRARY_INIT_sceSif();
// step 2: system prep
SystemThreadManager tm;
auto& deci_thread = tm.create_thread("DMP");
auto& iop_thread = tm.create_thread("IOP");
auto& ee_thread = tm.create_thread("EE");
// step 3: start the EE!
iop_thread.start(iop_runner);
ee_thread.start(ee_runner);
deci_thread.start(deci2_runner);
// step 4: wait for EE to signal a shutdown, which will cause the DECI thread to join.
deci_thread.join();
// DECI has been killed, shutdown!
// to be extra sure
tm.shutdown();
// join and exit
tm.join();
printf("GOAL Runtime Shutdown\n");
}
+14
View File
@@ -0,0 +1,14 @@
/*!
* @file runtime.h
* Setup and launcher for the runtime.
*/
#ifndef JAK1_RUNTIME_H
#define JAK1_RUNTIME_H
#include "common/common_types.h"
extern u8* g_ee_main_mem;
void exec_runtime(int argc, char** argv);
#endif // JAK1_RUNTIME_H
+136
View File
@@ -0,0 +1,136 @@
/*!
* @file deci2.cpp
* Implementation of SCE DECI2 library.
*/
#include <cassert>
#include <cstdio>
#include <cstring>
#include "deci2.h"
#include "game/system/Deci2Server.h"
namespace ee {
namespace {
constexpr int MAX_DECI2_PROTOCOLS = 4;
Deci2Driver protocols[MAX_DECI2_PROTOCOLS]; // info for each deci2 protocol registered
int protocol_count; // number of registered protocols
Deci2Driver* sending_driver; // currently sending protocol driver
::Deci2Server* server; // the server to send data to
} // namespace
/*!
* Initialize the library.
*/
void LIBRARY_INIT_sceDeci2() {
// reset protocols
for (auto& p : protocols) {
p = Deci2Driver();
}
protocol_count = 0;
server = nullptr;
sending_driver = nullptr;
}
/*!
* Run any pending requested sends.
*/
void LIBRARY_sceDeci2_run_sends() {
for (auto& prot : protocols) {
if (prot.active && prot.pending_send == 'H') {
sending_driver = &prot;
(prot.handler)(DECI2_WRITE, 0, prot.opt);
sending_driver = nullptr;
prot.pending_send = 0;
(prot.handler)(DECI2_WRITEDONE, 0, prot.opt);
}
}
}
/*!
* Register a Deci2Server with this library.
*/
void LIBRARY_sceDeci2_register(::Deci2Server* s) {
server = s;
}
/*!
* Open a new socket with given protocol number and handler.
* The "opt" pointer is passed to the handler function.
* I don't know why it's like this.
*/
s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, void* opt)) {
server->lock();
Deci2Driver drv;
drv.protocol = protocol;
drv.opt = opt;
drv.handler = handler;
drv.id = protocol_count + 1;
drv.active = true;
protocols[protocol_count++] = drv;
printf("[DECI2] Add new protocol driver %d for 0x%x\n", drv.id, drv.protocol);
server->unlock();
if (protocol_count == 1) {
// if we have our first protocol, inform the server we are ready to receive!
// then the server will accept incoming data.
server->send_proto_ready(protocols, &protocol_count);
}
return drv.id;
}
/*!
* Deactivate a DECI2 protocol by socket descriptor.
*/
s32 sceDeci2Close(s32 s) {
assert(s - 1 < protocol_count);
protocols[s - 1].active = false;
return 1;
}
/*!
* Start a send.
*/
s32 sceDeci2ReqSend(s32 s, char dest) {
assert(s - 1 < protocol_count);
auto& proto = protocols[s - 1];
proto.pending_send = dest;
return 0;
}
/*!
* Do a receive from socket s into buf of size len.
* Returns after data is copied.
*/
s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) {
assert(s - 1 < protocol_count);
protocols[s - 1].recv_size = len;
auto avail = protocols[s - 1].available_to_receive;
if (len <= avail) {
memcpy(buf, protocols[s - 1].recv_buffer, len);
return len;
} else {
printf("[DECI2] Error: ExRecv %d, only %d available!\n", len, avail);
return -1;
}
}
/*!
* Do a send.
*/
s32 sceDeci2ExSend(s32 s, void* buf, u16 len) {
assert(s - 1 < protocol_count);
if (!sending_driver) {
printf("sceDeci2ExSend called at illegal time!\n");
}
if (&protocols[s - 1] != sending_driver) {
printf("sceDeci2ExSend called with the wrong socket!\n");
}
server->send_data(buf, len);
return len;
}
} // namespace ee
+28
View File
@@ -0,0 +1,28 @@
/*!
* @file deci2.h
* Implementation of SCE DECI2 library.
*/
#ifndef JAK1_DECI2_H
#define JAK1_DECI2_H
#include "common/listener_common.h"
class Deci2Server;
namespace ee {
void LIBRARY_INIT_sceDeci2();
void LIBRARY_sceDeci2_run_sends();
void LIBRARY_sceDeci2_register(::Deci2Server* server);
s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, void* opt));
s32 sceDeci2Close(s32 s);
s32 sceDeci2ReqSend(s32 s, char dest);
s32 sceDeci2ExRecv(s32 s, void* buf, u16 len);
s32 sceDeci2ExSend(s32 s, void* buf, u16 len);
} // namespace ee
#endif // JAK1_DECI2_H
+216
View File
@@ -0,0 +1,216 @@
#include <cassert>
#include <cstring>
#include "iop.h"
#include "game/system/iop_thread.h"
namespace iop {
/*!
* Is the SIF initialized?
*/
u32 sceSifCheckInit() {
// the SIF is always initialized by the time OVERLORD starts.
// it would only be on an ancient dev kit where this might not be true.
return 1;
}
/*!
* Initialize SIF
*/
void sceSifInit() {
// do nothing!
}
/*!
* Initialize RPC
*/
void sceSifInitRpc(int mode) {
assert(mode == 0);
}
/*!
* Flush Data Cache
*/
void FlushDcache() {
// Do nothing! The data cache does not need to be flushed on x86 as we have no DMA which bypasses cache.
}
/*!
* Enable CPU Interrupts
*/
void CpuDisableIntr() {
}
/*!
* Disable CPU Interrupts
*/
void CpuEnableIntr() {
}
namespace {
::IOP* iop;
}
void LIBRARY_INIT() {
iop = nullptr;
}
void LIBRARY_register(::IOP* i) {
iop = i;
}
void LIBRARY_kill() {
iop->kill_from_ee();
}
/*!
* How much free memory is there, in bytes?
*/
int QueryTotalFreeMemSize() {
// this value is somewhat arbitrary - it's a lot, but not enough to make OVERLORD think it is running on
// an 8MB-of-IOP-RAM development machine.
return 0x100000;
}
/*!
* Allocate memory.
*/
void *AllocSysMemory(int type, unsigned long size, void *addr) {
assert(type == SMEM_Low);
assert(addr == nullptr);
return iop->iop_alloc(size);
}
/*!
* Create a new thread
*/
s32 CreateThread(ThreadParam* param) {
return iop->kernel.CreateThread(param->name, (u32(*)())param->entry);
}
/*!
* Create a new message box.
*/
s32 CreateMbx(MbxParam* param) {
(void)param;
return iop->kernel.CreateMbx();
}
s32 StartThread(s32 thid, u32 arg) {
assert(!arg);
iop->kernel.StartThread(thid);
return 0;
}
int GetThreadId() {
return iop->kernel.getCurrentThread();
}
void sceSifSetRpcQueue(sceSifQueueData* dq, int key) {
dq->key = key;
iop->kernel.set_rpc_queue(dq, key);
}
void sceSifRegisterRpc(sceSifServeData* serve, unsigned int request,
sceSifRpcFunc func, void* buff, sceSifRpcFunc cfunc, void* cbuff, sceSifQueueData* qd) {
serve->command = request;
serve->func = func;
serve->buff = buff;
(void)cfunc;
(void)cbuff;
assert(!cfunc);
assert(!cbuff);
qd->serve_data = serve;
}
void sceSifRpcLoop(sceSifQueueData* pd) {
iop->kernel.rpc_loop(pd);
}
int sceCdRead(uint32_t logical_sector, uint32_t sectors, void* buf, sceCdRMode* mode) {
(void)mode;
iop->kernel.read_disc_sectors(logical_sector, sectors, buf);
return 1;
}
int sceCdSync(int mode) {
(void)mode;
return 0;
}
int sceCdGetError() {
return 0; // no error
}
int sceCdGetDiskType() {
return SCECdPS2DVD; // always a DVD (for now)
}
int sceCdMmode(int media) {
(void)media;
return 1;
}
void DelayThread(u32 usec) {
iop->kernel.SuspendThread();
(void)usec;
}
int sceCdBreak() {
return 1;
}
int sceCdDiskReady(int mode) {
(void)mode;
return SCECdComplete;
}
u32 sceSifSetDma(sceSifDmaData* sdd, int len) {
assert(len == 1);
assert(len <= 0xc000);
// todo - sanity check the destination address.
memcpy(iop->ee_main_mem + (u64)(sdd->addr), sdd->data, sdd->size);
return 1;
}
s32 SendMbx(s32 mbxid, void* sendmsg) {
return iop->kernel.SendMbx(mbxid, sendmsg);
}
s32 PollMbx(MsgPacket** recvmsg, int mbxid) {
return iop->kernel.PollMbx((void**)recvmsg, mbxid);
}
static int now = 0;
void GetSystemTime(SysClock* time) {
time->lo = 0;
time->hi = now;
now += 10;
}
void SleepThread() {
iop->kernel.SleepThread();
}
s32 CreateSema(SemaParam* param) {
(void)param;
return iop->kernel.CreateSema();
}
s32 WaitSema(s32 sema) {
(void)sema;
throw std::runtime_error("NYI");
}
s32 SignalSema(s32 sema) {
(void)sema;
throw std::runtime_error("NYI");
}
s32 WakeupThread(s32 thid) {
iop->kernel.WakeupThread(thid);
return 0;
}
}
+140
View File
@@ -0,0 +1,140 @@
#ifndef JAK1_IOP_H
#define JAK1_IOP_H
#include "common/common_types.h"
#define SMEM_Low (0)
#define SMEM_High (1)
#define SMEM_Addr (2)
#define SCECdCD 1
#define SCECdDVD 2
#define SCECdIllgalMedia 0xff
#define SCECdIllegalMedia 0xff
#define SCECdDVDV 0xfe
#define SCECdCDDA 0xfd
#define SCECdPS2DVD 0x14
#define SCECdPS2CD 0x12
#define SCECdDETCT 0x01
#define SCECdComplete 0x02
#define SCECdNotReady 0x06
#define KE_MBOX_NOMSG -424
#define TH_C 0x02000000
class IOP;
namespace iop {
typedef void * (* sceSifRpcFunc)(unsigned int,void *,int);
struct sceSifServeData {
unsigned int command; // the RPC ID
sceSifRpcFunc func;
void* buff;
};
struct sceSifQueueData {
int key = -1;
sceSifServeData* serve_data = nullptr;
};
struct sceCdRMode {
uint8_t trycount;
uint8_t spindlctrl;
uint8_t datapattern;
uint8_t pad;
};
struct sceSifDmaData{
void* data;
void* addr;
unsigned int size;
unsigned int mode;
};
struct SysClock {
uint32_t hi, lo;
};
struct MsgPacket {
u32 dummy = 0;
};
struct MbxParam {
u32 attr;
u32 option;
};
struct ThreadParam {
u32 attr;
u32 option;
void *entry;
int stackSize;
int initPriority;
// added!
char name[64];
};
struct SemaParam {
uint32_t attr;
int32_t init_count;
int32_t max_count;
uint32_t option;
};
//void PS2_RegisterIOP(IOP *iop);
int QueryTotalFreeMemSize();
void *AllocSysMemory(int type, unsigned long size, void *addr);
int GetThreadId();
void CpuDisableIntr();
void CpuEnableIntr();
void SleepThread();
void DelayThread(u32 usec);
s32 CreateThread(ThreadParam* param);
s32 StartThread(s32 thid, u32 arg);
s32 WakeupThread(s32 thid);
void sceSifInitRpc(int mode);
void sceSifInitRpc(unsigned int mode);
void sceSifSetRpcQueue(sceSifQueueData* dq, int key);
void sceSifRegisterRpc(sceSifServeData* serve, unsigned int request,
sceSifRpcFunc func, void* buff, sceSifRpcFunc cfunc, void* cbuff, sceSifQueueData* qd);
void sceSifRpcLoop(sceSifQueueData* pd);
int sceCdRead(uint32_t logical_sector, uint32_t sectors, void* buf, sceCdRMode* mode);
int sceCdSync(int mode);
int sceCdGetError();
int sceCdGetDiskType();
int sceCdMmode(int media);
int sceCdBreak();
int sceCdDiskReady(int mode);
u32 sceSifSetDma(sceSifDmaData* sdd, int len);
s32 SendMbx(int mbxid, void* sendmsg);
s32 PollMbx(MsgPacket** recvmsg, int mbxid);
s32 CreateMbx(MbxParam* param);
void GetSystemTime(SysClock* time);
s32 CreateSema(SemaParam* param);
s32 WaitSema(s32 sema);
s32 SignalSema(s32 sema);
void FlushDcache();
u32 sceSifCheckInit();
void sceSifInit();
void LIBRARY_INIT();
void LIBRARY_register(::IOP* i);
void LIBRARY_kill();
}
#endif // JAK1_IOP_H
+62
View File
@@ -0,0 +1,62 @@
/*!
* @file libcdvd_ee.cpp
* Stub implementation of the EE CD/DVD library
*/
#include <cassert>
#include "libcdvd_ee.h"
namespace ee {
namespace {
// CD/DVD media type set by sceCdMMode
int media_mode;
}
void LIBRARY_INIT_sceCd() {
media_mode = -1;
}
/*!
* Initialize the CD/DVD subsystem.
* init_mode should be SCECdINIT
*/
int sceCdInit(int init_mode){
assert(init_mode == SCECdINIT);
return 1; // Initialization was performed normally
}
/*!
* Tell the library if we are expecting a CD or DVD.
*/
int sceCdMmode(int media) {
media_mode = media;
return 1; // If successful, returns 1
}
/*!
* Is the drive ready for commands?
* Mode is a flag for non-blocking, otherwise block until ready.
*/
int sceCdDiskReady(int mode) {
(void)mode;
// always ready!
return SCECdComplete;
}
/*!
* What type of disk do we have?
*/
int sceCdGetDiskType() {
// if we set CD or DVD, return the appropriate PS2 game disk type.
switch(media_mode) {
case SCECdCD:
return SCECdPS2CD;
case SCECdDVD:
return SCECdPS2DVD;
default:
// unset/unknown media mode, so drive won't work.
return SCECdIllegalMedia;
}
}
} // namespace ee
+36
View File
@@ -0,0 +1,36 @@
/*!
* @file libcdvd_ee.h
* Stub implementation of the EE CD/DVD library
*/
#ifndef JAK1_LIBCDVD_EE_H
#define JAK1_LIBCDVD_EE_H
// for sceCdInit
#define SCECdINIT 0x00
// Media modes
#define SCECdCD 1
#define SCECdDVD 2
// Status
#define SCECdComplete 0x02
#define SCECdNotReady 0x06
// Disk Types
#define SCECdIllegalMedia 0xff
#define SCECdDVDV 0xfe
#define SCECdCDDA 0xfd
#define SCECdPS2DVD 0x14
#define SCECdPS2CD 0x12
#define SCECdDETCT 0x01
namespace ee {
void LIBRARY_INIT_sceCd();
int sceCdInit(int init_mode);
int sceCdMmode(int media);
int sceCdDiskReady(int mode);
int sceCdGetDiskType();
} // namespace ee
#endif // JAK1_LIBCDVD_EE_H
+11
View File
@@ -0,0 +1,11 @@
#include "libscf.h"
namespace ee {
int sceScfGetAspect() {
return SCE_ASPECT_169;
}
int sceScfGetLanguage() {
return SCE_ENGLISH_LANGUAGE;
}
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef JAK1_LIBSCF_H
#define JAK1_LIBSCF_H
#define SCE_JAPANESE_LANGUAGE 0
#define SCE_ENGLISH_LANGUAGE 1
#define SCE_FRENCH_LANGUAGE 2
#define SCE_SPANISH_LANGUAGE 3
#define SCE_GERMAN_LANGUAGE 4
#define SCE_ITALIAN_LANGUAGE 5
#define SCE_DUTCH_LANGUAGE 6
#define SCE_PORTUGUESE_LANGUAGE 7
#define SCE_ASPECT_43 0
#define SCE_ASPECT_FULL 1
#define SCE_ASPECT_169 2
namespace ee {
/*!
* Get the aspect ratio setting of the PS2.
* It is either 4:3, 16:9, or FULL.
*/
int sceScfGetAspect();
/*!
* Get the language setting of the PS2.
* Return a SONY SCE_LANGUAGE value, which differs from GOAL.
*/
int sceScfGetLanguage();
}
#endif // JAK1_LIBSCF_H
+87
View File
@@ -0,0 +1,87 @@
#include <cstring>
#include <cassert>
#include "sif_ee.h"
#include "game/system/iop_thread.h"
#include "game/runtime.h"
namespace ee {
namespace {
::IOP* iop;
}
void LIBRARY_sceSif_register(::IOP* i) {
iop = i;
}
void LIBRARY_INIT_sceSif() {
iop = nullptr;
}
void sceSifInitRpc(unsigned int mode) {
(void)mode;
}
int sceSifRebootIop(const char* imgfile) {
(void)imgfile;
return 1;
}
int sceSifSyncIop() {
return 1;
}
void sceFsReset() {
}
int sceSifLoadModule(const char* name, int arg_size, const char* args) {
if(!strcmp(name, "cdrom0:\\\\DRIVERS\\\\OVERLORD.IRX;1") || !strcmp(name, "host0:binee/overlord.irx")) {
const char* src = args;
char* dst = iop->overlord_arg_data;
int cnt;
iop->overlord_argv[0] = nullptr;
for(cnt = 1; src - args < arg_size; cnt++) {
auto len = strlen(src);
memcpy(dst, src, len + 1);
iop->overlord_argv[cnt] = dst;
dst += len + 1;
src += len + 1;
}
iop->overlord_argc = cnt;
for(int i = 0; i < cnt; i++) {
if(iop->overlord_argv[i])
printf("arg %d : %s\n", i, iop->overlord_argv[i]);
}
iop->set_ee_main_mem(g_ee_main_mem);
iop->send_status(IOP_Status::IOP_OVERLORD_INIT);
iop->wait_for_overlord_init_finish();
}
return 1;
}
int sceMcInit() {
return 1;
}
s32 sceSifCallRpc(sceSifClientData* bd, u32 fno, u32 mode, void* send, s32 ssize, void* recv, s32 rsize, void* end_func, void* end_para) {
assert(!end_func);
assert(!end_para);
assert(mode == 1); // async
iop->kernel.sif_rpc(bd->rpcd.id, fno, mode, send, ssize, recv, rsize);
return 0;
}
s32 sceSifCheckStatRpc(sceSifRpcData* bd) {
iop->signal_run_iop();
return iop->kernel.sif_busy(bd->id);
}
s32 sceSifBindRpc(sceSifClientData* bd, u32 request, u32 mode) {
assert(mode == 1); // async
bd->rpcd.id = request;
bd->serve = (sceSifServeData*)1;
return 0;
}
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef JAK1_SIF_EE_H
#define JAK1_SIF_EE_H
#include "common/common_types.h"
class IOP;
namespace ee {
struct sceSifRpcData {
u8 dummy;
u32 id;
};
struct sceSifServeData {
u8 dummy;
};
struct sceSifClientData {
sceSifRpcData rpcd;
// unsigned int command;
void *buff;
void *gp;
// sceSifEndFunc func;
void *para;
// struct _sif_serve_data *serve;
sceSifServeData *serve;
};
void LIBRARY_sceSif_register(::IOP* i);
void LIBRARY_INIT_sceSif();
void sceSifInitRpc(unsigned int mode);
int sceSifRebootIop(const char* imgfile);
int sceSifSyncIop();
void sceFsReset();
int sceSifLoadModule(const char* name, int arg_size, const char* args);
int sceMcInit();
s32 sceSifCallRpc(sceSifClientData* bd, u32 fno, u32 mode, void* send, s32 ssize, void* recv, s32 rsize, void* end_func, void* end_para);
s32 sceSifCheckStatRpc(sceSifRpcData* bd);
s32 sceSifBindRpc(sceSifClientData* bd, u32 request, u32 mode);
}
#endif // JAK1_SIF_EE_H
+101
View File
@@ -0,0 +1,101 @@
#include <stdexcept>
#include <cassert>
#include "stubs.h"
namespace ee {
s32 sceOpen(const char *filename, s32 flag) {
(void)filename;
(void)flag;
throw std::runtime_error("sceOpen NYI");
}
s32 sceClose(s32 fd) {
(void)fd;
throw std::runtime_error("sceClose NYI");
}
s32 sceRead(s32 fd, void *buf, s32 nbyte) {
(void)fd;
(void)buf;
(void)nbyte;
throw std::runtime_error("sceRead NYI");
}
s32 sceWrite(s32 fd, const void *buf, s32 nbyte) {
(void)fd;
(void)buf;
(void)nbyte;
throw std::runtime_error("sceWrite NYI");
}
s32 sceLseek(s32 fd, s32 offset, s32 where) {
(void)fd;
(void)offset;
(void)where;
throw std::runtime_error("sceLseek NYI");
}
int scePadPortOpen(int port, int slot, void* data) {
(void)port;
(void)slot;
(void)data;
assert(false);
return 0;
}
void sceGsSyncV() {
assert(false);
}
void sceGsSyncPath() {
assert(false);
}
void sceGsResetPath() {
assert(false);
}
void sceGsResetGraph() {
assert(false);
}
void sceDmaSync() {
assert(false);
}
void sceGsPutIMR() {
assert(false);
}
void sceGsGetIMR() {
assert(false);
}
void sceGsExecStoreImage() {
assert(false);
}
void FlushCache() {
assert(false);
}
}
namespace iop {
u32 snd_BankLoadByLoc(u32 sector, u32 unk) {
(void)sector;
(void)unk;
assert(false);
return 0;
}
u32 snd_GetLastLoadError() {
assert(false);
return 0;
}
void snd_ResolveBankXREFS() {
assert(false);
}
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef JAK1_STUBS_H
#define JAK1_STUBS_H
#include "common/common_types.h"
#ifndef SCE_SEEK_SET
#define SCE_SEEK_SET (0)
#endif
#ifndef SCE_SEEK_CUR
#define SCE_SEEK_CUR (1)
#endif
#ifndef SCE_SEEK_END
#define SCE_SEEK_END (2)
#endif
#define SCE_RDONLY 0x0001
#define SCE_WRONLY 0x0002
#define SCE_RDWR 0x0003
#define SCE_NBLOCK 0x0010
#define SCE_APPEND 0x0100
#define SCE_CREAT 0x0200
#define SCE_TRUNC 0x0400
#define SCE_EXCL 0x0800
#define SCE_NOBUF 0x4000
#define SCE_NOWAIT 0x8000
#define SCE_PAD_DMA_BUFFER_SIZE 0x100
namespace ee {
s32 sceOpen(const char *filename, s32 flag);
s32 sceClose(s32 fd);
s32 sceRead(s32 fd, void *buf, s32 nbyte);
s32 sceWrite(s32 fd, const void *buf, s32 nbyte);
s32 sceLseek(s32 fd, s32 offset, s32 where);
void sceGsSyncV();
void sceGsSyncPath();
void sceGsResetPath();
void sceGsResetGraph();
void sceDmaSync();
void sceGsPutIMR();
void sceGsGetIMR();
void sceGsExecStoreImage();
void FlushCache();
int scePadPortOpen(int port, int slot, void* data);
}
namespace iop {
u32 snd_BankLoadByLoc(u32 sector, u32 unk);
u32 snd_GetLastLoadError();
void snd_ResolveBankXREFS();
}
#endif // JAK1_STUBS_H
+264
View File
@@ -0,0 +1,264 @@
/*!
* @file Deci2Server.cpp
* Basic implementation of a DECI2 server.
* Works with deci2.cpp (sceDeci2) to implement the networking on target
*/
#include <cstdio>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <cassert>
#include <utility>
#include "common/listener_common.h"
#include "common/versions.h"
#include "Deci2Server.h"
Deci2Server::Deci2Server(std::function<bool()> shutdown_callback) {
buffer = new char[BUFFER_SIZE];
want_exit = std::move(shutdown_callback);
}
Deci2Server::~Deci2Server() {
// if accept thread is running, kill it
if (accept_thread_running) {
kill_accept_thread = true;
accept_thread.join();
accept_thread_running = false;
}
delete[] buffer;
if (server_fd >= 0) {
close(server_fd);
}
if (new_sock >= 0) {
close(new_sock);
}
}
/*!
* Start waiting for the Listener to connect
*/
bool Deci2Server::init() {
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
server_fd = -1;
return false;
}
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
printf("[Deci2Server] Failed to setsockopt 1\n");
close(server_fd);
server_fd = -1;
return false;
}
int one = 1;
if (setsockopt(server_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) {
printf("[Deci2Server] Failed to setsockopt 2\n");
close(server_fd);
server_fd = -1;
return false;
}
timeval timeout = {};
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
if (setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
printf("[Deci2Server] Failed to setsockopt 3\n");
close(server_fd);
server_fd = -1;
return false;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(DECI2_PORT);
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
printf("[Deci2Server] Failed to bind\n");
close(server_fd);
server_fd = -1;
return false;
}
if (listen(server_fd, 0) < 0) {
printf("[Deci2Server] Failed to listen\n");
close(server_fd);
server_fd = -1;
return false;
}
server_initialized = true;
accept_thread_running = true;
kill_accept_thread = false;
accept_thread = std::thread(&Deci2Server::accept_thread_func, this);
return true;
}
/*!
* Return true if the listener is connected.
*/
bool Deci2Server::check_for_listener() {
if (server_connected) {
if (accept_thread_running) {
accept_thread.join();
accept_thread_running = false;
}
return true;
} else {
return false;
}
}
/*!
* Send data from buffer. User must provide appropriate headers.
*/
void Deci2Server::send_data(void* buf, u16 len) {
lock();
if (!server_connected) {
printf("[DECI2] send while not connected, not sending!\n");
} else {
uint16_t prog = 0;
while (prog < len) {
auto wrote = write(new_sock, (char*)(buf) + prog, len - prog);
prog += wrote;
if (!server_connected || want_exit()) {
unlock();
return;
}
}
}
unlock();
}
/*!
* Lock the DECI mutex. Should be done before modifying protocols.
*/
void Deci2Server::lock() {
deci_mutex.lock();
}
/*!
* Unlock the DECI mutex. Should be done after modifying protocols.
*/
void Deci2Server::unlock() {
deci_mutex.unlock();
}
/*!
* Wait for protocols to become ready.
* This avoids the case where we receive messages before protocol handlers are set up.
*/
void Deci2Server::wait_for_protos_ready() {
if (protocols_ready)
return;
std::unique_lock<std::mutex> lk(deci_mutex);
cv.wait(lk, [&] { return protocols_ready; });
}
/*!
* Inform server that protocol handlers are ready.
* Will unblock wait_for_protos_ready and incoming messages will be dispatched to these
* protocols. You can change the protocol handlers, but you should lock the mutex before
* doing so.
*/
void Deci2Server::send_proto_ready(Deci2Driver* drivers, int* driver_count) {
lock();
d2_drivers = drivers;
d2_driver_count = driver_count;
protocols_ready = true;
unlock();
cv.notify_all();
}
void Deci2Server::run() {
int desired_size = (int)sizeof(Deci2Header);
int got = 0;
while (got < desired_size) {
assert(got + desired_size < BUFFER_SIZE);
auto x = read(new_sock, buffer + got, desired_size - got);
if (want_exit()) {
return;
}
got += x > 0 ? x : 0;
}
auto* hdr = (Deci2Header*)(buffer);
printf("[DECI2] Got message:\n");
printf(" %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst);
hdr->rsvd = got;
// see what protocol we got:
lock();
int handler = -1;
for (int i = 0; i < *d2_driver_count; i++) {
auto& prot = d2_drivers[i];
if (prot.active && prot.protocol) {
if (handler != -1) {
printf("[DECI2] Warning: more than on protocol handler for this message!\n");
}
handler = i;
}
}
if (handler == -1) {
printf("[DECI2] Warning: no handler for this message, ignoring...\n");
unlock();
return;
// throw std::runtime_error("no handler!");
}
auto& driver = d2_drivers[handler];
int sent_to_program = 0;
while (!want_exit() && (hdr->rsvd < hdr->len || sent_to_program < hdr->rsvd)) {
// send what we have to the program
if (sent_to_program < hdr->rsvd) {
// driver.next_recv_size = 0;
// driver.next_recv = nullptr;
driver.recv_buffer = buffer + sent_to_program;
driver.available_to_receive = hdr->rsvd - sent_to_program;
(driver.handler)(DECI2_READ, driver.available_to_receive, driver.opt);
// memcpy(driver.next_recv, buffer + sent_to_program, driver.next_recv_size);
sent_to_program += driver.recv_size;
}
// receive from network
if (hdr->rsvd < hdr->len) {
auto x = read(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd);
if (want_exit()) {
return;
}
got += x > 0 ? x : 0;
hdr->rsvd += got;
}
}
(driver.handler)(DECI2_READDONE, 0, driver.opt);
unlock();
}
/*!
* Background thread for waiting for the listener.
*/
void Deci2Server::accept_thread_func() {
socklen_t l = sizeof(addr);
while (!kill_accept_thread) {
new_sock = accept(server_fd, (sockaddr*)&addr, &l);
if (new_sock >= 0) {
u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR};
send(new_sock, &versions, 8, 0); // todo, check result?
server_connected = true;
return;
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/*!
* @file Deci2Server.h
* Basic implementation of a DECI2 server.
* Works with deci2.cpp (sceDeci2) to implement the networking on target
*/
#ifndef JAK1_DECI2SERVER_H
#define JAK1_DECI2SERVER_H
#include <netinet/in.h>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include "game/system/deci_common.h"
class Deci2Server {
public:
static constexpr int BUFFER_SIZE = 32 * 1024 * 1024;
Deci2Server(std::function<bool()> shutdown_callback);
~Deci2Server();
bool init();
bool check_for_listener();
void send_data(void* buf, u16 len);
void lock();
void unlock();
void wait_for_protos_ready();
void send_proto_ready(Deci2Driver* drivers, int* driver_count);
void run();
private:
void accept_thread_func();
bool kill_accept_thread = false;
char* buffer = nullptr;
int server_fd;
sockaddr_in addr;
int new_sock;
bool server_initialized = false;
bool accept_thread_running = false;
bool server_connected = false;
std::function<bool()> want_exit;
std::thread accept_thread;
std::condition_variable cv;
bool protocols_ready = false;
std::mutex deci_mutex;
Deci2Driver* d2_drivers = nullptr;
int* d2_driver_count = nullptr;
};
#endif // JAK1_DECI2SERVER_H
+314
View File
@@ -0,0 +1,314 @@
#include <cassert>
#include <cstring>
#include "IOP_Kernel.h"
#include "game/sce/iop.h"
/*!
* Create a new thread. Will not run the thread.
*/
s32 IOP_Kernel::CreateThread(std::string name, u32 (*func)()) {
if(_currentThread != -1) throw std::runtime_error("tried to create thread from thread");
u32 ID = (u32)_nextThID++;
if(threads.size() != ID) throw std::runtime_error("thread number error?");
// add entry
threads.emplace_back(name, func, ID, this);
// setup the thread!
// printf("[IOP Kernel] SetupThread %s...\n", name.c_str());
// hack to allow creating a "null thread" which doesn't/can't run but occupies slot 0.
if(func) {
_currentThread = ID;
// create OS thread, will run the setupThread function
threads.back().thread = new std::thread(&IOP_Kernel::setupThread, this, ID);
// wait for thread to finish setup.
threads.back().waitForReturnToKernel();
// ensure we are back in the kernel.
_currentThread = -1;
}
return ID;
}
/*!
* Start a thread. Runs it once, then marks it to run on each dispatch of the IOP kernel.
*/
void IOP_Kernel::StartThread(s32 id) {
threads.at(id).started = true; // mark for run
runThread(id); // run now
}
/*!
* Wrapper around entry for a thread.
*/
void IOP_Kernel::setupThread(s32 id) {
// printf("\tthread %s has started!\n", threads.at(id).name.c_str());
returnToKernel();
threads.at(id).waitForDispatch();
// printf("[IOP Kernel] Thread %s first dispatch!\n", threads.at(id).name.c_str());
if(_currentThread != id) {
throw std::runtime_error("the wrong thread has run!\n");
}
(threads.at(id).function)();
printf("Thread %s has returned!\n", threads.at(id).name.c_str());
threads.at(id).done = true;
returnToKernel();
}
/*!
* Run a thread (call from kernel)
*/
void IOP_Kernel::runThread(s32 id) {
if(_currentThread != -1) throw std::runtime_error("tried to runThread in a thread");
_currentThread = id;
threads.at(id).dispatch();
threads.at(id).waitForReturnToKernel();
_currentThread = -1;
}
/*!
* Suspend a thread (call from user thread). Will simply allow other threads to run.
* Unless we are sleeping, in which case this will return when we are woken up
* Like yield
*/
void IOP_Kernel::SuspendThread() {
s32 oldThread = getCurrentThread();
threads.at(oldThread).returnToKernel();
threads.at(oldThread).waitForDispatch();
if(_currentThread != oldThread) {
throw std::runtime_error("bad resume");
}
}
/*!
* Sleep a thread. Must be explicitly woken up.
*/
void IOP_Kernel::SleepThread() {
if(getCurrentThread() == -1) {
mainThreadSleep = true;
while(mainThreadSleep) {
dispatchAll();
}
} else {
threads.at(getCurrentThread()).started = false;
SuspendThread();
}
}
/*!
* Wake up a thread. Doesn't run it immediately though.
*/
void IOP_Kernel::WakeupThread(s32 id) {
if(id == -1) {
mainThreadSleep = false;
} else {
threads.at(id).started = true;
}
// todo, should we ever switch directly to that thread?
}
/*!
* Dispatch all IOP threads.
*/
void IOP_Kernel::dispatchAll() {
for(u64 i = 0; i < threads.size(); i++) {
if(threads[i].started && !threads[i].done) {
// printf("[IOP Kernel] Dispatch %s (%ld)\n", threads[i].name.c_str(), i);
_currentThread = i;
threads[i].dispatch();
threads[i].waitForReturnToKernel();
_currentThread = -1;
//printf("[IOP Kernel] back to kernel!\n");
}
}
}
/*!
* Start running kernel.
*/
void IopThreadRecord::returnToKernel() {
runThreadReady = false;
if(kernel->getCurrentThread() != thID) throw std::runtime_error("tried to sleep the wrong thread!");
{
std::lock_guard<std::mutex> lck(*threadToKernelMutex);
syscallReady = true;
}
threadToKernelCV->notify_one();
}
/*!
* Start running thread.
*/
void IopThreadRecord::dispatch() {
syscallReady = false;
if(kernel->getCurrentThread() != thID) throw std::runtime_error("tried to dispatch the wrong thread!");
{
std::lock_guard<std::mutex> lck(*kernelToThreadMutex);
runThreadReady = true;
}
kernelToThreadCV->notify_one();
}
/*!
* Kernel waits for thread to return
*/
void IopThreadRecord::waitForReturnToKernel() {
std::unique_lock<std::mutex> lck(*threadToKernelMutex);
threadToKernelCV->wait(lck, [this]{return syscallReady;});
// syscallReady = false;
}
/*!
* Thread waits for kernel to dispatch it.
*/
void IopThreadRecord::waitForDispatch() {
//if(kernel->getCurrentThread() == -1) throw std::runtime_error("tried to suspend main!\n");
std::unique_lock<std::mutex> lck(*kernelToThreadMutex);
kernelToThreadCV->wait(lck, [this]{return runThreadReady;});
//runThreadReady = false;
}
void IOP_Kernel::set_rpc_queue(iop::sceSifQueueData *qd, u32 thread) {
for(const auto& r : sif_records) {
assert(!(r.qd == qd || r.thread_to_wake == thread));
}
SifRecord rec;
rec.thread_to_wake = thread;
rec.qd = qd;
sif_records.push_back(rec);
}
typedef void * (* sif_rpc_handler)(unsigned int,void *,int);
bool IOP_Kernel::sif_busy(u32 id) {
sif_mtx.lock();
bool rv = false;
bool found = false;
for(auto& r : sif_records) {
if(r.qd->serve_data->command == id) {
rv = !r.cmd.finished;
found = true;
break;
}
}
assert(found);
sif_mtx.unlock();
return rv;
}
void IOP_Kernel::sif_rpc(s32 rpcChannel, u32 fno, bool async, void *sendBuff, s32 sendSize, void *recvBuff,
s32 recvSize) {
assert(async);
sif_mtx.lock();
// step 1 - find entry
SifRecord* rec = nullptr;
for(auto& e : sif_records) {
if(e.qd->serve_data->command == (u32)rpcChannel) {
rec = &e;
}
}
assert(rec);
// step 2 - check entry is safe to give command to
assert(rec->cmd.finished && rec->cmd.started);
// step 3 - memcpy!
memcpy(rec->qd->serve_data->buff, sendBuff, sendSize);
// step 4 - setup command
rec->cmd.buff = rec->qd->serve_data->buff;
rec->cmd.size = sendSize;
rec->cmd.fno = fno;
rec->cmd.copy_back_buff = recvBuff;
rec->cmd.copy_back_size = recvSize;
rec->cmd.started = false;
rec->cmd.finished = false;
sif_mtx.unlock();
}
void IOP_Kernel::rpc_loop(iop::sceSifQueueData* qd) {
while(true) {
bool got_cmd = false;
SifRpcCommand cmd;
sif_rpc_handler func = nullptr;
// get command and mark it as started if we get it
sif_mtx.lock();
for(auto& r : sif_records) {
if(r.qd == qd) {
cmd = r.cmd;
got_cmd = true;
r.cmd.started = true;
func = r.qd->serve_data->func;
}
}
sif_mtx.unlock();
// handle command
if(got_cmd) {
if(cmd.shutdown_now) {
return;
}
if(!cmd.started) {
// cf
assert(func);
auto data = func(cmd.fno, cmd.buff, cmd.size);
if(cmd.copy_back_buff && cmd.copy_back_size) {
memcpy(cmd.copy_back_buff, data, cmd.copy_back_size);
}
sif_mtx.lock();
for(auto& r : sif_records) {
if(r.qd == qd) {
assert(r.cmd.started);
r.cmd.finished = true;
}
}
sif_mtx.unlock();
}
}
SuspendThread();
}
}
void IOP_Kernel::read_disc_sectors(u32 sector, u32 sectors, void *buffer) {
if(!iso_disc_file) {
iso_disc_file = fopen("./disc.iso", "rb");
}
assert(iso_disc_file);
if(fseek(iso_disc_file, sector * 0x800, SEEK_SET)) {
assert(false);
}
auto rv = fread(buffer, sectors * 0x800, 1, iso_disc_file);
assert(rv == 1);
}
void IOP_Kernel::shutdown() {
// shutdown most threads
for(auto& r : sif_records) {
r.cmd.shutdown_now = true;
}
for(auto& t : threads) {
t.wantExit = true;
}
for(auto& t : threads) {
if(t.thID == 0) continue;
while(!t.done) {
dispatchAll();
}
t.thread->join();
}
}
IOP_Kernel::~IOP_Kernel() {
if(iso_disc_file) {
fclose(iso_disc_file);
}
}
+180
View File
@@ -0,0 +1,180 @@
#ifndef JAK_IOP_KERNEL_H
#define JAK_IOP_KERNEL_H
#include <thread>
#include <string>
#include <queue>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "common/common_types.h"
class IOP_Kernel;
namespace iop {
struct sceSifQueueData;
}
struct SifRpcCommand {
bool started = true;
bool finished = true;
bool shutdown_now = false;
void* buff;
int fno;
int size;
void* copy_back_buff;
int copy_back_size;
};
struct SifRecord {
iop::sceSifQueueData* qd;
SifRpcCommand cmd;
u32 thread_to_wake;
};
struct IopThreadRecord {
IopThreadRecord(std::string n, u32 (*f)(), s32 ID, IOP_Kernel* k) : name(n), function(f), thID(ID), kernel(k) {
kernelToThreadCV = new std::condition_variable;
threadToKernelCV = new std::condition_variable;
kernelToThreadMutex = new std::mutex;
threadToKernelMutex = new std::mutex;
}
~IopThreadRecord() {
delete kernelToThreadCV;
delete threadToKernelCV;
delete kernelToThreadMutex;
delete threadToKernelMutex;
delete thread;
}
std::string name;
u32 (*function)();
std::thread* thread = nullptr;
bool wantExit = false;
bool started = false;
bool done = false;
s32 thID = -1;
IOP_Kernel* kernel;
bool runThreadReady = false;
bool syscallReady = false;
std::mutex *kernelToThreadMutex, *threadToKernelMutex;
std::condition_variable *kernelToThreadCV, *threadToKernelCV;
void returnToKernel();
void waitForReturnToKernel();
void waitForDispatch();
void dispatch();
};
class IOP_Kernel {
public:
IOP_Kernel() {
// this ugly hack
threads.reserve(16);
CreateThread("null-thread", nullptr);
CreateMbx();
}
~IOP_Kernel();
s32 CreateThread(std::string n, u32 (*f)());
void StartThread(s32 id);
void SuspendThread();
void SleepThread();
void WakeupThread(s32 id);
void dispatchAll();
void set_rpc_queue(iop::sceSifQueueData *qd, u32 thread);
void rpc_loop(iop::sceSifQueueData* qd);
void shutdown();
/*!
* Resume the kernel.
*/
void returnToKernel() {
if(_currentThread < 0) throw std::runtime_error("tried to return to kernel not in a thread");
threads[_currentThread].returnToKernel();
}
/*!
* Get current thread ID.
*/
s32 getCurrentThread() {
return _currentThread;
}
/*!
* Create a message box
*/
s32 CreateMbx() {
s32 id = mbxs.size();
mbxs.emplace_back();
return id;
}
/*!
* Set msg to thing if its there and pop it.
* Returns if it got something.
*/
s32 PollMbx(void** msg, s32 mbx) {
if(_currentThread != -1 && threads.at(_currentThread).wantExit) {
// total hack - returning this value causes the ISO thread to error out and quit.
return -0x1a9;
}
// printf("poll %d %ld\n", mbx, mbxs.size());
if(mbx >= (s32) mbxs.size()) throw std::runtime_error("invalid PollMbx");
s32 gotSomething = mbxs[mbx].empty() ? 0 : 1;
if(gotSomething) {
void* thing = mbxs[mbx].front();
// printf("pop from msgbox %d %p\n", mbx, thing);
if(msg)
*msg = thing;
mbxs[mbx].pop();
}
return gotSomething ? 0 : -424;
}
/*!
* Push something into a mbx
*/
s32 SendMbx(s32 mbx, void* value) {
if(mbx >= (s32) mbxs.size()) throw std::runtime_error("invalid SendMbx");
mbxs[mbx].push(value);
// printf("push into messagebox %d %p\n", mbx, value);
// printf("mbx size %ld\n", mbxs.size());
return 0;
}
s32 CreateSema() {
return 1;
}
void read_disc_sectors(u32 sector, u32 sectors, void* buffer);
bool sif_busy(u32 id);
void sif_rpc(s32 rpcChannel, u32 fno, bool async, void *sendBuff, s32 sendSize, void *recvBuff, s32 recvSize);
private:
void setupThread(s32 id);
void runThread(s32 id);
s32 _nextThID = 0;
std::atomic<s32> _currentThread = {-1};
std::vector<IopThreadRecord> threads;
std::vector<std::queue<void*>> mbxs;
std::vector<SifRecord> sif_records;
bool mainThreadSleep = false;
FILE* iso_disc_file = nullptr;
std::mutex sif_mtx;
};
#endif //JAK_IOP_KERNEL_H
+167
View File
@@ -0,0 +1,167 @@
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "SystemThread.h"
//////////////////////
// Thread Manager //
//////////////////////
/*!
* Create a new thread with the given name.
*/
SystemThread& SystemThreadManager::create_thread(const std::string& name) {
if (thread_count >= MAX_SYSTEM_THREADS) {
throw std::runtime_error("Out of System Threads! Please increase MAX_SYSTEM_THREADS");
}
auto& thread = threads[thread_count];
// reset thread
thread.initialization_complete = false;
thread.name = name;
thread.id = thread_count;
thread.manager = this;
thread_count++;
return thread;
}
/*!
* Print the CPU usage statistics for all threads.
*/
void SystemThreadManager::print_stats() {
double total_user = 0, total_kernel = 0;
printf("%8s | %5s | %5s\n", "Name", "User", "Kernel");
printf("--------------------------\n");
for (int id = 0; id < thread_count; id++) {
auto& thread = threads[id];
printf("%8s | %5.1f | %5.1f\n", thread.name.c_str(), thread.cpu_user * 100.,
thread.cpu_kernel * 100.);
total_kernel += thread.cpu_kernel;
total_user += thread.cpu_user;
}
printf("%8s | %5.1f | %5.1f\n\n", "#TOTAL#", total_user * 100., total_kernel * 100.);
}
/*!
* Request all threads to stop
*/
void SystemThreadManager::shutdown() {
for (int i = 0; i < thread_count; i++) {
printf("# Stop %s\n", threads[i].name.c_str());
threads[i].stop();
}
}
/*!
* Join all threads, if they are running
*/
void SystemThreadManager::join() {
for (int i = 0; i < thread_count; i++) {
printf("# Join %s\n", threads[i].name.c_str());
if (threads[i].running) {
threads[i].join();
}
}
}
/*!
* bootstrap function to call a SystemThread's function
*/
void* bootstrap_thread_func(void* x) {
SystemThread* thd = (SystemThread*)x;
SystemThreadInterface interface(thd);
thd->function(interface);
printf("[SYSTEM] Thread %s is returning\n", thd->name.c_str());
return nullptr;
}
/*!
* Start a thread and wait for its initialization
*/
void SystemThread::start(std::function<void(SystemThreadInterface&)> f) {
printf("# Initialize %s...\n", name.c_str());
function = f;
pthread_create(&thread, nullptr, bootstrap_thread_func, this);
running = true;
// and wait for initialization
{
std::unique_lock<std::mutex> mlk(initialization_mutex);
while (!initialization_complete) {
initialization_cv.wait(mlk);
}
}
}
/*!
* Join a system thread
*/
void SystemThread::join() {
void* x;
pthread_join(thread, &x);
running = false;
}
/*!
* Set flag in system thread so want_exit() returns true.
*/
void SystemThread::stop() {
want_exit = true;
}
/*!
* Signal from a thread that initialization has complete, and the caller of SystemThread::start()
* will be unblocked.
*/
void SystemThreadInterface::initialization_complete() {
std::unique_lock<std::mutex> mlk(thread.initialization_mutex);
thread.initialization_complete = true;
thread.initialization_cv.notify_all();
printf(" OK\n");
}
/*!
* Should we try and exit?
*/
bool SystemThreadInterface::get_want_exit() const {
return thread.want_exit;
}
/*!
* Trigger a full system shutdown.
*/
void SystemThreadInterface::trigger_shutdown() {
thread.manager->shutdown();
}
#include <sys/time.h>
#include <sys/resource.h>
/*!
* Get thread performance statistics and report them.
*/
void SystemThreadInterface::report_perf_stats() {
if (thread.stat_diff_timer.getMs() > 16.f) {
thread.stat_diff_timer.start();
uint64_t current_ns = thread.stats_timer.getNs();
rusage stats;
getrusage(RUSAGE_THREAD, &stats);
uint64_t current_kernel = stats.ru_stime.tv_usec + (1000000 * stats.ru_stime.tv_sec);
uint64_t current_user = stats.ru_utime.tv_usec + (1000000 * stats.ru_utime.tv_sec);
uint64_t ns_dt = current_ns - thread.last_collection_nanoseconds;
uint64_t dt_kernel = current_kernel - thread.last_cpu_kernel;
uint64_t dt_user = current_user - thread.last_cpu_user;
thread.cpu_kernel = dt_kernel * 1000. / (double)ns_dt;
thread.cpu_user = dt_user * 1000. / (double)ns_dt;
thread.last_cpu_kernel = current_kernel;
thread.last_cpu_user = current_user;
thread.last_collection_nanoseconds = current_ns;
}
}
+89
View File
@@ -0,0 +1,89 @@
/*!
* @file SystemThread.h
* Threads for the runtime.
*/
#ifndef RUNTIME_SYSTEMTHREAD_H
#define RUNTIME_SYSTEMTHREAD_H
#include <string>
#include <functional>
#include <pthread.h>
#include <array>
#include <mutex>
#include <condition_variable>
#include "Timer.h"
constexpr int MAX_SYSTEM_THREADS = 16;
class SystemThreadInterface;
class SystemThreadManager;
/*!
* Runs a function in a thread and provides a SystemThreadInterface to that function.
* Once the thread is ready, it should tell the interface with intitialization_complete().
* Thread functions should try to return when get_want_exit() returns true.
* Thread functions should also call report_perf_stats every now and then to update performance
* statistics.
*/
class SystemThread {
public:
void start(std::function<void(SystemThreadInterface&)> f);
void join();
void stop();
SystemThread() = default;
private:
friend class SystemThreadInterface;
friend class SystemThreadManager;
friend void* bootstrap_thread_func(void* thd);
std::string name = "invalid";
pthread_t thread;
SystemThreadManager* manager;
std::function<void(SystemThreadInterface &)> function;
bool initialization_complete = false;
std::mutex initialization_mutex;
std::condition_variable initialization_cv;
Timer stats_timer;
Timer stat_diff_timer;
double cpu_user = 0, cpu_kernel = 0;
uint64_t last_cpu_user = 0, last_cpu_kernel = 0;
uint64_t last_collection_nanoseconds = 0;
int id = -1;
bool want_exit = false;
bool running = false;
};
/*!
* The interface used by a thread in the runtime.
*/
class SystemThreadInterface {
public:
SystemThreadInterface(SystemThread* p) : thread(*p) {
}
void initialization_complete();
void report_perf_stats();
bool get_want_exit() const;
void trigger_shutdown();
private:
SystemThread& thread;
};
/*!
* A manager of all threads in the runtime.
*/
class SystemThreadManager {
public:
SystemThread& create_thread(const std::string& name);
void print_stats();
void shutdown();
void join();
private:
std::array<SystemThread, MAX_SYSTEM_THREADS> threads;
int thread_count = 0;
};
#endif //RUNTIME_SYSTEMTHREAD_H

Some files were not shown because too many files have changed in this diff Show More