mirror of
https://github.com/open-goal/jak-project
synced 2026-08-24 07:30:24 -04:00
check in existing work
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
BasedOnStyle: Chromium
|
||||
ColumnLimit: 100
|
||||
SortIncludes: false
|
||||
@@ -0,0 +1,4 @@
|
||||
# for clion
|
||||
cmake-build-debug/*
|
||||
.idea/*
|
||||
build/*
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "third-party/googletest"]
|
||||
path = third-party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -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)`
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(¶ms);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "sbank.h"
|
||||
|
||||
void InitBanks() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef JAK_V2_SBANK_H
|
||||
#define JAK_V2_SBANK_H
|
||||
|
||||
void InitBanks();
|
||||
|
||||
#endif //JAK_V2_SBANK_H
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <assert.h>
|
||||
#include "soundcommon.h"
|
||||
|
||||
|
||||
void PrintBankInfo(void* buffer) {
|
||||
(void)buffer;
|
||||
assert(false);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef JAK_V2_SOUNDCOMMON_H
|
||||
#define JAK_V2_SOUNDCOMMON_H
|
||||
|
||||
void PrintBankInfo(void* buffer);
|
||||
|
||||
#endif //JAK_V2_SOUNDCOMMON_H
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <cstring>
|
||||
#include "srpc.h"
|
||||
|
||||
u8 gMusicTweakInfo[0x204];
|
||||
|
||||
void srpc_init_globals() {
|
||||
memset(gMusicTweakInfo, 0, sizeof(gMusicTweakInfo));
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "ssound.h"
|
||||
|
||||
void InitSound_Overlord() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef JAK_V2_SSOUND_H
|
||||
#define JAK_V2_SSOUND_H
|
||||
|
||||
void InitSound_Overlord();
|
||||
|
||||
#endif //JAK_V2_SSOUND_H
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <assert.h>
|
||||
#include "stream.h"
|
||||
|
||||
u32 STRThread() {
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 PLAYThread() {
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "libscf.h"
|
||||
|
||||
namespace ee {
|
||||
int sceScfGetAspect() {
|
||||
return SCE_ASPECT_169;
|
||||
}
|
||||
|
||||
int sceScfGetLanguage() {
|
||||
return SCE_ENGLISH_LANGUAGE;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user